現實生活中的許多東西都有共用內容。例如,小狗和小貓都是動物。物件也可以有共用內容,您可以在它們的類別之間,利用一般化來釐清這些內容。您可以將共用內容擷取到它們自己的類別中,以便未來能更容易變更和維護系統。
一般化顯示一個類別繼承另一個類別。繼承的類別稱為後代。被繼承的類別稱為上代。繼承關係表示上代的定義對於後代的物件也有效,其中包括如屬性、關係或物件作業之類的任何內容。一般化的繪製,是從後代類別畫向上代類別。
一般化可以在許多階段中進行,讓您建立複雜而多層的繼承階層模型。一般內容放在繼承階層的上層部分,特殊內容的位置較低。換句話說,您可以利用一般化來建立一般概念的特殊化模型。
範例
在回收機系統中,所有類別 (Can、Bottle 和 Crate) 分別描述不同類型的存放項目。除了類型相同之外,它們有兩個共用內容:高度和重量。您可以利用獨立類別 Deposit Item
中的屬性和作業來建立這些內容的模型。Can、Bottle 和 Crate 都繼承這個類別的內容。
Can、Bottle 和 Crate 等類別都有高度和重量這兩個共用內容。每個都是 Deposit Item 這個一般概念的特殊化。
類別可以利用多親繼承來繼承多個其他類別,不過,它通常只會繼承一個類別。
如果您使用多親繼承,有些潛在的問題不可不知:
-
如果類別繼承多個類別,您必須檢查各個上代如何指定關係、作業和屬性的名稱。如果在多個上代中出現相同的名稱,您必須描述這對於特定繼承類別的意義,例如,將名稱限定為指示它的宣告來源。
-
如果使用重複的繼承關係;在這個情況下,後代會多次繼承同一個上代。當發生這個情況,繼承階層會有如下所示的「菱形」。
多重和重複的繼承。Scrolling Window With Dialog Box 類別多次繼承 Window 類別。
在這個環境定義中,可能會出現下列問題:「Scrolling Window With Dialog Box 的實例包括幾份 Windows 的屬性?」
因此,如果您在使用重複的繼承,您必須有它的語意的清晰定義;在大部分情況下,這是由支援多親繼承的程式語言來定義。
一般而言,支配多親繼承的程式語言規則都很複雜,通常不容易正確使用。因此,建議您必要時才使用多親繼承,且要始終小心。
未實例化的類別,如果它的存在只是為了供其他類別繼承,它就是抽象類別。實際產生實例的類別則是具體類別。請注意,抽象類別必須有至少一個後代,它才有用。
範例
倉庫系統中的 Pallet Place 是代表不同集裝架位置類型之共用內容的抽象實體類別。Station、Transporter 和 Storage Unit
等具體類別繼承這個類別,它們都可以作為倉庫中的集裝架位置。所有這些物件都有一個共用內容:它們可以存放一或多個集裝架。
被繼承的類別(這裡是 Pallet Place)是抽象類別,它本身並沒有實例化。
由於類別模板有不同用途,因此,不同類別模板之間的繼承關係並沒有意義。例如,讓界限類別繼承實體類別,會使界限類別成為混合物。因此,您只應在相同模板的類別之間使用一般化。
您可以利用一般化來表示類別之間的兩種關係:
-
子類型化,指定後代是上代的子類型。子類型化表示後代繼承上代的結構和行為,且後代是上代的一個類型(也就是說,後代是一個子類型,可以在任何狀況之下替代它的所有上代)。
-
子類別化,指定後代是上代的子類別(不是子類型)。子類別化表示後代繼承上代的結構和行為,且後代不是上代的一個類型。
您可以將多個類別共用的內容分開,將它們放在獨立的類別中,供其他類別繼承,或建立新的類別,將較一般的類別特殊化,並讓它們繼承一般類別,來建立這類關係。
如果兩個變式相符,在設定類別之間的正確繼承關係時,應該不會有困難。不過,在某些情況下,它們會不符,您必須使繼承的使用維持可理解的情況。最起碼,您應該知道模型中每個繼承關係的目的。
子類型化表示後代是一個子類型,可以在任何狀況之下替代它的所有上代。
子類型化是多型性的特殊案例,它是一個很重要的內容,因為它可讓您設計所有用戶端(使用上代的物件),而不需要考量上代的潛在後代。這會使用戶端物件更具一般性,更可重複使用。當用戶端使用實際的物件時,它會以一種特定方式來運作,且一律會發現物件在執行它的作業。子類型化可確保系統可容忍子類型集的變更。
範例
在倉庫系統中,Transporter Interface 類別定義了與所有類型的運輸設備(如起重機和卡車)通訊的基本功能。這個類別定義了 executeTransport 作業及其他東西。
Truck Interface 和 Crane Interface 類別都繼承 Transporter Interface;也就是說,這兩個類別的物件都會回應 executeTransport 訊息。這些物件隨時都可能代表
Transporter Interface,且會提供它的所有行為。因此,其他物件(用戶端物件)可以傳送訊息給 Transporter Interface 物件,但不需要知道 Truck Interface 或 Crane
Interface object 物件會不會回應訊息。
Transporter Interface 類別甚至可以是抽象的,絕不獨立建立實例。在這個情況下,Transporter Interface 可能只定義 executeTransport 作業的簽章,而由後代類別來實作它。
部分物件導向語言(如 C++)會使用類別階層作為類型階層,迫使設計者利用繼承來建立設計模型中的子類型。其他物件導向語言(如 Smalltalk-80)則不會在編譯時進行類型檢查。如果物件無法回應收到的訊息,它們會產生錯誤訊息。
甚至在不含類型檢查的語言中,利用一般化來指示子類型關係也很好。在某些情況下,不論語言是否接受,您都應該利用一般化來使物件模型和程式碼更容易瞭解和維護。如此使用繼承關係是不是好的樣式,會隨著程式語言的慣例而大不相同。
子類別化構成了一般化重複使用的方面。當子類別化時,您要考慮繼承其他類別所定義的內容,可以重複使用實作的哪些部分。當您實作特定類別時,子類別化可以節省工作,讓您重複使用程式碼。
範例
在 Smalltalk-80 類別庫中,Dictionary 類別繼承 Set 的內容。
這個一般化的原因在於 Dictionary 後來可以重複使用 Set 實作中的某些一般方法和儲存策略。雖然您可以將 Dictionary 視為一個 Set(包含鍵值組),但 Dictionary 並不是 Set 的子類型,因為您不能在
Dictionary 中加入任何種類的物件(只有鍵值組)。使用 Dictionary 的物件並不知道它實際上是一個 Set。
子類別化通常會造成很難理解、很難維護的不合邏輯的繼承階層。因此,除非在使用程式語言方面有其他建議,否則,不建議您只為了重複使用而使用繼承。這類重複使用的維護,通常會很難處理。Set 類別中的任何變更都可能隱含了所有繼承 Set
類別之類別的大量變更。請注意這一點,只繼承穩定的類別。繼承會實際凍結 Set 的實作,因為它的變更成本太高。
當在設計中使用一般化關係時,應該相當依賴程式語言的繼承語意和所提出的繼承用法。物件導向語言支援類別之間的繼承,但非物件導向語言不支援。您應該在設計模型中處理語言特性。如果您使用的語言不支援繼承或多親繼承,您必須在實作中模擬繼承關係。在這個情況下,您最好在設計模型中建立模擬的模型,而不用一般化來說明繼承結構。利用一般化來建立繼承結構的模型,之後,在實作中模擬繼承,可能會毀壞設計。
如果您使用的語言不支援繼承或多親繼承,您必須在實作中模擬繼承關係。在這個情況下,您最好在設計模型中建立模擬的模型,而不用一般化來說明繼承結構。利用一般化來建立繼承結構的模型,之後,只在實作中模擬繼承,可能會毀壞設計。
在模擬期間,您可能必須變更介面及其他物件內容。建議您利用下列其中一種方式來模擬繼承關係:
-
讓後代將訊息轉遞給上代。
-
將上代的程式碼複製到每個後代中。在這個情況下,不會建立任何上代類別。
範例
在這個範例中,後代會利用鏈結(關聯實例)將訊息轉遞給上代。
Can、Bottle 和 Crate 物件的共用行為指派給一個特殊類別。在必要時,共用這個行為的物件會傳送一則訊息給 Deposit Item 物件來執行行為。
|