今年一月份的時候,筆者受命去為一家營造公司上課,課程的主題,除了講解一般所謂的資訊系統為何之外,還包括了何謂物件導向的程式設計,時間兩個小時。本來以為這是一項輕鬆簡單的任務,但是沒想到老闆交代客戶的要求是:上課內容不但要豐富精彩,而且重點必須是放在『物件導向的程式設計』這上面,這下子筆者的臉上不但冒出許多小丸子線,而且任務一下子就變成了『不可能的任務』,因為短短的兩個小時,除了扣掉自我介紹、嚥口水、咳嗽、打噴嚏,以及段落之間的休止符之外,大概剩下不了多少時間。所以要在這麼短的時間內介紹這樣的主題,筆者只好盡量撿一些重點來說明,所以可想而知的結果是,筆者在台上比手劃腳,講的口沫橫飛,台下則是問號掉了一地。不過在準備教材的過程中卻讓筆者想起了兩段往事。
第一件事情是在
1995 年年初,我的第一個老闆突然跑來跟我說,『公司即日起開放員工申請訂購工作上所需的雜誌或書籍,這是申請單,你填一下吧!』,我記得當時只填了一份高煥堂先生的『物件導向雜誌』。過了沒多久,老闆就一臉臭臭的回覆我說:『這本雜誌用的是
VB 作範例,而
VB 又不是物件導向語言,沒有參考的價值』所以我的申請,就這麼被駁回了。第二件事情是在
1995 年年中時,筆者參加中台灣地區的
Delphi 研討會,記得當時講師說了一段話,讓筆者印象十分深刻。他說:『我們使用
Delphi 來開發應用系統,其實就已經在用物件導向的方式來寫程式了,因為在
Delphi 之中,我們幾乎都是在物件的事件中,使用物件的屬性以及方法來解決事情。』
這兩件事情跟本文的主題有何關係呢?其實關係大了。因為,如果筆者詢問,什麼是物件導向的程式設計呢?相信很多讀者都可以答覆筆者說:『物件導向的程式設計,有別於傳統水瀑式(Water
Fall)的程式設計以模組作為思考基礎,而它則是以物件作為思考對象,進而進行程式的分割與應用。』接著告訴筆者,現在有哪些程式語言是支援物件導向的。嗯!這些答案都是滿標準,蠻制式的。但是基本上,還是沒有說清楚什麼是物件導向的程式設計?但是如果答案是上述講師所描述的那樣,那麼事實恐怕也不盡然!
根據
Brad Cox 於
1980 年所發表文獻中指出,所謂的物件導向應該具備下列的特性:
-
物件(Object)
以及訊息(Message)
-
繼承(Inheritance)。
-
封裝(Encapsulation)。
-
動態連結(Dynamic
Binding)。
而其中繼承、封裝、動態連結則又是物件導向中,最重要也具盛名的特性,所以很多人都據此來評斷,哪種語言或工具是不是支援
OO,特別是我們可以在一些論壇之中,常常可以見到諸如這樣的評語:『VB
不支援類別(Class)的繼承概念,同時也不支援資訊封裝的方法,所以
VB 不是一個
OO 程式語言』、『Java
由於不支援指標型態,因此更能徹底封裝類別的資訊,跟
C++ 比較起來更能貼近
OO 的精神』然而對於這些評語,筆者姑且不討論他們之間的對錯,只是覺得發表這些評論的人,把上述
OO 的特性,當作是個『定理』來遵從了,所以才造成論壇中的烽煙四起、口水肆溢的現象出現。其實,事實上在各家程式語言之中,對於
OO 支援的程度都不一致,特別是連號稱
OO 的先驅
C++ 都是如此,所以如果我們硬是以某一種語言作為標準,來評斷其他語言作法上並不公平。因此筆者根據
Brad Cox 所公布的
OO 特性,在此大膽的進行一項假設,『凡是支援繼承、封裝、動態連結的程式語言或工具,我們可以說它們是支援
OO 的,反之則亦然』。現在我們就對這個假設開始進行驗證,不過在此之前我們首先對剛剛所謂的
OO 特性進行以下的定義:
-
繼承
→ Source 的
Reuse。
-
封裝
→ 保護程式中的資訊,是透過驗證過的程序來存取。
-
動態連結
→ 於執行時期決定執行物件的種類與分法。
我們以繼承與封裝為例,並且以素有
OO 之稱的
Delphi,與不支援 OO
的 VB 兩種工具,來逐一比對它們之間的差異。
type
TA = class(TObject)
public
procedure SayHello;
end;
TB = class(TA)
public
procedure SayHello;
end;
procedure
TB.SayHello;
begin
Inherited;
ShowMessage('Hello by Class B ...');
end;
procedure
TA.SayHello;
begin
ShowMessage('Hello by Class A ...');
end;
上面的範例是一個簡單繼承的例子,我們可以看出其中的繼承關係,其實只不過是從一個函數再去呼叫另一個函數而已,所以如果單單從這個觀點來看的話,VB
也是可以做的到的。
TC
= class(TObjct)
private
FCaption: String;
function GetCaption: String;
procedure SetCaption(const Value: String);
public
property Caption: String read GetCaption write SetCaption;
end;
上面是一個資訊封裝的例子,其中屬性
Caption 必須透過
GetCaption 與
SetCaption 這兩個方法才能存取得到,目的在於保護
FCaption 不會被任意的存取。現在我們再看看下面
VB 的程式碼。
Option
Explicit
Private
pMember() As String
Private
pCount As Integer
Private
Sub Class_Initialize()
pCount = 0
End
Sub
Public
Property Get Count() As Variant
Count = pCount
End
Property
Public
Sub Add(vNewValue As String)
pCount = pCount + 1
ReDim Preserve pMember(pCount - 1)
pMember(pCount - 1) = vNewValue
End
Sub
Public
Sub Clear()
pCount = 0
ReDim pMember(pCount)
End
Sub
Public
Sub MoveItem(vIdx As Integer)
Dim i As Integer
For i = vIdx To UBound(pMember) - 1
pMember(i) = pMember(i + 1)
Next
pCount = pCount - 1
ReDim Preserve pMember(pCount)
End
Sub
Public
Function IndexOF(vSearch As String) As Integer
Dim i As Integer
IndexOF = -1
For i = LBound(pMember) To UBound(pMember)
If vSearch = pMember(i) Then
IndexOF = i
Exit Function
End If
Next
End
Function
Public
Function Item(vIdx As Integer) As String
Item = pMember(vIdx)
End
Function
Public
Sub SetItem(vIdx As Integer, ByVal vNewValue As Variant)
pMember(vIdx) = vNewValue
End
Sub
以上是我用
VB 寫的一個
cls 檔(即是
VB 的物件類別模組),用以實做一個
TSingleList,雖然它不是所謂的
Class 形式,但是透過
private 與
public 的宣告,一樣具有所謂資訊封裝的效果。
procedure
TForm1.Button1Click(Sender: TObject);
begin
TWidgetControl(Sender).Enabled := not
TWidgetControl(Sender).Enabled;
end;
上面的範例是
Delphi 的一個動態連結的範例,我們可以看到參數
Sender 在編輯時期,並未確定它的真正型態,而是到了執行時期,我們才藉由
TWidgetControl 類別取得
Enabled 的屬性值。而這樣的效果在
VB 中,用不定型態,物件變數或者利用
COM 的特性,一樣可以達成。例如我們可以運用物件變數(如:Object、
Control),來達到這樣的結果:
Public
Sub EnableControl( Sender as Object)
Sender.Enabled = not Sender.Enabled
End
Sub
當然,若要檢查型態,只要運用
Typeof 或
TypeName 來確認型態即可,我們可以對上面的程式做些修改:
Public
Sub EnableControl( Sender as Object)
If TypeName(Sender) = "TWidgetControl" then
Sender.Enabled = not Sender.Enabled
End If
End
Sub
從上面的推演結果,相信讀者已經認可筆者的假設是可以獲得成立的,也就是說
VB 是支援
OO 的程式語言。嗯!這樣的說法相當弔詭,而且在理論基礎上也有些牽強,不過本文撰寫的目的,不是要賣弄詭辯之術,而是要藉此機會向各位讀者說明,其實物件導向程式設計是一種『設計的方法』、一種『思考模式』而不是一種『定理』。這樣說太模糊了,我們還是範例來說明吧!
右圖的範例是一個含有三組以三個
Button作為
Group 的程式,當使用者按下第一個按鈕時,會
Disable 本身,並且
Enable 第二個按鈕,同理其他的按鈕也是如此,這樣的作法,在於讓使用者依次序來按下
Button 而不讓有次序不同的操作出現,而這個範例之中,筆者提供了三種不同的撰寫方式,現在我們來看看它們的程式碼是如何的:
第一種,分別在各自
Button 的
OnClick 事件撰寫處理的程式碼。
procedure
TForm1.Button1Click(Sender: TObject);
begin
Button1.Enabled := False;
Button2.Enabled := True;
Button3.Enabled := False;
end;
procedure
TForm1.Button2Click(Sender: TObject);
begin
Button1.Enabled := False;
Button2.Enabled := False;
Button3.Enabled := True;
end;
procedure
TForm1.Button3Click(Sender: TObject);
begin
Button1.Enabled := True;
Button2.Enabled := False;
Button3.Enabled := False;
end;
第二種,在某一個
Button 的
OnClick 事件中撰寫處理程序,然後其他
Button 的
OnClick 事件再指向這個處理程序。
Button4.Enabled := False;
Button5.Enabled := False;
Button6.Enabled := False;
if Sender = Button4 then Button5.Enabled := True;
if Sender = Button5 then Button6.Enabled := True;
if Sender = Button6 then Button4.Enabled := True;
第三種,我們撰寫了一個
TButtonGroup 的新的類別,專門來處理按鈕順序的問題。
type
TButtonGroup = class(TGroupBox)
private
Button1: TButton;
Button2: TButton;
Button3: TButton;
procedure OnClick(Sender: TObject);
public
constructor Create(Owner: TComponent); override;
destructor Destroy; override;
end;
constructor
TButtonGroup.Create(Owner: TComponent);
begin
inherited;
Button1 := TButton.Create(Self);
Button2 := TButton.Create(Self);
Button3 := TButton.Create(Self);
Button1.Parent := Self;
Button2.Parent := Self;
Button3.Parent := Self;
Button1.Name := 'Button7';
Button2.Name := 'Button8';
Button3.Name := 'Button9';
Button1.Top := 25;
Button2.Top := Button1.Top + Button1.Height + 10;
Button3.Top := Button2.Top + Button2.Height + 10;;
Button1.Left := 28;
Button2.Left := 28;
Button3.Left := 28;
Button2.Enabled := False;
Button3.Enabled := False;
Button1.OnClick := OnClick;
Button2.OnClick := OnClick;
Button3.OnClick := OnClick;
end;
destructor
TButtonGroup.Destroy;
begin
Button1.Free;
Button2.Free;
Button3.Free;
inherited;
end;
procedure
TButtonGroup.OnClick(Sender: TObject);
begin
Button1.Enabled := False;
Button2.Enabled := False;
Button3.Enabled := False;
if Sender = Button1 then Button2.Enabled := True;
if Sender = Button2 then Button3.Enabled := True;
if Sender = Button3 then Button1.Enabled := True;
end;
procedure
TForm1.FormShow(Sender: TObject);
begin
BG := TButtonGroup.Create(Self);
BG.Parent := Self;
end;
procedure
TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
BG.Free;
end;
從上面的範例之中,我們可以清楚的看到,雖然程式碼都是由
Delphi 來撰寫,所表現的行為也是相同的,但是很明顯的第一種方法非常的不高明,因為它把程式碼分散到每一個物件的
OnClick 事件中來處理,這時,如果所需要的物件,很少的話,那麼還感覺不出來有何問題,但是,一旦需要控制的物件很多,那麼整支程式,豈不是到處都是類似這種控制方式的程式碼,這豈非是不智之舉,所以我們歸類這種寫法,屬於新手級的寫法。而第二種方法,雖然還是需要在程式之中撰寫控制程式碼,但是明顯地就比第一種方法簡潔許多,所以基本上,我們歸類這種寫法為老手級的寫法,因為這樣寫法已經具備模組化的觀念,程式可以寫的十分地有條不紊。第三種方法我們稱之為物件導向式的寫法,因為這種寫法已經將這種類似物件操作的控制程式碼,轉移到新增的
TButtonGroup 類別之中,所以主程式裡面,就可以完全不用理會那些
Button 之間要如何運作,程式看起來更為簡潔有力。
從上面的範例中,相信聰明的讀者已經感受到同樣的程式,不同寫法之間的差異了。不過這些差異雖然對於使用者而言,是完全感受不到它們之間的不同所在。對於毛主席而言,不管白貓或者是黑貓,也是只要會抓老鼠的貓,就是好貓,但是對於程式員在作法上,差別影響就很大了。因為一支有條不紊的程式,總是比較容易維護。當然,對此有些人可能會不服氣,並且強調這是個人程式撰寫的風格問題,但是筆者要強調的是,撰寫程式原本就是希望要迅速有效的為客戶解決問題,而不是在玩『記憶金庫』(註
1)這樣的遊戲,當然更重要的是,程式要讓別人看的懂,這樣才可以提升軟體的價值,而使用
OO,也正是它提供方法來達成這樣的目的。所以討論到這裡,我們可以回應本文之初所描述的那兩件事情,那就是:雖然
VB 普遍不被人認為是屬於
OO 的程式語言,但是事實上,使用
VB,我們還是可以很
OO 的;同理,既使我們用的是
OO 的程式語言,例如
C++、
Java 等程式語言,一樣可以寫出很不
OO 的系統出來。其中的關鍵,就是程式員是否真的具有
OO 的概念。
下圖是筆者以前用
VB 所撰寫的專案,請留意畫面中,右方專案清單裡的檔案型態,其中除了一般的
vbs,以及
frm 類型的檔案之外,還多了
cls 類型的檔案,而且筆者還故意的使用類似
Delphi 對於類別命名的慣例,在檔案名稱之前加上『T』這個字母,其作法就是仿照
OO 中,對於類別的定義。至於
VB 所編譯出來的程式,以前在工程師間一直被詬病有不穩定的傳聞,但是這支程式在客戶使用的這些年以來,一直都很穩定。這證明了不管使用何種語言,只要採用一個好的方法,的確可以產生一個穩定的程式,因此有誰能說
VB 不好呢!
記得筆者之前所說的物件導向程式設計,是一種設計的方法,一種思考的模式嗎?剛剛筆者的演繹,只是示範了物件導向的設計方法,而物件導向的思考的模式,則又是怎樣的一回事呢?我們以一個範例來說明:
現在我們預備撰寫一部西遊記的遊戲,其中的角色包括唐三藏、孫悟空、豬八戒以及妖怪等。然後在遊戲之中會遇到過河,以及遇到妖怪等事件,在傳統的思考模式之中,系統通常會用切割成下面的樣子。
-
西遊記主程式。
-
過河。
-
遇到妖怪。
-
妖怪遇到三藏師徒。
其中過河部分會包括三個步驟:
-
如果唐三藏過河則划船。
-
如果孫悟空過河則用飛的。
-
如果豬八戒過河則用游的。
而三藏師徒遇到妖怪也會分成三個步驟:
-
如果唐三藏遇到妖怪則念經。
-
如果孫悟空遇到妖怪則斬妖。
-
如果豬八戒遇到妖怪則大喊救命。
而妖怪遇到三藏師徒也會分成三個步驟:
-
如果是唐三藏則吃掉。
-
如果是孫悟空則逃跑。
-
如果是豬八戒則戲弄他。
然而在物件導向的思考模式下,系統通常會用切割成下面的樣子。
-
系統主程式。
-
唐三藏。
a.
過河則划船。
b.
遇到妖怪則念經。
-
孫悟空。
a.
過河則飛。
b.
遇到妖怪則斬妖。
-
豬八戒。
a.
過河則游。
b.
遇到妖怪則呼救。
-
妖怪。
a.
遇到唐三藏則吃。
b.
遇到孫悟空則跑。
c.
遇到豬八戒則戲弄。
聰明的讀者,看出上面範例中的不同點了嗎?很明顯的,在傳統的方式裡雖然模組化可以將程式規劃的很清楚,但是免不了,每一個模組都必須跟全域變數(或物件)糾纏在一起。這樣的結果,在團隊的開發中是非常不利的,因為任何人,任何模組的修正都會影響到系統的正確性。而物件導向的模式則完全沒有這樣的問題。另外還有一個重點是在
OO
的思考模式裡,我們不但可以順利切割整個應用程式的程式碼部分,就連程式中的邏輯部分,也一併切割的清清楚楚。這是一個在開發應用系統中,不容易出錯的最主要關鍵。
討論到這裡,很多人都會問為什麼我們要採用物件導向程式設計(OOP)的方式來設計系統呢?其實原因無他,就是要減少系統開發的時間以及出錯的機率。然而很遺憾的,在筆者接觸許多國內的案例中,雖然他們都是使用
Java、 Delphi、或者是 C++,但是卻鮮少有人真正使用物件導向的思維來設計程式,更甚者,連基本模組化的工作,都做不好的,更大有人在,這是很令人痛心的現象。因為這樣的結果,將導致國內的資訊市場中,充斥一些不健全的系統,而在劣幣驅逐良幣的情況下,國內將根本無法擺脫軟體代工的命運。而所謂的資訊人,或許
說是有機會看到這篇文章的一群人而言,也會因為環境的因素,逐漸演變成一頭受過高等教育但是只會拉磨的驢子而已。筆者在此並無意自抬身價,只是想藉由本文傳達一個訊息,那就是,其實我們還有更好的選擇,可以讓我們的未來更加美好而已。
註 1:『記憶金庫』是一種開啟保險庫的遊戲,由旁人隨機唸出一堆密碼,然後再測試玩者能記憶多少個密碼的遊戲。
註
2:有關上述的範例程式,請點擊下載。
參考文獻(Bibliography):
-
[Anderson.,1989].J.a.Anderson,J.McDonald.L.Holland,
and E. Scranage, “Automated Object Orient Requirements
Analysis and Design.” Proceedings of the Sixth Washington
Ada Symposium. June 26-29, 1989.pp 265-272.
-
[Atkinson
et al., 1989].M. Atkinson, F. Bancilhon, D. DeWitt, K.
Dittrich, D. Maier , and S. Zdonik, “The Object Orient
Database System Manifesto,”(Invited Paper), Proceedings of
the First International Conference on Deductive and
Object-Oriented Databases, Kyoto, Japan, December 4-6, 1989,
pp.40 – 57.
-
[Bhaskar,
1983].K.S. Bhasker, “How Object-Oriented Is Your System,”
SIGPLAN Notices, Vol.18, No 10, October 1983, pp 8- 11
-
[Bobrow
et al., 1988].D.G. Bobrow, L.G. DeMichiel, R.P. Gabrial,S.E
Keene, G.Kiczales, and D.A. Moon, “Common LISP Object System
Specification X3J13 Document 88 – 002R “SIGPLAN Notices,
Vol.23, special issue, September 1988.
-
[Booch,
1981].G. Booch “Describing Software Design in Ada,
“SIGPLAN Notices, Vol.16, No9, September 1981, p.42 – 47.
-
[Booch,
1982].G. Booch, “Object Oriented Design,” Ada Letters,
Vol.I, No.3,March-April 1982, pp.64-76.
-
[Booch,
1986].G. Booch, “Object Oriented Development,” IEEE
Transactions Software Engineering, Vol. SE-12, No 2, February
1986, pp211 – 221
-
[Booch,
1991].G. Booch, Object-Oriented Design With Applications,
Benjamin/Curmmings, Menlo Park, California, 1991.
-
[BSE,
1992]. Berard Software Engineering, Inc., A Project Management
Hardbook for Object-Oriented Software Development, Volume 1,
Berard Software Engineering, Inc., Gaithersburg, Maryland,
1992.
-
[Cattell,
1991].R.G.G. Cattell. Object Data Management: Object-Oriented
and Extended Relational Database System, Addison-Wesley
Publishing Company, Reading, Massachusetts.1991.
-
[Chidamber
and Kemerer, 1991].S.R, Chidamber and C.F. Kemerer, “Toward
a Metrics Suite for Object-Oriented Design, “OOPSLA ’91
Conference Proceedings, special issue of SIGPLAN Notices, Vol.
26, No 11, November 1991, pp 197-211.
-
[Coad
and Yourdon, 1990].P.Coad and E. Yourdon, OOA –
Object-Oriented Analysis, 2nd Edition, Prentice Hall,
Englewood Cliffs, New Jensey, 1990.
-
[Cox,
1986].B.J Cox, Object Oriented Programming Approach,
Addison-Wesley, Reading, Massachusetts, 1986.
-
[Dittrich
and Dayal, 1986].K. Dittrich and U.Dayal, Editors, Proceedings
of the 1986 International Workshop on Object-Oriented Database
System, IEEE Catalog Number 86TH9161-0, IEEE Computer Society
Press, Washington, D.C., 1986.
-
[Smith
and Robson, 1992].M.D. Smith and D.J. Robson, “A Framework
for Testing Object-Oriented Programs, “Journal of
Object-Oriented Programming, Vol.5,No3, June 1992, pp45-53.
-
[賀元、賴明宗、劉燈]世紀末軟體革命C++,
GUI與物件導向理論。
-
[]物件導向雜誌。