Mobile wallpaper 1
1615 字
8 分钟
笔记·设计模式:适配器模式和外观模式

结构型设计模式提出了一种组合对象以创建新功能的方法,接下来我们先介绍两种结构型设计模式

适配器模式#

定义#

要解决的问题:当已有接口不可改的时候,我们需要引入一层系统来使得互不兼容的接口互相兼容,这一层代码叫做适配器

实例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)属于结构型设计模式,它的核心目的是:

为复杂子系统提供一个简单的接口,隐藏子系统的复杂性

适用场景#

  1. 系统功能复杂且模块化: 系统内部有多个复杂的子系统,但外部调用者只需要其中的一部分功能,甚至只关心最后的结果。使用外观模式后,调用者可以忽略子系统的复杂性,直接使用外观类提供的简单接口。

  2. 简化接口调用: 对于一些复杂的子系统或者外部库,可能需要调用多个方法、传递多个参数,并且返回不同的结果。外观模式将这些复杂的调用封装起来,提供一个简洁的接口。

  3. 减少子系统间的依赖: 外观模式让调用者无需了解子系统的细节,减少了系统模块之间的耦合,减少了维护的复杂性。

外观模式的结构#

外观模式主要包含三个角色:

  • 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: HDMI
Sound system is on.
Volume set to: 5
Playing movie: Inception
Shutting down the theater...
Stopping DVD playback.
Projector input set to: None
Volume set to: 0

优缺点#

优点:

  1. 简化客户端接口: 外观模式为客户端提供了简洁易用的接口,减少了客户端的复杂性。客户端无需了解子系统的细节,只需要调用外观类的方法即可。

  2. 解耦客户端与子系统: 外观类对客户端隐藏了子系统的复杂性,降低了客户端与子系统的耦合度,使得系统更易于维护和扩展。

  3. 便于修改子系统: 子系统的改动可以通过修改外观类来隔离,客户端的调用不受影响。

缺点:

  1. 可能导致不必要的“封装”: 如果系统中的子系统并不复杂,或者子系统之间的接口已经很简单,外观模式可能会引入不必要的层次,增加开发和维护的工作量。

  2. 功能不够灵活: 外观模式隐藏了子系统的细节,意味着客户端无法访问更细粒度的操作,如果需要更加灵活的控制,外观类可能就无法满足需求。

笔记·设计模式:适配器模式和外观模式
https://blog.snowy.moe/posts/53672/
作者
Muika
发布于
2026-02-10
许可协议
CC BY-NC-SA 4.0