结构型模式关注如何将类和对象组合成更大的结构,同时保持结构的灵活和高效率。
1. 适配器模式(Adapter)
意图
将一个类的接口转换成客户希望的另一个接口。适配器使原本因接口不兼容而不能一起工作的类可以一起工作。
问题
现有类(Adaptee)的接口与客户端期望的目标接口(Target)不匹配。例如,你希望复用一个已有的日志库,但它输出的格式不符合你系统要求的统一接口。
解决方案
- 创建适配器(Adapter) 类实现目标(Target)接口。
- 适配器内部持有一个适配者(Adaptee)对象的引用。
- 适配器将目标接口的调用转换为对适配者相应方法的调用。
结构(UML)
┌─────────────┐ ┌─────────────┐
│ Target │ │ Adaptee │
├─────────────┤ ├─────────────┤
│ + request() │ │ + specificRequest() │
└─────────────┘ └─────────────┘
△ △
│ │
┌─────────────┐ ┌─────────────────┐
│ Adapter │──────▶│ (holds adaptee)│
├─────────────┤ └─────────────────┘
│ + request() │
└─────────────┘
参与者
- Target:客户端期望使用的接口。
- Adaptee:需要适配的已有类,有特殊接口。
- Adapter:实现 Target 接口,内部调用 Adaptee 的方法。
协作
- 客户端通过 Target 接口调用 Adapter 的方法。
- Adapter 将调用转发给 Adaptee,完成适配。
适用性
- 需要使用现有类,但其接口不符合需求。
- 希望创建一个可复用的类,与不相关或不可预见的类协作。
- (对象适配器)需要适配多个 Adaptee 及其子类。
Python 示例
# Adaptee: 不兼容的接口
class EuropeanSocket:
def voltage(self): return 230
def live(self): return "live wire"
def neutral(self): return "neutral"
# Target: 客户端期望的接口
class USASocket:
def voltage(self): return 120
def hot(self): return "hot"
def neutral(self): return "neutral"
# Adapter
class Adapter(USASocket):
def __init__(self, euro_socket: EuropeanSocket):
self._euro = euro_socket
def voltage(self):
return 120 # 转换电压
def hot(self):
return "hot" # self._euro.live() 映射 live -> hot
# Client
def plug_in(socket: USASocket):
print(f"Plugging with {socket.voltage()}V, hot: {socket.hot()}")
euro = EuropeanSocket()
adapter = Adapter(euro)
plug_in(adapter)2. 桥接模式(Bridge)
意图
将抽象部分与它的实现部分分离,使它们都可以独立地变化。
问题
当抽象可能有多种实现方式时,如果用继承,类层次会急剧膨胀且难以维护。例如:形状(圆形、方形)和颜色(红、蓝)的组合——用继承需要 2×2=4 个类;若增加形状或颜色,类数量爆炸。
解决方案
- 将抽象(Abstraction)与实现(Implementor)分离,使用组合而非继承。
- Abstraction 持有 Implementor 的引用,客户通过 Abstraction 操作,具体行为委托给 Implementor。
- 两者可独立扩展,在运行时动态组合。
结构(UML)
┌─────────────┐ ┌─────────────┐
│ Abstraction │──────────│ Implementor │
├─────────────┤ ├─────────────┤
│ + operation()│ │ + operationImpl()│
└─────────────┘ └─────────────┘
△ △
┌─────────────┐ ┌─────────────┐
│RefinedAbstraction│ │ConcreteImplementorA│
└─────────────┘ └─────────────┘
参与者
- Abstraction:定义抽象接口,维护一个指向 Implementor 的引用。
- RefinedAbstraction:扩展 Abstraction 的接口。
- Implementor:定义实现类接口,通常只提供基本操作。
- ConcreteImplementor:实现 Implementor 接口。
协作
- Abstraction 将客户端请求转发给其 Implementor 对象。
适用性
- 不希望抽象和实现之间有一个固定的绑定关系。
- 抽象和实现都应该可以通过子类化独立扩展。
- 对抽象的实现部分的修改应对客户不产生影响。
Python 示例
# Implementor
class DrawingAPI:
def draw_circle(self, x, y, radius): pass
# ConcreteImplementors
class DrawingAPI1(DrawingAPI):
def draw_circle(self, x, y, radius):
print(f"API1 drawing circle at ({x},{y}) radius {radius}")
class DrawingAPI2(DrawingAPI):
def draw_circle(self, x, y, radius):
print(f"API2 drawing circle at ({x},{y}) radius {radius}")
# Abstraction
class Shape:
def __init__(self, drawing_api: DrawingAPI):
self._drawing_api = drawing_api
def draw(self): pass
def resize(self, factor): pass
# RefinedAbstraction
class Circle(Shape):
def __init__(self, x, y, radius, drawing_api):
super().__init__(drawing_api)
self._x = x
self._y = y
self._radius = radius
def draw(self):
self._drawing_api.draw_circle(self._x, self._y, self._radius)
def resize(self, factor):
self._radius *= factor
# Client
circle1 = Circle(1, 2, 3, DrawingAPI1())
circle1.draw()
circle2 = Circle(5, 7, 11, DrawingAPI2())
circle2.draw()3. 组合模式(Composite)
意图
将对象组合成树形结构以表示“部分-整体”的层次结构。组合模式使客户端对单个对象和组合对象的使用具有一致性。
问题
处理树形结构(如文件系统、UI 控件)时,叶节点和容器节点需要不同处理,代码会有大量 if 判断。
解决方案
- 定义一个组件(Component) 接口,叶节点(Leaf)和容器(Composite)都实现该接口。
- 容器对象包含子组件列表,并实现添加、删除子组件的方法。
- 客户端统一使用 Component 接口操作,无需区分叶子和容器。
结构(UML)
┌─────────────┐
│ Component │
├─────────────┤
│ + operation()│
│ + add(c) │ (可选)
│ + remove(c) │
└─────────────┘
△
┌────┴────┐
┌───────┐ ┌───────┐
│ Leaf │ │Composite│
├───────┤ ├───────┤
│ + operation││ + operation│
└───────┘ │ + add() │
│ + remove()│
└───────────┘
参与者
- Component:为组合中的对象声明接口,可实现默认行为。
- Leaf:表示叶节点对象,没有子节点。
- Composite:定义有子节点的行为,存储子组件,实现子组件相关操作。
协作
- 客户端通过 Component 接口操作组合结构,对单个对象和组合对象一视同仁。
适用性
- 表示对象的“部分-整体”层次结构。
- 希望客户端忽略组合对象与单个对象的不同。
Python 示例
from abc import ABC, abstractmethod
# Component
class Graphic(ABC):
@abstractmethod
def draw(self): pass
# Leaf
class Circle(Graphic):
def draw(self): print("Drawing Circle")
class Rectangle(Graphic):
def draw(self): print("Drawing Rectangle")
# Composite
class CompositeGraphic(Graphic):
def __init__(self):
self._children = []
def add(self, graphic: Graphic):
self._children.append(graphic)
def remove(self, graphic: Graphic):
self._children.remove(graphic)
def draw(self):
for child in self._children:
child.draw()
# Client
leaf1 = Circle()
leaf2 = Rectangle()
composite = CompositeGraphic()
composite.add(leaf1)
composite.add(leaf2)
composite.draw()4. 装饰器模式(Decorator)
意图
动态地给一个对象添加额外的职责。相比生成子类,装饰器模式更灵活。
问题
用继承扩展功能是静态的,且子类数量可能爆炸。例如:给窗口添加滚动条、边框、阴影等功能,子类组合会很多。
解决方案
- 定义组件(Component) 接口。
- 具体组件(ConcreteComponent) 实现该接口。
- 装饰器(Decorator) 类也实现 Component 接口,并持有一个 Component 的引用。
- 具体装饰器(ConcreteDecorator)扩展装饰器,在调用前后添加行为。
结构(UML)
┌─────────────┐
│ Component │
├─────────────┤
│ + operation()│
└─────────────┘
△
┌─────────────┐
│ Decorator │──────▶ (holds Component)
├─────────────┤
│ + operation()│
└─────────────┘
△
┌─────────────┐
│ConcreteDecoratorA│
├─────────────┤
│ + operation()│
│ + addedBehavior()│
└─────────────┘
参与者
- Component:定义对象接口。
- ConcreteComponent:原始对象,可以动态添加职责。
- Decorator:维持指向 Component 的引用,并实现 Component 接口。
- ConcreteDecorator:添加额外职责。
协作
- Decorator 将请求转发给 Component,并可能在转发前后执行额外操作。
适用性
- 在不影响其他对象的情况下,动态、透明地给单个对象添加职责。
- 需要扩展一个类的功能,但生成子类不可行(如类被标记为 final,或子类数量膨胀)。
Python 示例
# Component
class Coffee:
def cost(self): return 5
def description(self): return "Coffee"
# Decorator
class CoffeeDecorator(Coffee):
def __init__(self, coffee: Coffee):
self._coffee = coffee
def cost(self):
return self._coffee.cost()
def description(self):
return self._coffee.description()
# ConcreteDecorators
class MilkDecorator(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 1
def description(self):
return self._coffee.description() + ", Milk"
class SugarDecorator(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 0.5
def description(self):
return self._coffee.description() + ", Sugar"
# Client
my_coffee = Coffee()
my_coffee = MilkDecorator(my_coffee)
my_coffee = SugarDecorator(my_coffee)
print(my_coffee.description(), "cost:", my_coffee.cost())5. 外观模式(Facade)
意图
为子系统中的一组接口提供一个统一的简化接口。外观模式定义了一个高层接口,使子系统更易使用。
问题
复杂子系统有多个类,客户端需要了解很多细节才能使用。比如启动计算机需要 CPU、内存、硬盘等对象协同操作。
解决方案
- 引入外观(Facade) 类,它提供简单的接口(如
start()),内部封装子系统的复杂交互。 - 客户端只需与外观交互,无需了解子系统内部。
结构(UML)
┌─────────────┐
│ Client │
└──────┬──────┘
│
┌──────▼──────┐ ┌─────────────────┐
│ Facade │─────▶│ Subsystem classes │
└─────────────┘ └─────────────────┘
参与者
- Facade:知道哪些子系统类负责处理请求,将客户端请求委派给适当的子系统对象。
- Subsystem classes:实现子系统的功能,处理 Facade 指派的任务,但不知道 Facade 的存在。
协作
- 客户端调用 Facade 的方法,Facade 将调用转发给子系统。
适用性
- 需要为复杂子系统提供简单接口。
- 希望将客户端与子系统解耦,降低编译依赖。
- 需要分层系统中为每层定义入口点。
Python 示例
# Subsystem classes
class CPU:
def freeze(self): print("CPU freeze")
def jump(self, position): print(f"CPU jump to {position}")
def execute(self): print("CPU execute")
class Memory:
def load(self, position, data): print(f"Memory load {data} to {position}")
class HardDrive:
def read(self, lba, size): return f"boot sector data"
# Facade
class ComputerFacade:
def __init__(self):
self._cpu = CPU()
self._memory = Memory()
self._hd = HardDrive()
def start(self):
self._cpu.freeze()
boot_data = self._hd.read(0, 1024)
self._memory.load(0, boot_data)
self._cpu.jump(0)
self._cpu.execute()
# Client
computer = ComputerFacade()
computer.start()6. 享元模式(Flyweight)
意图
运用共享技术有效地支持大量细粒度的对象。
问题
当系统中存在大量相似对象(如文字处理器中的每个字符),每个对象存储全部数据会消耗大量内存。
解决方案
- 将对象的状态分为内部状态(可共享,不随环境改变)和外部状态(不可共享,由客户端传递)。
- 享元工厂维护一个享元池,客户端请求时若存在则返回共享对象,否则创建新对象。
- 客户端负责传递外部状态。
结构(UML)
┌─────────────┐ ┌─────────────┐
│ FlyweightFactory│────▶│ Flyweight │
├─────────────┤ ├─────────────┤
│ + getFlyweight(key)│ │ + operation(extrinsicState)│
└─────────────┘ └─────────────┘
△
┌─────────────┐
│ConcreteFlyweight│
├─────────────┤
│ - intrinsicState│
│ + operation(extrinsic)│
└─────────────┘
参与者
- Flyweight:声明接口,接受外部状态。
- ConcreteFlyweight:实现 Flyweight 接口,存储内部状态。
- FlyweightFactory:创建并管理享元对象,确保共享。
- Client:维护外部状态,并调用享元对象。
协作
- 客户端不直接实例化 ConcreteFlyweight,而是通过工厂获取。
- 客户端将外部状态传递给享元的 operation 方法。
适用性
- 大量对象且内存开销大。
- 对象的大多数状态可以变为外部状态。
- 删除外部状态后,可以用相对较少的共享对象替代。
Python 示例
# Flyweight
class Character:
def __init__(self, char_code: str):
self._char_code = char_code # intrinsic state
def display(self, font_size: int): # extrinsic state
print(f"Character '{self._char_code}' with font size {font_size}")
# FlyweightFactory
class CharacterFactory:
_pool = {}
@classmethod
def get_character(cls, char_code: str):
if char_code not in cls._pool:
cls._pool[char_code] = Character(char_code)
return cls._pool[char_code]
# Client
def render_text(text: str, font_size: int):
for ch in text:
char = CharacterFactory.get_character(ch)
char.display(font_size)
render_text("hello", 12)7. 代理模式(Proxy)
意图
为其他对象提供一种代理以控制对这个对象的访问。
问题
直接访问一个对象可能带来问题:例如远程访问开销大、需要延迟加载大对象、或者需要控制访问权限。
解决方案
- 代理(Proxy)实现与真实对象(RealSubject)相同的接口。
- 代理持有真实对象的引用,并在访问前后执行额外操作(如权限检查、延迟初始化、日志)。
结构(UML)
┌─────────────┐
│ Subject │
├─────────────┤
│ + request() │
└─────────────┘
△
┌────┴────┐
┌───────┐ ┌───────┐
│ Proxy │ │RealSubject│
├───────┤ ├───────┤
│ + request()│ │ + request()│
└───────┘ └───────┘
参与者
- Subject:定义 RealSubject 和 Proxy 的公共接口。
- RealSubject:真正的业务对象。
- Proxy:维护引用使得可以访问 RealSubject,并实现 Subject 接口。
协作
- 客户端通过 Proxy 访问 RealSubject,Proxy 控制访问方式(远程、虚拟、保护等)。
适用性
- 远程代理:隐藏对象位于不同地址空间的事实。
- 虚拟代理:延迟加载,直到真正需要才创建对象。
- 保护代理:控制访问权限。
- 智能引用:在访问时附加额外操作(如引用计数、日志)。
Python 示例
# Subject
class Image:
def display(self): pass
# RealSubject
class RealImage(Image):
def __init__(self, filename):
self._filename = filename
self._load_from_disk()
def _load_from_disk(self):
print(f"Loading {self._filename} from disk...")
def display(self):
print(f"Displaying {self._filename}")
# Proxy
class ProxyImage(Image):
def __init__(self, filename):
self._filename = filename
self._real_image = None
def display(self):
if self._real_image is None:
self._real_image = RealImage(self._filename)
self._real_image.display()
# Client
img = ProxyImage("photo.jpg")
img.display() # loads and displays
img.display() # only displays (already loaded)结构型模式总结
| 模式 | 核心思想 | 主要解决的问题 |
|---|---|---|
| 适配器 | 转换接口,使不兼容的类可协作 | 接口不匹配 |
| 桥接 | 抽象与实现分离,独立变化 | 多维度变化导致类爆炸 |
| 组合 | 树形结构,统一对待叶子和容器 | 部分-整体层次表示 |
| 装饰器 | 动态添加职责,替代子类扩展 | 避免子类膨胀,运行时增强 |
| 外观 | 提供统一简化接口,封装子系统 | 复杂子系统易用性 |
| 享元 | 共享细粒度对象,节省内存 | 大量相似对象的内存开销 |
| 代理 | 控制对象访问,增加间接层 | 访问控制、延迟加载、远程访问 |