结构型设计模式提出了一种组合对象以创建新功能的方法,接下来我们先介绍两种结构型设计模式
适配器模式
定义
要解决的问题:当已有接口不可改的时候,我们需要引入一层系统来使得互不兼容的接口互相兼容,这一层代码叫做适配器
实例A
此处实例来源于: 精通 Python 设计模式(第二版)中的章节内容
假设现在我们有两个外部接口如下:
class Musician: def __init__(self, name): self.name = name
def __str__(self): return f'the musician {self.name}'
def play(self): return 'plays music'
class Dancer: def __init__(self, name): self.name = name
def __str__(self): return f'the dancer {self.name}'
def dance(self): return 'does a dance performance'音乐家和舞者的表演方法命名都各不相同,此时我们又无法更改类的函数命名(可能是因为这是外部库的原因),我们就需要引入一个适配器来兼容各表演者的方法调用。
class Adapter: def __init__(self, obj, adapted_methods): self.obj = obj self.__dict__.update(adapted_methods) # 等同于 setattr: 其中 key 作为类的属性名
def __str__(self): return str(self.obj)def main():
objects = [Musician('Roy Ayers'), Dancer('Shane Sparks')]
for obj in objects: if hasattr(obj, 'play'): adapted_methods = dict(organize_event=obj.play) elif hasattr(obj, 'dance'): adapted_methods = dict(organize_event=obj.dance)
# referencing the adapted object here obj = Adapter(obj, adapted_methods)
print(f'{obj} {obj.organize_event()}')实例B
假设你在做一个统一通知系统,但历史原因导致现在存在三套通知实现:
class LegacyEmailSender: def send_mail(self, to: str, subject: str, content: str) -> None: print(f"[Email] to={to}, subject={subject}")
class SmsClient: def push(self, phone: str, message: str) -> bool: print(f"[SMS] phone={phone}") return True
class CorpIM: def notify(self, payload: dict) -> None: print(f"[IM] payload={payload}")现在你需要为你的新系统设计自己想要的“统一接口”:
from abc import ABC, abstractmethod
class Notifier(ABC): @abstractmethod def send(self, user: str, title: str, body: str) -> None: ...要让这些旧接口适配你的新系统,你需要为每个旧接口编写适配器:
class EmailAdapter(Notifier): def __init__(self, sender: LegacyEmailSender): self._sender = sender
def send(self, user: str, title: str, body: str) -> None: self._sender.send_mail( to=user, subject=title, content=body, )
class SmsAdapter(Notifier): def __init__(self, client: SmsClient): self._client = client
def send(self, user: str, title: str, body: str) -> None: # SMS 没有 title,只能降级 message = f"{title}: {body}" ok = self._client.push(user, message) if not ok: raise RuntimeError("SMS send failed")
class CorpIMAdapter(Notifier): def __init__(self, im: CorpIM): self._im = im
def send(self, user: str, title: str, body: str) -> None: payload = { "receiver": user, "meta": { "title": title, }, "content": body, } self._im.notify(payload)在业务层使用依赖注入等方式调用适配器实现:
def alert(notifier: Notifier): notifier.send( user="user@example.com", title="Warning", body="Disk almost full", )
alert(EmailAdapter(LegacyEmailSender()))alert(SmsAdapter(SmsClient()))alert(CorpIMAdapter(CorpIM()))外观模式
定义
有时候,系统中的某个功能涉及多个模块或复杂的内部接口,调用方(客户端)只需要关注功能的最终结果,而不需要关心内部是如何实现的。外观模式提供了一个统一的接口,让用户可以简化与系统内部复杂接口的交互。
外观模式(Facade Pattern)属于结构型设计模式,它的核心目的是:
为复杂子系统提供一个简单的接口,隐藏子系统的复杂性。
适用场景
-
系统功能复杂且模块化: 系统内部有多个复杂的子系统,但外部调用者只需要其中的一部分功能,甚至只关心最后的结果。使用外观模式后,调用者可以忽略子系统的复杂性,直接使用外观类提供的简单接口。
-
简化接口调用: 对于一些复杂的子系统或者外部库,可能需要调用多个方法、传递多个参数,并且返回不同的结果。外观模式将这些复杂的调用封装起来,提供一个简洁的接口。
-
减少子系统间的依赖: 外观模式让调用者无需了解子系统的细节,减少了系统模块之间的耦合,减少了维护的复杂性。
外观模式的结构
外观模式主要包含三个角色:
-
Facade(外观类): 提供统一接口,简化客户端对多个子系统的调用,客户端只需要调用外观类的方法,而不需要知道子系统的实现。
-
Subsystem(子系统类): 系统中的一组复杂功能模块,它们提供不同的服务,但通常会暴露一个相对复杂的接口。
-
Client(客户端): 客户端通过外观类访问子系统,简化了操作过程。
实例:家庭影院系统
假设你要设计一个家庭影院系统,涉及多个子系统:音响系统、投影仪、DVD 播放器等。调用者希望一键启动,享受电影的观看体验。
子系统
class DVDPlayer: def on(self): print("DVD Player is on.")
def play(self, movie: str): print(f"Playing movie: {movie}")
def stop(self): print("Stopping DVD playback.")
class Projector: def on(self): print("Projector is on.")
def set_input(self, input_type: str): print(f"Projector input set to: {input_type}")
class SoundSystem: def on(self): print("Sound system is on.")
def set_volume(self, volume: int): print(f"Volume set to: {volume}")外观类:家庭影院
class HomeTheaterFacade: def __init__(self, dvd: DVDPlayer, projector: Projector, sound_system: SoundSystem): self._dvd = dvd self._projector = projector self._sound_system = sound_system
def watch_movie(self, movie: str): print("Get ready to watch a movie...") self._dvd.on() self._projector.on() self._projector.set_input("HDMI") self._sound_system.on() self._sound_system.set_volume(5) self._dvd.play(movie)
def end_movie(self): print("Shutting down the theater...") self._dvd.stop() self._projector.set_input("None") self._sound_system.set_volume(0)4.3 客户端
# 客户端使用外观类来简化操作dvd = DVDPlayer()projector = Projector()sound_system = SoundSystem()
home_theater = HomeTheaterFacade(dvd, projector, sound_system)
# 客户端通过外观类调用简化的接口home_theater.watch_movie("Inception")home_theater.end_movie()输出:
Get ready to watch a movie...DVD Player is on.Projector is on.Projector input set to: HDMISound system is on.Volume set to: 5Playing movie: InceptionShutting down the theater...Stopping DVD playback.Projector input set to: NoneVolume set to: 0优缺点
优点:
-
简化客户端接口: 外观模式为客户端提供了简洁易用的接口,减少了客户端的复杂性。客户端无需了解子系统的细节,只需要调用外观类的方法即可。
-
解耦客户端与子系统: 外观类对客户端隐藏了子系统的复杂性,降低了客户端与子系统的耦合度,使得系统更易于维护和扩展。
-
便于修改子系统: 子系统的改动可以通过修改外观类来隔离,客户端的调用不受影响。
缺点:
-
可能导致不必要的“封装”: 如果系统中的子系统并不复杂,或者子系统之间的接口已经很简单,外观模式可能会引入不必要的层次,增加开发和维护的工作量。
-
功能不够灵活: 外观模式隐藏了子系统的细节,意味着客户端无法访问更细粒度的操作,如果需要更加灵活的控制,外观类可能就无法满足需求。