第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音乐播放器