“要儘量使用合成/聚合,儘量不要使用繼承。”
陳述:
在一個新的對象裏面使用一些已有的對象,使之成爲新對象的一部分,新對象通過向這些對象的委派達到複用已有功能的目的。
引入:
如我們所知,在面向對象設計裏,不同環境中複用已有設計和實現的基本方法:
- 繼承。
- 合成/聚合。
繼承複用通過擴展一個已有對象的實現來得到新的功能,基類明顯地捕獲共同的屬性和方法,而子類通過增加新的屬性和方法來擴展超類的實現。繼承是類型的複用。
繼承複用的優點:
- 新的實現較爲容易,因爲超類的大部分功能可通過繼承關係自動進入子類;
- 修改或擴展繼承而來的實現較爲容易。
- 繼承複用破壞封裝,因爲繼承將超類的實現細節暴露給子類。“白箱”複用;
- 如果超類的實現發生改變,那麼子類的實現也不得不發生改變。
- 從超類繼承而來的實現是靜態的,不可能再運行時間內發生改變,因此沒有足夠的靈活性。
由於合成/聚合可以將已有的對象納入到新對象中,使之成爲新對象的一部分,因此新的對象可以調用已有對象的功能,
其優點在於:
- 新對象存取成分對象的唯一方法是通過成分對象的接口;
- 成分對象的內部細節對新對象不可見。 “黑箱”複用;
- 該複用支持封裝。
- 該複用所需的依賴較少。
- 每一個新的類可將焦點集中在一個任務上。
- 該複用可在運行時間內動態進行,新對象可動態引用於成分對象類型相同的對象。
- 通過這種複用建造的系統會有較多的對象需要管理。
- 爲了能將多個不同的對象作爲組合塊(composition block)來使用,必須仔細地對接口進行定義。
Coad法則由Peter Coad提出,總結了一些什麼時候使用繼承作爲複用工具的條件。 Coad法則:
只有當以下Coad條件全部被滿足時,才應當使用繼承關係:
- 子類是超類的一個特殊種類,而不是超類的一個角色。區分“Has-A”和“Is-A”。只有“Is-A”關係才符合繼承關係,“Has-A”關係應當用聚合來描述。
- 永遠不會出現需要將子類換成另外一個類的子類的情況。如果不能肯定將來是否會變成另外一個子類的話,就不要使用繼承。
- 子類具有擴展超類的責任,而不是具有置換掉(override)或註銷掉(Nullify)超類的責任。如果一個子類需要大量的置換掉超類的行爲,那麼這個類就不應該是這個超類的子類。
- 只有在分類學角度上有意義時,纔可以使用繼承。不要從工具類繼承。
“Is-A”代表一個類是另外一個類的一種;
“Has-A”代表一個類是另外一個類的一個角色,而不是另外一個類的特殊種類。
錯誤在於把“角色”的等級結構和“人”的等級結構混淆了。“經理”,“僱員”,“學生”是一個人的角色,一個人可以同時擁有上述角色。如果按繼承來設計,那麼如果一個人是僱員,就不可能是經理,也不可能是學生,顯然不合理。
正確的設計是有個抽象類“角色”,“人”可以擁有多個“角色”(聚合),“僱員”,“經理”,“學生”是“角色”的子類。
注意:
里氏替換原則是繼承複用的基礎。
只有兩個類滿足里氏替換原則的時候,纔可能是“Is-A”關係。
如果兩個類是“Has-A”關係而非“Is-A”關係,但是設計成了繼承,那麼肯定違反里氏替換原則。
總結:
- 組合與繼承都是重要的複用方法
- 在OO開發的早期,繼承被過度地使用
- 隨着時間的發展,人們發現優先使用組合可以獲得複用性與簡單性更佳的設計
- 可以通過繼承,擴充(enlarge)可用的組合類集(the set of composable classes)
- 組合與繼承可以一起工作
- 基本法則是:優先使用對象組合,而非(類)繼承
參考資源:
《設計模式:可複用面向對象軟件的基礎》,ERICH
GAMMA RICHARD HELM RALPH JOHNSON JOHN VLISSIDES著作,李英軍 馬曉星 蔡敏 劉建中譯,機械工業出版社,2005.6
《敏捷軟件開發:原則、模式與實踐》,Robert C. Martin著,鄧輝譯,清華大學出版社,2003.9
《設計模式解析》,Alan Shalloway等著(徐言聲譯),人民郵電出版社,2006.10