高频设计模式详解
写代码久了会发现,很多"看起来很聪明"的设计,其实都有名字。设计模式不是让你炫技的,而是前人踩过坑之后总结出来的"遇到这类问题,这么写不容易出错"。
这是系列的第一篇,我挑了 9 个出场率最高的模式。目标:看完能明白它们各自解决什么问题,以及什么时候该用。例子全部用 Python,尽量贴近日常开发场景。
| 类型 | 模式 | 一句话 |
|---|---|---|
| 创建型 | 工厂方法 | 用工厂创建对象,不写 new |
| 创建型 | 抽象工厂 | 一组相关对象的整体工厂 |
| 结构型 | 适配器 | 转换接口 |
| 结构型 | 装饰者 | 包装新增功能,不改原类 |
| 结构型 | 外观 | 一个简单接口替代复杂子系统 |
| 行为型 | 策略 | 不同策略,不同算法 |
| 行为型 | 观察者 | 一对多通知 |
| 行为型 | 状态 | 对象行为随状态变化 |
| 行为型 | 命令 | 请求封装成对象 |
一、创建型:管"对象怎么生出来"
创建型模式的核心焦虑只有一个:我不想在业务代码里到处写"直接构造对象"。
因为一旦写死 SomeClass(),你的代码就和这个具体类绑死了。哪天要换实现、要根据配置切换,就得满世界改代码。
1. 工厂方法(Factory Method)
一句话:用工厂创建对象,不写 new。
问题场景:你要创建一个对象,但"具体创建哪一个"取决于运行时的条件。
核心思想:把"构造对象"这个动作藏到一个方法(工厂)里,调用方只说"给我一个 XXX",不关心具体是哪个类。
python
class JsonExporter:
def export(self, data): ...
class CsvExporter:
def export(self, data): ...
class XmlExporter:
def export(self, data): ...
def create_exporter(fmt):
"""工厂:根据格式决定造哪个对象"""
mapping = {
"json": JsonExporter,
"csv": CsvExporter,
"xml": XmlExporter,
}
if fmt not in mapping:
raise ValueError(f"unknown format: {fmt}")
return mapping[fmt]()
# 调用方只说要什么格式,不关心具体是哪个类
exporter = create_exporter("csv")
exporter.export(data)
好在哪:以后新增一种导出格式,只改工厂一个地方,调用方一行都不用动。
2. 抽象工厂(Abstract Factory)
一句话:一组相关对象的整体工厂。
问题场景 :工厂方法一次造一个对象。但有时候你需要造一整套配套的对象,而且这套东西必须"成套",不能混搭。
核心思想:一个工厂负责生产一"族"相互配套的产品。
python
# 场景:给应用做浅色/深色两套 UI 主题,
# 一套主题里的按钮、输入框必须风格统一,不能混搭
class LightButton: ...
class LightInput: ...
class DarkButton: ...
class DarkInput: ...
class LightThemeFactory:
def create_button(self): return LightButton()
def create_input(self): return LightInput()
class DarkThemeFactory:
def create_button(self): return DarkButton()
def create_input(self): return DarkInput()
def build_ui(factory):
button = factory.create_button()
input_ = factory.create_input() # 保证和按钮同一套风格
return button, input_
build_ui(DarkThemeFactory())
和工厂方法的区别:
- 工厂方法:造一个东西(一个导出器)。
- 抽象工厂:造一套东西(按钮 + 输入框,且保证它们风格匹配)。
关键价值:保证"配套"。你不会不小心用深色按钮配上浅色输入框。
二、结构型:管"对象怎么拼在一起"
结构型模式关心的是:已有的类怎么组合、包装、对接,才能既满足需求又不把代码搞乱。
3. 适配器(Adapter)
一句话:转换接口。
问题场景:你想用一个现成的类,但它的接口和你系统要求的接口对不上。改它不现实(可能是第三方库、可能是老代码)。
核心思想:中间加一层"转换插头",把 A 的接口翻译成 B 的接口。就像出国用的电源转换头,插座和插头形状不同,中间垫一个转换器。
python
# 系统里统一约定:数据源都要有 read() 方法
class DataSource:
def read(self): ...
# 但第三方库只提供了这个,方法名和用法都对不上
class ThirdPartyReader:
def fetch_content(self):
return "raw data from lib"
# 适配器:把 read() 翻译成 fetch_content()
class ThirdPartyAdapter(DataSource):
def __init__(self, reader):
self._reader = reader
def read(self):
return self._reader.fetch_content()
# 现在第三方库也能当作标准 DataSource 使用
source = ThirdPartyAdapter(ThirdPartyReader())
source.read()
特征 :适配器不新增功能,只做接口翻译。
4. 装饰者(Decorator)
一句话:包装新增功能,不改原类。
问题场景:你想给一个对象加功能(加日志、加缓存、加权限校验),但不想改它的源码,也不想为每种组合都写一个子类(那会爆炸)。
核心思想 :用一个"包装类"把原对象裹起来,包装类和原对象拥有相同的接口,对外表现一致,但在调用前后偷偷加料。而且可以层层包裹。
python
class DataService:
def get(self, key):
return f"value_of_{key}"
# 装饰者:接口和被包装对象一致,内部持有它
class LoggingDecorator:
def __init__(self, service):
self._service = service
def get(self, key):
print(f"[log] getting {key}")
return self._service.get(key) # 前后加日志
class CacheDecorator:
def __init__(self, service):
self._service = service
self._cache = {}
def get(self, key):
if key not in self._cache: # 加一层缓存
self._cache[key] = self._service.get(key)
return self._cache[key]
# 层层套娃:原始服务 → 加缓存 → 加日志
service = LoggingDecorator(CacheDecorator(DataService()))
service.get("user_1")
和适配器的区别 :适配器改的是接口形状 ;装饰者不改接口形状,改的是功能行为(对外看起来还是同一个东西,但能力增强了)。
Python 里的
@decorator语法就是这个思想的语言级支持,本质一样:包一层,加点料。
5. 外观(Facade)
一句话:一个简单接口替代复杂子系统。
问题场景:一个功能背后要协调好多子系统,调用顺序复杂,参数繁多。你不想让每个调用方都去学这一整套流程。
核心思想:提供一个"门面"类,对外一个简单方法,内部帮你把复杂的子系统调用编排好。
python
# 内部子系统一大堆
class VideoDecoder: ...
class AudioMixer: ...
class Renderer: ...
class MediaFacade:
def __init__(self):
self._decoder = VideoDecoder()
self._mixer = AudioMixer()
self._renderer = Renderer()
def play(self, file):
# 内部帮你把一长串流程编排好
stream = self._decoder.decode(file)
audio = self._mixer.mix(stream)
self._renderer.render(stream, audio)
# 调用方只需要一行
MediaFacade().play("movie.mp4")
关键价值 :降低使用门槛,把复杂度封在门后。调用方不需要懂内部有多少子系统。
和适配器的区别:适配器是"把一个不兼容接口翻译成另一个",一对一;外观是"把一堆复杂接口简化成一个好用的",一对多。
三、行为型:管"对象之间怎么协作、怎么变"
行为型模式关心的是职责分配和交互流程:谁做什么、状态怎么变、请求怎么传。
6. 策略(Strategy)
一句话:不同策略,不同算法。
问题场景 :完成同一件事有好几种算法/规则,你不想用一堆 if-else 硬塞在一个方法里。
核心思想:把每种算法各自封装起来,它们拥有相同的调用方式,运行时可以自由替换。
python
# 场景:购物车按不同规则算折扣
def no_discount(amount): return amount
def vip_discount(amount): return amount * 0.8
def clearance_discount(amount): return amount * 0.5
class Cart:
def __init__(self, strategy):
self._strategy = strategy # 策略作为参数传入
def total(self, amount):
return self._strategy(amount)
# 想用哪种规则就传哪个,没有一坨 if-else
Cart(vip_discount).total(100) # 80
Cart(clearance_discount).total(100) # 50
关键价值:算法之间彼此独立,新增一种算法不影响其他的;调用方按需切换。
在 Python 里函数是一等公民,策略常常直接用"传一个函数"来实现,比写一堆策略类更轻。
7. 观察者(Observer)
一句话:一对多通知。
问题场景:一个对象状态变了,多个对象需要立刻知道并做出反应。你不想让这个对象去硬编码"变了之后要通知谁谁谁"。
核心思想:观察者主动来"订阅",被观察者状态变化时"广播"通知所有订阅者。发布方不关心谁在听、有几个人在听。
python
class EventSource:
def __init__(self):
self._subscribers = []
def subscribe(self, callback):
self._subscribers.append(callback)
def notify(self, event):
for cb in self._subscribers: # 挨个通知
cb(event)
# 谁想听,谁来订阅
source = EventSource()
source.subscribe(lambda e: print(f"记日志: {e}"))
source.subscribe(lambda e: print(f"发邮件: {e}"))
source.notify("用户下单")
# 记日志: 用户下单
# 发邮件: 用户下单
关键价值 :发布方和订阅方解耦。发布方只管喊一嗓子,谁爱听谁听,随时能加订阅者。
8. 状态(State)
一句话:对象行为随状态变化。
问题场景 :一个对象在不同状态下,同样的操作会有完全不同的反应。用 if state == ... 写会形成巨大的状态判断泥潭。
核心思想:把每个状态做成一个类,对象的行为委托给"当前状态对象"。状态切换 = 换一个状态对象。
python
# 场景:订单状态机
class PendingState:
def pay(self, order):
print("支付成功")
order.set_state(PaidState()) # 切到已支付
def cancel(self, order):
print("订单已取消")
order.set_state(CancelledState())
class PaidState:
def pay(self, order):
print("已经支付过了") # 同样是 pay,反应完全不同
def cancel(self, order):
print("已支付,走退款流程")
order.set_state(CancelledState())
class CancelledState:
def pay(self, order): print("订单已取消,无法支付")
def cancel(self, order): print("订单已取消")
class Order:
def __init__(self):
self._state = PendingState()
def set_state(self, state): self._state = state
def pay(self): self._state.pay(self) # 行为交给当前状态
def cancel(self): self._state.cancel(self)
order = Order()
order.pay() # 支付成功
order.pay() # 已经支付过了
关键价值:状态相关的逻辑各自归位,行为随状态自然切换,避免一大坨条件判断。
和策略的区别:两者结构很像,但意图不同。策略是"由外部选一个算法,选完通常不变";状态是"对象内部随流转自己切换状态,会来回变"。策略之间平等无关,状态之间往往有明确的转移关系。
9. 命令(Command)
一句话:请求封装成对象。
问题场景:你想把"一个操作"当成数据来传递、排队、记录、撤销。但普通的函数调用是没法存起来、排队的。
核心思想:把"要做的事"打包成一个对象(里面记着做什么、参数是什么)。这样请求就能被存进队列、延迟执行、记日志、甚至回滚。
python
# 场景:支持撤销的文本编辑器
class InsertCommand:
def __init__(self, doc, text):
self._doc = doc
self._text = text
def execute(self):
self._doc.content += self._text
def undo(self): # 命令自己知道怎么回滚
self._doc.content = self._doc.content[:-len(self._text)]
class Document:
def __init__(self):
self.content = ""
self._history = []
def run(self, command):
command.execute()
self._history.append(command) # 把命令存起来
def undo(self):
if self._history:
self._history.pop().undo() # 撤销上一条
doc = Document()
doc.run(InsertCommand(doc, "hello "))
doc.run(InsertCommand(doc, "world"))
print(doc.content) # hello world
doc.undo()
print(doc.content) # hello
关键价值:调用者(发命令的)和执行者(干活的)彻底解耦。命令是对象,就能排队、异步、记录、撤销。
收个尾:怎么记住它们
如果嫌九个太多,先记住每一类的"焦虑点":
- 创建型------不想写死构造。工厂方法造一个,抽象工厂造一套。
- 结构型------已有的东西怎么拼。适配器改接口,装饰者加功能,外观做简化。
- 行为型------对象之间怎么配合。策略换算法,观察者做通知,状态随流转变行为,命令把请求变对象。
几个最容易混的,记住区别就够用:
- 适配器 vs 装饰者:前者改接口 ,后者加功能。
- 外观 vs 适配器:前者简化一堆 接口,后者翻译一个接口。
- 策略 vs 状态:前者外部选、通常不变;后者内部转、会来回切。
设计模式不是背下来就有用,而是当你在代码里闻到"这块 if-else 越写越长""这个类改一处要动十处"的味道时,能想起来"哦,这里可能该上某个模式了"。