SD-03 结构型模式
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 通过适配器接入,谁都不用改。
⚠️ 常见错误
- 用适配器长期掩盖本应重构的接口设计——两套接口并存是持续的成本。
- 在适配器里塞入大量业务逻辑,把它变成"改过的旧类",失去了"纯粹翻译"的意义。
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
装饰器与继承的区别:继承在编译期就把能力组合写死;装饰器在运行时按需叠加,能自由排列组合,不会产生"组合爆炸"的类数量。
⚠️ 常见错误
- 装饰层把对象的类型层层包住,代码里用
isinstance判断具体类型会失效——要面向抽象判断。 - 装饰层数过多时调试困难,排查问题时很难看出"被包了几层、每层做了什么",要保留清晰的层级日志。
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()
代理和装饰器结构相似(都是"包一层"),但目的不同:代理重在控制访问,装饰器重在增强功能。
⚠️ 常见错误
- 代理对外泄露了真实对象的引用,调用方绕过代理直接操作,控制就失效了。
- 没有"延迟创建""权限控制"等真实需求,却硬加虚拟代理,反而平白多了一层调用。
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()。目录树、菜单树、公司组织架构都是典型例子。
⚠️ 常见错误
- 树里出现环路(A 包含 B,B 又包含 A),递归会无限循环——添加子节点时要防止成环。
- 把"部分-整体"强套到非树形结构上,生硬地造出一棵树,反而增加复杂度。
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,三个子系统的内部改动不会波及调用方,耦合大大降低。
⚠️ 常见错误
- 外观类变成"上帝对象",把子系统的逻辑全收进来,子系统反而名存实亡——外观应该薄,逻辑应该留在子系统里。
- 每来一个新需求就改外观,把外观变成"需求垃圾桶",接口稳定性的价值就被消耗掉了。
📌 术语对照(中英)
| 中文 | English |
|---|---|
| 适配器 | Adapter |
| 装饰器 | Decorator |
| 代理 | Proxy |
| 组合 | Composite |
| 外观 | Facade |
🎯 复习清单
- 五种结构型模式各解决什么"结构问题",分别用一个生活例子说明。
- 代理 vs 装饰器的核心区别(控制访问 vs 增强功能)。
- 组合模式的递归结构与统一接口为什么方便调用方。
- 外观模式如何降低调用方与子系统的耦合。
- 适配器为什么能"不改动任何一方"实现协同。