物件導向實例 – 是定理還是方法

今年一月份的時候,筆者受命去為一家營造公司上課,課程的主題,除了講解一般所謂的資訊系統為何之外,還包括了何謂物件導向的程式設計,時間兩個小時。本來以為這是一項輕鬆簡單的任務,但是沒想到老闆交代客戶的要求是:上課內容不但要豐富精彩,而且重點必須是放在『物件導向的程式設計』這上面,這下子筆者的臉上不但冒出許多小丸子線,而且任務一下子就變成了『不可能的任務』,因為短短的兩個小時,除了扣掉自我介紹、嚥口水、咳嗽、打噴嚏,以及段落之間的休止符之外,大概剩下不了多少時間。所以要在這麼短的時間內介紹這樣的主題,筆者只好盡量撿一些重點來說明,所以可想而知的結果是,筆者在台上比手劃腳,講的口沫橫飛,台下則是問號掉了一地。不過在準備教材的過程中卻讓筆者想起了兩段往事。

第一件事情是在 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與物件導向理論。

[]物件導向雜誌。

谢谢分享哦 Y^^