03-结构型模式¶
学习时间: 约4-5小时 难度级别: ⭐⭐⭐ 中级 前置知识: SOLID设计原则、创建型模式 学习目标: 掌握7种结构型模式的意图、结构和实现,理解如何通过组合对象获得新功能
🎯 学习目标¶
- 理解结构型模式解决的核心问题:如何将类或对象组合成更大的结构
- 掌握适配器、装饰器、代理三大高频结构型模式
- 理解Python装饰器与装饰器模式的关系
- 掌握代理模式的多种应用(缓存代理、远程代理、保护代理)
- 能在实际项目中识别并应用结构型模式
目录¶
- 1. 结构型模式概述
- 2. 适配器模式(Adapter)
- 3. 桥接模式(Bridge)
- 4. 组合模式(Composite)
- 5. 装饰器模式(Decorator)
- 6. 外观模式(Facade)
- 7. 享元模式(Flyweight)
- 8. 代理模式(Proxy)
- 9. 结构型模式对比
- 10. 练习与自我检查
1. 结构型模式概述¶
结构型模式关注如何将类和对象组合为更大的结构,在保持结构灵活高效的同时实现新功能。
| 模式 | 一句话 | 核心思想 |
|---|---|---|
| 适配器 | 接口转换器 | 不兼容接口之间的桥梁 |
| 桥接 | 分离抽象与实现 | 多维度变化独立扩展 |
| 组合 | 树形结构 | 部分-整体统一处理 |
| 装饰器 | 动态包装 | 不修改对象动态添加功能 |
| 外观 | 简化接口 | 复杂子系统的统一简单入口 |
| 享元 | 共享对象 | 大量相似对象共享内部状态 |
| 代理 | 控制访问 | 通过代理间接访问真实对象 |
2. 适配器模式(Adapter)¶
意图¶
将一个类的接口转换为客户端期望的另一个接口,使原本不兼容的类可以一起工作。
适用场景¶
- 使用已有类,但接口与需求不匹配
- 集成第三方库,统一不同SDK的接口
- 旧系统迁移,新旧接口不兼容
结构¶
Python实现¶
from abc import ABC, abstractmethod
# ====== 目标接口 ======
class PaymentGateway(ABC):
@abstractmethod
def pay(self, amount: float) -> bool:
pass
# ====== 已有的第三方SDK(接口不同) ======
class AlipaySDK:
def create_payment(self, total_fee, subject=""):
print(f"Alipay: ¥{total_fee}")
return {"status": "success"}
class WeChatPaySDK:
def unified_order(self, total_amount_in_cents):
print(f"WeChatPay: ¥{total_amount_in_cents / 100}")
return {"return_code": "SUCCESS"}
class PayPalSDK:
def make_payment(self, usd_amount):
print(f"PayPal: ${usd_amount}")
return True
# ====== 适配器 ======
class AlipayAdapter(PaymentGateway):
def __init__(self, sdk: AlipaySDK):
self._sdk = sdk
def pay(self, amount):
result = self._sdk.create_payment(total_fee=amount)
return result.get("status") == "success"
class WeChatPayAdapter(PaymentGateway):
def __init__(self, sdk: WeChatPaySDK):
self._sdk = sdk
def pay(self, amount):
result = self._sdk.unified_order(total_amount_in_cents=int(amount * 100))
return result.get("return_code") == "SUCCESS"
class PayPalAdapter(PaymentGateway):
def __init__(self, sdk: PayPalSDK):
self._sdk = sdk
def pay(self, amount):
return self._sdk.make_payment(usd_amount=amount)
# ====== 使用 ======
def checkout(gateway: PaymentGateway, amount: float):
"""客户端代码只依赖统一接口"""
if gateway.pay(amount):
print("Payment successful!")
else:
print("Payment failed!")
checkout(AlipayAdapter(AlipaySDK()), 99.9)
checkout(WeChatPayAdapter(WeChatPaySDK()), 99.9)
Java实现¶
public interface PaymentGateway {
boolean pay(double amount);
}
public class AlipayAdapter implements PaymentGateway {
private AlipaySDK sdk;
public AlipayAdapter(AlipaySDK sdk) { this.sdk = sdk; }
public boolean pay(double amount) {
Map<String, String> result = sdk.createPayment(amount, "");
return "success".equals(result.get("status"));
}
}
优缺点¶
| 优点 | 缺点 |
|---|---|
| 解耦客户端与第三方实现 | 增加了间接层的复杂度 |
| 遵循开闭原则 | 适配器过多时代码变复杂 |
| 单一职责(接口转换独立) |
实际应用¶
- Python的
io.TextIOWrapper适配字节流为文本流 - Java JDBC驱动适配不同数据库
- 旧API到新API的迁移层
3. 桥接模式(Bridge)¶
意图¶
将抽象与实现分离,使两者可以独立变化。处理两个维度的变化。
适用场景¶
- 一个类有两个或多个独立变化的维度
- 避免多维继承导致的类爆炸
Python实现¶
from abc import ABC, abstractmethod
# ====== 实现维度:渲染方式 ======
class Renderer(ABC):
@abstractmethod
def render_circle(self, x, y, radius): pass
@abstractmethod
def render_rectangle(self, x, y, w, h): pass
class VectorRenderer(Renderer):
def render_circle(self, x, y, radius):
print(f"Drawing circle at ({x},{y}) r={radius} as vector")
def render_rectangle(self, x, y, w, h):
print(f"Drawing rect at ({x},{y}) {w}x{h} as vector")
class RasterRenderer(Renderer):
def render_circle(self, x, y, radius):
print(f"Drawing circle at ({x},{y}) r={radius} as pixels")
def render_rectangle(self, x, y, w, h):
print(f"Drawing rect at ({x},{y}) {w}x{h} as pixels")
# ====== 抽象维度:图形类型 ======
class Shape(ABC):
def __init__(self, renderer: Renderer):
self.renderer = renderer # 桥接:持有渲染器引用
@abstractmethod
def draw(self): pass
class Circle(Shape):
def __init__(self, renderer, x, y, radius):
super().__init__(renderer)
self.x, self.y, self.radius = x, y, radius
def draw(self):
self.renderer.render_circle(self.x, self.y, self.radius)
class Rectangle(Shape):
def __init__(self, renderer, x, y, w, h):
super().__init__(renderer)
self.x, self.y, self.w, self.h = x, y, w, h
def draw(self):
self.renderer.render_rectangle(self.x, self.y, self.w, self.h)
# 两个维度可以自由组合
circle_vector = Circle(VectorRenderer(), 10, 10, 5)
circle_raster = Circle(RasterRenderer(), 10, 10, 5)
circle_vector.draw()
circle_raster.draw()
优缺点¶
| 优点 | 缺点 |
|---|---|
| 分离抽象与实现,独立扩展 | 增加了代码复杂度 |
| 避免继承层次爆炸 | 需要正确识别两个维度 |
| 符合开闭原则 |
4. 组合模式(Composite)¶
意图¶
将对象组合为树形结构,使客户端可以统一处理叶子节点和组合节点。
Python实现¶
from abc import ABC, abstractmethod
from typing import List
class FileSystemItem(ABC):
def __init__(self, name):
self.name = name
@abstractmethod
def get_size(self) -> int:
pass
@abstractmethod
def display(self, indent=0):
pass
class File(FileSystemItem):
"""叶子节点"""
def __init__(self, name, size):
super().__init__(name)
self.size = size
def get_size(self):
return self.size
def display(self, indent=0):
print(" " * indent + f"📄 {self.name} ({self.size}KB)")
class Directory(FileSystemItem):
"""组合节点"""
def __init__(self, name):
super().__init__(name)
self.children: List[FileSystemItem] = []
def add(self, item: FileSystemItem):
self.children.append(item)
return self
def remove(self, item: FileSystemItem):
self.children.remove(item)
def get_size(self):
return sum(child.get_size() for child in self.children)
def display(self, indent=0):
print(" " * indent + f"📁 {self.name}/ ({self.get_size()}KB)")
for child in self.children:
child.display(indent + 1)
# 构建树形结构
root = Directory("project")
src = Directory("src")
src.add(File("main.py", 15))
src.add(File("utils.py", 8))
root.add(src)
root.add(File("README.md", 3))
root.add(File("requirements.txt", 1))
root.display()
# 📁 project/ (27KB)
# 📁 src/ (23KB)
# 📄 main.py (15KB)
# 📄 utils.py (8KB)
# 📄 README.md (3KB)
# 📄 requirements.txt (1KB)
实际应用¶
- 文件系统(文件和目录)
- GUI组件树(容器和控件)
- 组织架构(部门和员工)
- HTML DOM树
5. 装饰器模式(Decorator)¶
意图¶
动态地为对象添加新功能,比继承更灵活。
适用场景¶
- 需要在运行时动态扩展对象功能
- 不想通过继承产生大量子类
- 需要灵活组合多种增强功能
Python实现¶
from abc import ABC, abstractmethod
# ====== 组件接口 ======
class DataSource(ABC):
@abstractmethod
def write(self, data: str): pass
@abstractmethod
def read(self) -> str: pass
# ====== 具体组件 ======
class FileDataSource(DataSource):
def __init__(self, filename):
self.filename = filename
self._data = ""
def write(self, data):
self._data = data
print(f"Writing to {self.filename}: {data[:50]}...") # 切片操作:[start:end:step]提取子序列
def read(self):
return self._data
# ====== 装饰器基类 ======
class DataSourceDecorator(DataSource):
def __init__(self, source: DataSource):
self._source = source
def write(self, data):
self._source.write(data)
def read(self):
return self._source.read()
# ====== 具体装饰器 ======
class EncryptionDecorator(DataSourceDecorator):
"""加密装饰器"""
def write(self, data):
encrypted = self._encrypt(data)
super().write(encrypted) # super()调用父类方法
def read(self):
data = super().read()
return self._decrypt(data)
def _encrypt(self, data):
return ''.join(chr(ord(c) + 1) for c in data)
def _decrypt(self, data):
return ''.join(chr(ord(c) - 1) for c in data)
class CompressionDecorator(DataSourceDecorator):
"""压缩装饰器"""
def write(self, data):
compressed = self._compress(data)
super().write(compressed)
def read(self):
data = super().read()
return self._decompress(data)
def _compress(self, data):
return f"[compressed:{len(data)}]{data[:10]}..."
def _decompress(self, data):
return data # 简化实现
class LoggingDecorator(DataSourceDecorator):
"""日志装饰器"""
def write(self, data):
print(f"[LOG] Writing {len(data)} bytes")
super().write(data)
def read(self):
print(f"[LOG] Reading data")
return super().read()
# ====== 灵活组合 ======
# 基础写入
source = FileDataSource("data.txt")
# 加密 + 压缩 + 日志(像套娃一样层层包装)
source = LoggingDecorator(
CompressionDecorator(
EncryptionDecorator(source)
)
)
source.write("Hello, Design Patterns!")
data = source.read()
与Python装饰器的关系¶
Python的 @decorator 语法糖与设计模式中的装饰器模式思想相似但不完全等同:
- 设计模式的装饰器:面向对象,包装一个对象,增强其方法
- Python的
@decorator:包装一个函数,增强其行为
import functools
import time
# Python函数装饰器 — 装饰器模式的函数式实现
def timer(func):
@functools.wraps(func)
def wrapper(*args, **kwargs): # *args接收任意位置参数;**kwargs接收任意关键字参数
start = time.time()
result = func(*args, **kwargs)
elapsed = time.time() - start
print(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
def retry(max_attempts=3):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(max_attempts):
try: # try/except捕获异常
return func(*args, **kwargs)
except Exception as e:
if attempt == max_attempts - 1:
raise
print(f"Retry {attempt + 1}/{max_attempts}: {e}")
return wrapper
return decorator
@timer
@retry(max_attempts=3)
def fetch_data(url):
# 模拟网络请求
return f"Data from {url}"
Java实现¶
public interface DataSource { // interface定义类型契约
void write(String data);
String read();
}
public class EncryptionDecorator implements DataSource {
private DataSource source;
public EncryptionDecorator(DataSource source) {
this.source = source;
}
public void write(String data) {
source.write(encrypt(data));
}
public String read() {
return decrypt(source.read());
}
}
// 组合使用
DataSource source = new LoggingDecorator(
new EncryptionDecorator(
new FileDataSource("data.txt")
)
);
优缺点¶
| 优点 | 缺点 |
|---|---|
| 比继承更灵活 | 多层装饰器调试困难 |
| 运行时动态添加功能 | 装饰器顺序可能影响结果 |
| 符合开闭原则 | 包装层过多影响性能(通常可忽略) |
实际应用¶
- Java的
BufferedReader(new FileReader(file))— I/O流装饰器 - Python的
@property,@staticmethod,@classmethod - Django/Flask的视图装饰器(
@login_required)
6. 外观模式(Facade)¶
意图¶
为复杂子系统提供一个简单的统一接口。
Python实现¶
class VideoDecoder:
def decode(self, filename):
print(f"Decoding video: {filename}")
return "video_data"
class AudioDecoder:
def decode(self, filename):
print(f"Decoding audio: {filename}")
return "audio_data"
class SubtitleParser:
def parse(self, filename):
print(f"Parsing subtitles: {filename}")
return "subtitle_data"
class VideoRenderer:
def render(self, video, audio, subtitles):
print(f"Rendering video with audio and subtitles")
# 外观类:简化复杂操作
class VideoPlayerFacade:
def __init__(self):
self._video_decoder = VideoDecoder()
self._audio_decoder = AudioDecoder()
self._subtitle_parser = SubtitleParser()
self._renderer = VideoRenderer()
def play(self, filename):
"""一个方法搞定复杂的播放流程"""
video = self._video_decoder.decode(filename)
audio = self._audio_decoder.decode(filename)
subtitles = self._subtitle_parser.parse(filename)
self._renderer.render(video, audio, subtitles)
# 使用:简单调用
player = VideoPlayerFacade()
player.play("movie.mp4")
实际应用¶
- Python的
requests库是HTTP协议的外观 - jQuery简化了DOM操作API
- SLF4J是多种日志框架的统一外观
7. 享元模式(Flyweight)¶
意图¶
通过共享细粒度对象来减少内存用量,适用于大量相似对象的场景。
Python实现¶
class TreeType:
"""享元对象:共享的内部状态"""
def __init__(self, name, color, texture):
self.name = name
self.color = color
self.texture = texture
def draw(self, x, y):
print(f"Drawing {self.name} tree at ({x},{y})")
class TreeFactory:
"""享元工厂:管理共享对象"""
_types = {}
@classmethod # @classmethod类方法,第一个参数为类本身
def get_tree_type(cls, name, color, texture) -> TreeType:
key = f"{name}_{color}_{texture}"
if key not in cls._types:
cls._types[key] = TreeType(name, color, texture)
print(f" [New TreeType created: {name}]")
return cls._types[key]
class Tree:
"""包含外部状态(位置)的具体树"""
def __init__(self, x, y, tree_type: TreeType):
self.x = x
self.y = y
self.type = tree_type # 引用共享的享元
def draw(self):
self.type.draw(self.x, self.y)
# 创建森林:100万棵树,但只有少数几种类型
import random
forest = []
for _ in range(10):
tree_type = TreeFactory.get_tree_type(
random.choice(["Oak", "Pine", "Birch"]),
random.choice(["Green", "DarkGreen"]),
"standard"
)
forest.append(Tree(random.randint(0, 1000), random.randint(0, 1000), tree_type))
print(f"\nTrees: {len(forest)}, Shared types: {len(TreeFactory._types)}")
实际应用¶
- Python的小整数缓存(-5到256)
- Java的String常量池
- 字体渲染中的字形缓存
- 游戏中大量相同贴图的渲染
8. 代理模式(Proxy)¶
意图¶
为对象提供一个替代品或占位符,以控制对该对象的访问。
代理类型¶
| 类型 | 说明 | 典型场景 |
|---|---|---|
| 虚拟代理 | 延迟创建昂贵对象 | 大图片懒加载 |
| 保护代理 | 控制访问权限 | 权限检查 |
| 远程代理 | 代表远程对象 | RPC / REST API客户端 |
| 缓存代理 | 缓存请求结果 | API缓存、数据库查询缓存 |
| 日志代理 | 记录访问日志 | API调用记录 |
Python实现¶
from abc import ABC, abstractmethod # ABC抽象基类;abstractmethod强制子类实现
import time
from functools import lru_cache
# ====== 接口 ======
class DataService(ABC):
@abstractmethod
def get_data(self, key: str) -> str:
pass
# ====== 真实服务 ======
class RemoteDataService(DataService):
def get_data(self, key):
print(f" [Remote] Fetching data for '{key}'... (slow)")
time.sleep(0.5) # 模拟网络延迟
return f"Data-{key}-{time.time():.0f}"
# ====== 缓存代理 ======
class CachingProxy(DataService):
def __init__(self, service: DataService, ttl=60):
self._service = service
self._cache = {}
self._ttl = ttl
def get_data(self, key):
now = time.time()
if key in self._cache:
data, timestamp = self._cache[key]
if now - timestamp < self._ttl:
print(f" [Cache HIT] '{key}'")
return data
print(f" [Cache MISS] '{key}'")
data = self._service.get_data(key)
self._cache[key] = (data, now)
return data
# ====== 保护代理 ======
class AuthProxy(DataService):
def __init__(self, service: DataService, allowed_roles: list):
self._service = service
self._allowed_roles = allowed_roles
self._current_role = "guest"
def login(self, role):
self._current_role = role
def get_data(self, key):
if self._current_role not in self._allowed_roles:
raise PermissionError(f"Role '{self._current_role}' not allowed!")
return self._service.get_data(key)
# ====== 日志代理 ======
class LoggingProxy(DataService):
def __init__(self, service: DataService):
self._service = service
def get_data(self, key):
print(f"[LOG] get_data('{key}') called at {time.strftime('%H:%M:%S')}")
start = time.time()
result = self._service.get_data(key)
elapsed = time.time() - start
print(f"[LOG] get_data('{key}') returned in {elapsed:.3f}s")
return result
# ====== 组合使用 ======
service = LoggingProxy(
CachingProxy(
RemoteDataService(),
ttl=10
)
)
print(service.get_data("user:1")) # Cache MISS → 远程调用
print(service.get_data("user:1")) # Cache HIT → 直接返回
Java实现(动态代理)¶
import java.lang.reflect.*;
// JDK动态代理
public class LoggingHandler implements InvocationHandler { // extends继承;implements实现接口
private Object target;
public LoggingHandler(Object target) {
this.target = target;
}
@Override // @Override重写父类方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("[LOG] Calling: " + method.getName());
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
long elapsed = System.currentTimeMillis() - start;
System.out.println("[LOG] " + method.getName() + " took " + elapsed + "ms");
return result;
}
}
// 创建动态代理
DataService realService = new RemoteDataService();
DataService proxy = (DataService) Proxy.newProxyInstance(
DataService.class.getClassLoader(),
new Class[]{DataService.class},
new LoggingHandler(realService)
);
proxy.getData("user:1");
优缺点¶
| 优点 | 缺点 |
|---|---|
| 不修改真实对象,扩展功能 | 增加间接层,可能影响性能 |
| 可以控制对象生命周期 | 代码复杂度增加 |
| 对客户端透明 | 代理过多时难以维护 |
实际应用¶
- Python的
@property实质上是属性代理 - ORM中的懒加载(如SQLAlchemy的延迟加载关系)
- Web框架的中间件(缓存、认证、限流)
- Java Spring AOP(基于动态代理)
9. 结构型模式对比¶
| 模式 | 核心 | 关系 |
|---|---|---|
| 适配器 | 接口转换 | 通常包装一个已存在的对象 |
| 桥接 | 分离两个维度 | 组合关系,构建时确定 |
| 组合 | 树形结构 | 部分-整体的递归结构 |
| 装饰器 | 动态增强 | 透明包装,可嵌套 |
| 外观 | 简化接口 | 不增加功能,只简化使用 |
| 享元 | 共享实例 | 分离内外状态 |
| 代理 | 控制访问 | 接口不变,增加控制 |
容易混淆的模式对比:
- 装饰器 vs 代理:装饰器增强功能(客户端知道),代理控制访问(客户端不知道内部有代理)
- 适配器 vs 外观:适配器转换接口(一对一),外观简化接口(一对多子系统)
- 装饰器 vs 适配器:装饰器不改变接口只增强功能,适配器改变接口
10. 练习与自我检查¶
✏️ 练习题¶
-
适配器实践:写一个适配器将
xml.etree.ElementTree和json模块统一到相同的数据读取接口DataReader.read(filepath) -> dict。 -
装饰器组合:实现一个HTTP请求处理链:
LoggingDecorator → AuthDecorator → RateLimitDecorator → RealHandler。验证装饰器顺序不同时行为的变化。 -
代理实现:为一个图片加载器实现虚拟代理(懒加载)— 只有在第一次调用
display()时才从磁盘加载图片。 -
组合模式:用组合模式实现一个菜单系统,支持菜单项(叶子)和子菜单(组合),实现
display()和get_total_price()。 -
享元实践:模拟一个文字编辑器,大量字符共享字体/大小/颜色信息,每个字符只存储自己的位置。统计共享前后内存使用差异。
面试要点¶
Q1: 装饰器模式和代理模式的区别? A: 装饰器重在"增强功能"(如添加加密、压缩),客户端通常知道包了装饰器。代理重在"控制访问"(如缓存、权限验证),对客户端透明,接口不变。
Q2: Python的装饰器语法是装饰器模式吗? A: 思想类似但不完全等同。设计模式的装饰器是面向对象的(包装对象),Python的
@decorator是函数式的(包装函数),但核心思想相同——不修改原始代码动态添加行为。Q3: 适配器模式有哪些实际应用? A: 统一不同支付SDK接口、使用旧接口适配新系统、将XML数据源适配为JSON接口等。核心是在不修改已有类的前提下让不兼容的接口协同工作。
自我检查清单¶
- 能区分适配器、装饰器和代理三者的意图差异
- 能实现Python函数装饰器和类装饰器模式
- 理解代理模式的多种变体(缓存/保护/虚拟/远程)
- 能用组合模式处理树形结构
- 理解外观模式的简化作用
- 知道享元模式适用的场景(大量相似对象)
下一章: 04-行为型模式 — 学习对象之间如何高效协作