03 · 结构型模式(Structural Patterns)

📅 预计 60 分钟 | ⭐ = 重要知识点 | 📌 中英术语见文末


3.0 先给直觉:怎么把类"搭"成更大的结构

创建型模式解决"怎么造对象",结构型模式解决"类和对象怎么组织成更大的结构"。五种经典套路各有各的搭法:

  • 适配器:让接口不兼容的两个类能一起工作(转接头)。
  • 装饰器:给对象动态加能力,层层叠加(加料)。
  • 代理:给对象加一层控制(替身/经纪人)。
  • 组合:把"部分"和"整体"统一对待(树)。
  • 外观:给复杂子系统一个简单门面(服务员)。

3.1 适配器模式(Adapter)⭐

一个"转接头"

适配器像插座转换头:中国的两脚插头插不进英式三孔插座,加一个转接头就解决,两边都不用改。它让接口不兼容的两个类能协同工作,不修改任何一方。

典型场景:老代码只会调用旧接口,新系统要求新接口。用一个适配器把旧接口"翻译"成新接口:

class OldPrinter:                     # 旧接口:print_line
    def print_line(self, text):
        print(text)

class NewAPI:                         # 新系统期望的接口
    def print(self, msg): ...

class Adapter(NewAPI):                # 适配器:满足新接口,转发给旧实现
    def __init__(self, old: OldPrinter):
        self.old = old
    def print(self, msg):
        self.old.print_line(msg)      # 翻译调用

调用方只依赖 NewAPI,旧的 OldPrinter 通过适配器接入,谁都不用改。

⚠️ 常见错误

  1. 用适配器长期掩盖本应重构的接口设计——两套接口并存是持续的成本。
  2. 在适配器里塞入大量业务逻辑,把它变成"改过的旧类",失去了"纯粹翻译"的意义。

3.2 装饰器模式(Decorator)⭐

一层层加料

装饰器像给咖啡加料:拿铁 = 咖啡 + 牛奶,摩卡 = 咖啡 + 牛奶 + 巧克力。不是为每种组合单独写一个类,而是用装饰一层层包装,运行时动态组合能力。

class Coffee:
    def cost(self): return 10

class MilkDecorator:                   # 装饰器持有被装饰对象
    def __init__(self, coffee): self.coffee = coffee
    def cost(self): return self.coffee.cost() + 3

class MochaDecorator:
    def __init__(self, coffee): self.coffee = coffee
    def cost(self): return self.coffee.cost() + 5

c = Coffee()
c = MilkDecorator(c)                   # 先加奶
c = MochaDecorator(c)                  # 再加巧克力
print(c.cost())                        # 18

装饰器与继承的区别:继承在编译期就把能力组合写死;装饰器在运行时按需叠加,能自由排列组合,不会产生"组合爆炸"的类数量。

⚠️ 常见错误

  1. 装饰层把对象的类型层层包住,代码里用 isinstance 判断具体类型会失效——要面向抽象判断。
  2. 装饰层数过多时调试困难,排查问题时很难看出"被包了几层、每层做了什么",要保留清晰的层级日志。

3.3 代理模式(Proxy)⭐

找个"替身"替我干活

代理模式给对象加一个替身,外部访问替身,由替身再去访问真实对象。像明星的经纪人:粉丝找经纪人约档期,经纪人核实之后再安排明星,明星本人不直接暴露。

常见变种:

  • 远程代理:替身在本机,真实对象在远程,屏蔽网络细节。
  • 虚拟代理:真实对象创建昂贵,先放一个轻量替身,真正需要时才创建。
  • 保护代理:先检查访问权限,再决定是否放行。
class Video:
    def play(self): print("播放视频")

class VideoProxy:                      # 保护 + 延迟 代理
    def __init__(self):
        self._real = None

    def play(self, user):
        if user != "VIP":              # 权限检查
            print("无权访问")
            return
        if self._real is None:         # 需要时才创建真实对象
            self._real = Video()
        self._real.play()

代理和装饰器结构相似(都是"包一层"),但目的不同:代理重在控制访问,装饰器重在增强功能

⚠️ 常见错误

  1. 代理对外泄露了真实对象的引用,调用方绕过代理直接操作,控制就失效了。
  2. 没有"延迟创建""权限控制"等真实需求,却硬加虚拟代理,反而平白多了一层调用。

3.4 组合模式(Composite)⭐

部分和整体一样对待

组合模式用树形结构表示"部分-整体"关系,让客户端用一致的方式处理单个对象和对象集合。像文件夹:一个文件夹里既有文件也有子文件夹,删除、重命名、统计大小都能统一操作,调用方不用区分"这是文件还是文件夹"。

class Node:                            # 抽象:叶子与容器统一接口
    def size(self): ...

class File(Node):
    def __init__(self, n): self.n = n
    def size(self): return self.n

class Folder(Node):                    # 容器:里面可以是文件或文件夹
    def __init__(self): self.children = []
    def add(self, node): self.children.append(node)
    def size(self):
        return sum(c.size() for c in self.children)   # 递归汇总

调用方只需面对 Node,统一调用 size()。目录树、菜单树、公司组织架构都是典型例子。

⚠️ 常见错误

  1. 树里出现环路(A 包含 B,B 又包含 A),递归会无限循环——添加子节点时要防止成环。
  2. 把"部分-整体"强套到非树形结构上,生硬地造出一棵树,反而增加复杂度。

3.5 外观模式(Facade)⭐

一个简单的门面

外观模式为复杂子系统提供统一、简单的高层接口。像餐厅服务员:你不用自己跑后厨、仓库、收银台,跟服务员说一句,他全帮你搞定。

一个下单子系统涉及库存、支付、物流三个模块。让客户直接面对三者很累,外观包装一层:

class OrderFacade:
    def __init__(self):
        self.inv = Inventory()
        self.pay = Payment()
        self.ship = Shipping()

    def order(self, item_id, amount):
        self.inv.check(item_id)        # 内部复杂流程对外隐藏
        self.pay.charge(amount)
        self.ship.schedule(item_id)
        return "下单成功"

调用方只依赖 OrderFacade,三个子系统的内部改动不会波及调用方,耦合大大降低。

⚠️ 常见错误

  1. 外观类变成"上帝对象",把子系统的逻辑全收进来,子系统反而名存实亡——外观应该薄,逻辑应该留在子系统里。
  2. 每来一个新需求就改外观,把外观变成"需求垃圾桶",接口稳定性的价值就被消耗掉了。

📌 术语对照(中英)

中文 English
适配器 Adapter
装饰器 Decorator
代理 Proxy
组合 Composite
外观 Facade

🎯 复习清单

  • 五种结构型模式各解决什么"结构问题",分别用一个生活例子说明。
  • 代理 vs 装饰器的核心区别(控制访问 vs 增强功能)。
  • 组合模式的递归结构与统一接口为什么方便调用方。
  • 外观模式如何降低调用方与子系统的耦合。
  • 适配器为什么能"不改动任何一方"实现协同。