• 沒有找到結果。

第三章 物件導向設計模式

3.5 制訂物件實作

物件的具體實作細節是由類別所定義,類別定義出物件的內部資 料和佈局、物件所能執行的操作。

UML(United Modeling Language,統一塑模語言)將類別畫成 長方形,最上面是類別名字,其下列出類別所定的資料,最下面列出 類別的操作;三者之間以橫線隔開。傳回值和個體變數(instance variable)的型別可寫可不寫,因為並未假定所用的是靜態型別程式 語言。(如圖 3-1)

圖 3-1 利用 UML 圖示法表示類別

類別可具現出(instantiate)物件,我們可稱此物件是類別所生之 個體(instance)。具現程序會替物件內部資料(由個體變數組成)及

ClassName

InstanceVariable1

InstanceVariable2:Type

……

Operation1( )

Operation2( ):Type

……

搭配的操作配置出記憶體空間,同一類別可具現出許多相似的物件個 體。

以 UML 表示類別可具現出另一個類別的物件,箭頭指向被生成 者。(如圖 3-2)

<< create >>

圖 3-2 以 UML 表示類別可具現另一類別的示意圖

我們可利用類別繼承機制(class inheritance)從既存類別定義出 新類別。當子類別(subclass)繼承親代類別、父類別(parent class)

時,前者會包含後者所有的資料和操作定義,子類別的物件個體會包 含這個子類別和親代類別所有的資料、能夠執行這個子類別和親代類 別所有的操作。UML 以空心三角形箭頭表示繼承關係:(如圖 3-3)

圖 3-3 以 UML 表示類別繼承關係示意圖

抽象類別(abstract class)主要目的是替多個子類別訂出共通介 Instantiator Instantiatee

ParentClass

Operation( )

SubClass

面。抽象類別會將部份或全部的實作細節交付給子類別的操作去實 現,因此抽象類別無法具現出物件個體。抽象類別有宣告、但未實作 的操作,叫做抽象操作(abstract operation);與抽象類別相反的則叫 做具象類別(concrete class)。

子類別可對承襲親代類別的行為加以修改或重新定義,更精確地 講:子類別可覆寫(override)親代類別所賦予的操作。覆寫功能讓 子類別有機會以異於親代類別的方式處理訊息要求。類別繼承能藉由 延伸既存類別以定義出新類別,讓你更容易定義一整族功能相近的物 件。

為便於和具象類別相區隔,UML 的類別圖將抽象類別名字和抽 象操作名字寫成斜體,也可添加一段虛擬碼在操作身上。(如圖 3-4)

implementation

pseudocode

圖 3-4 抽象類別與具象類別的關係

Mixin 類別專門用來替其它類別增添額外的介面或功能。它和抽

AbstractClass

Operation( )

ConcreteSubclass

Operation( )

象類別很像,都不是用來具現物件實體的。Mixin 類別需要動用多重 繼承機制:(如圖 3-5)

圖 3-5 利用多重繼承機制增加新功能

物件的類別(class)和型別(type)之間的分別有些差異。物件 的類別乃定義物件的實作方式:定義物件的內部狀態及操作的實作 碼。相對的,物件的型別只定義它的介面:物件可回應哪些訊息要求。

一個物件可以有許多型別,不同類別的物件也可以具有相同的型別。

當然,類別和型別有很密切的關係。類別因為有定義出可執行的物件 操作,自然也定義出物件的型別;當我們說“某物件是某類別的具現 個體”時,自然也意謂著“此物件具有支援此類別所制定的介面”。

我們也必須留意分辨類別繼承和介面繼承之間的差異。類別繼承 ExistingClass

ExistingOperation( )

Mixin

MixinOperation( )

AugmentedClass

ExistingOperation( ) MixinOperation( )

佈局的機制;相對的,介面繼承則在描述物件之間的可替換性。

這兩種觀念很容易弄混,因為許多程式程言根本就沒有區分。像 在 C++語言裡,“繼承”同時代表“介面繼承”和“實作繼承”兩種 意思。所以 C++程式中,介面繼承的標準寫法是“public 繼承某一具 有(純)虛擬函數的類別”,至於最純粹的介面繼承,就只能勉強以

“public 繼承某一純抽象類別”的方式來近似;最純粹的實作繼承或 介面繼承,就只能勉強以“private 繼承”的方式來近似。

基本上,類別繼承這種機制只是以“再利用親代類別功能”方式 來延伸應用程式的功能,可根據既有類別快速定義出新品種的物件,

承襲既有類別的特性,花最少的力氣得到所想要的結果。但“再利用 實作碼”功能並不是繼承的全部;定義一整族具備同一介面的物件

(通常是以“繼承某一抽象類別”方式實作)也是相當重要的。因為 多型需要它。

若繼承用得夠謹慎(有人認為這才真的算是用得正確),所有自 抽象類別衍生出來的類別都會共用前者的界面,這意謂著:子類別只 會添加或覆寫,並不會隱匿親代類別的操作,如此一來,所有子類別 都能回應此抽象類別的介面訊息要求,都成為抽象類別的子型別。

堅持只以抽象類別介面的角度來操控物件,有兩個優點:

1. 客戶端不必知曉所用物件之確切型別,只須知道這物件的確符合

它所預期的介面即可。

2. 客戶端不必知曉所用物件之確切類別,只須知道這介面是由哪一 個抽象類別所定義的。

如此即可大幅降低子系統之間的實作依存關係,反映出底下這項 可再利用物件導向設計的原則:

針 對 介 面 而 寫 , 不 要 針 對 實 作 。

Program to an interface, not an implementation.

宣告變數時,不應該宣告成是某特定具象類別的物件個體,應該只宣 告成某抽象類別所定義的介面,這才是設計模式的主軸。

當然,最後終究還是得在程式的某處具現出具象類別的物件實體

(亦即:指定特定的實作細節),此時生成模式(Abstract Factory、

Builder、Factory Method、Prototype、Singleton)就能派上用場。這些 模式抽象化物件的生成過程,給我們好幾種將介面與實作連繫起來的 具現方式。生成模式可確保系統乃是針對介面而寫,而不是實作。