第22章:橋接模式
繼承帶來的麻煩
對象的繼承關係是在編譯時就定義好了,所以無法在運行時改變從父類繼承的實現。子類的實現與它的父類有非常緊密的依賴關係,以至於父類實現中的任何變化必然會導致子類發生變化。當需要複用子類時,如果繼承下來的實現不適合解決新的問題,則父類必須重寫或被其他更適合的類替換。這種依賴關係限制了靈活性並最終限制了複用性。
合成、聚合複用原則
合成、聚合複用原則(composition & aggregation reuse principle,CARP),儘量使用合成、聚合,儘量不要使用類繼承。
合成
(組合,composition)和聚合
(aggregation)都是關聯
的特殊種類。
聚合
:表示一種弱的“擁有”關係,體現的是A
對象可以包含B
對象,但B
對象不是A
對象的一部分;
合成
:表示一種強的“擁有”關係,體現了嚴格的部分和整體的關係,部分和整體的生命週期相同。
例:大雁有兩個翅膀,翅膀與大雁是部分和整體的關係,並且它們的生命週期是相同的,於是大雁和翅膀就是合成關係;大雁是羣居動物,所以每隻大雁都是屬於一個雁羣,一個雁羣可以有多隻大雁,所以大雁和雁羣是聚合關係。
合成、聚合複用原則的優點是優先使用對象的合成、聚合將有助於你保持每個類被封裝並被集中在單個任務上。這樣類和類繼承層次會保持較小規模,並且不太可能增長爲不可控制的龐然大物。
緊耦合v.s.鬆耦合
- 緊耦合(繼承)
- 鬆耦合(聚合)
鬆耦合代碼
手機軟件抽象類
遊戲、通訊錄等具體類
手機品牌類
品牌N品牌M具體類
客戶端調用代碼
增加MP3音樂播放類
增加品牌S
合成、聚合複用原則:優先使用對象的合成或聚合,而不是類繼承。繼承是一種強耦合的結構,父類變,子類必須變,與合成、聚合相比,繼承容易造成不必要的麻煩。所以使用繼承時,一定要在is-a
的關係時再考慮使用。
橋接模式
橋接(bridge)模式:將抽象部分與它的實現部分分離,使它們都可以獨立地變化。
抽象
與它的實現
分離不是讓抽象類與其派生類分離,因爲這沒有任何意義。實現
指的是抽象類和它的派生類用來實現自己的對象。由於實現的方式有多種,橋接模式
的核心意圖就是把這些實現獨立出來,讓它們各自地變化。這就使得每種實現的變化不會影響其他實現,從而達到應對變化的目的。
Implementor
類
ConcreteImplementorA
和ConcreteImplementorB
等派生類
Abstraction
類
RefinedAbstraction
類
客戶端實現
橋接模式
所說的“將抽象部分與它的實現部分分離”是實現系統可能有多角度分類,每一種分類都有可能變化,那麼就把這種多角度分離出來讓它們獨立變化,減少它們之間的耦合。
在需要多角度去分類實現對象時,只用繼承會造成大量的類增加,不滿足開放-封閉原則
,此時應該要考慮橋接模式。
橋接模式示例
任務:手機應用(多品牌,多應用)
from abc import ABCMeta, abstractmethod
from typing import Text
class HandsetSoft(metaclass=ABCMeta):
"""
手機軟件抽象類(Implementor)
"""
@abstractmethod
def run(self) -> None:
pass
class HandsetGame(HandsetSoft):
"""
手機遊戲類
"""
def run(self) -> None:
print("運行手機遊戲")
class HandsetAddressList(HandsetSoft):
"""
手機通訊錄類
"""
def run(self) -> None:
print("運行手機通訊錄")
class Handset(metaclass=ABCMeta):
"""
手機品牌類(Abstraction)
"""
def __init__(self) -> None:
self._soft = None
def set_handset_soft(self, soft: HandsetSoft) -> None:
self._soft = soft
@abstractmethod
def run(self) -> None:
pass
class HandsetBrandN(Handset):
"""
手機品牌N
"""
def run(self) -> None:
print("手機N")
self._soft.run()
class HandsetBrandM(Handset):
"""
手機品牌M
"""
def run(self) -> None:
print("手機M")
self._soft.run()
# 客戶端調用代碼
if __name__ == "__main__":
ab = HandsetBrandN()
ab.set_handset_soft(HandsetGame())
ab.run()
ab.set_handset_soft(HandsetAddressList())
ab.run()
ab = HandsetBrandM()
ab.set_handset_soft(HandsetGame())
ab.run()
ab.set_handset_soft(HandsetAddressList())
ab.run()
手機N
運行手機遊戲
手機N
運行手機通訊錄
手機M
運行手機遊戲
手機M
運行手機通訊錄
class HandsetBrandS(Handset):
"""
手機品牌S
"""
def run(self) -> None:
print("手機S")
self._soft.run()
class HandsetMP3(HandsetSoft):
"""
手機MP3類
"""
def run(self) -> None:
print("運行手機MP3音樂播放器")
# 客戶端調用代碼
if __name__ == "__main__":
ab = HandsetBrandS()
ab.set_handset_soft(HandsetMP3())
ab.run()
手機S
運行手機MP3音樂播放器