Vol.01 高频设计模式

高频设计模式详解

写代码久了会发现,很多"看起来很聪明"的设计,其实都有名字。设计模式不是让你炫技的,而是前人踩过坑之后总结出来的"遇到这类问题,这么写不容易出错"。

这是系列的第一篇,我挑了 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 越写越长""这个类改一处要动十处"的味道时,能想起来"哦,这里可能该上某个模式了"。

相关推荐
我登哥MVP8 小时前
走进 Gang of Four 设计模式:代理模式
设计模式·代理模式
我登哥MVP9 小时前
走进 Gang of Four 设计模式:外观模式
java·设计模式·外观模式
ttod_qzstudio1 天前
【软考设计模式】备忘录模式:对象状态的捕获与无损恢复精讲
设计模式·备忘录模式
ttod_qzstudio1 天前
【软考设计模式】责任链模式:请求传递的多级处理与发送接收解耦精讲
设计模式·责任链模式
2501_914245931 天前
C语言设计模式详解:从理论到实践的完整指南
c语言·开发语言·设计模式
CaffeinePro1 天前
四⼤极简架构原则KISS/DRY/YAGNI/LOD
设计模式·架构
霸道流氓气质1 天前
SpringBoot中设计模式组合使用示例-策略、模板、观察者、门面、工厂、单例。
spring boot·后端·设计模式
ttod_qzstudio1 天前
【软考设计模式】观察者模式:一对多依赖的自动通知与订阅解耦精讲
观察者模式·设计模式
ttod_qzstudio1 天前
【软考设计模式】命令模式:请求封装与调用接收解耦的精讲
设计模式·命令模式