Python 设计模式——桥接模式(Bridge Pattern)详解
本文面向零基础新手,从“类爆炸”问题到抽象与实现分离,从简单示例到多维度变化,全方位讲解桥接模式。
一、什么是桥接模式?
1.1 通俗理解
桥接模式是一种结构型设计模式,核心思想是:
把“抽象”(高层逻辑、业务概念)和“实现”(底层具体做法)拆开,让它们可以独立变化;通过“组合”把抽象和实现连在一起,而不是用继承把各种组合都写成子类,从而避免“类爆炸”。
可以这样类比:
- 不用桥接:你要做“红色圆形、蓝色圆形、红色方形、蓝色方形……”每种组合写一个类(RedCircle、BlueCircle、RedSquare、BlueSquare…),再加绿色就再乘一批类,类数量 = 形状数 × 颜色数,爆炸式增长。
- 用桥接:形状是一类东西(抽象),颜色/绘制方式是另一类东西(实现)。一个“形状”对象持有一个“颜色/绘制”对象,画的时候让颜色去画。这样形状类只有圆形、方形…,颜色类只有红、蓝…,类数量 = 形状数 + 颜色数,新增颜色或形状都只加一个类,不互相乘。
所以,桥接模式解决的是:当有两个(或更多)会独立变化的维度时,用“组合”代替“继承枚举所有组合”,避免类爆炸、便于扩展。
1.2 为什么需要桥接?——类爆炸问题
假设你要做“形状 + 颜色”:形状有圆形、方形,颜色有红、蓝。用继承硬拼会怎样?
# 糟糕做法:每个组合一个类
class RedCircle:
def draw(self):
print("画红色圆形")
class BlueCircle:
def draw(self):
print("画蓝色圆形")
class RedSquare:
def draw(self):
print("画红色方形")
class BlueSquare:
def draw(self):
print("画蓝色方形")
再加一个“绿色”、一个“三角形”,就要再写 GreenCircle、GreenSquare、GreenTriangle、RedTriangle、BlueTriangle……类数量 = 形状 × 颜色,难以维护。
桥接的做法:
- “形状”是一维(抽象):圆形、方形…
- “颜色/绘制实现”是另一维(实现):红、蓝…
- 形状不继承颜色,而是持有一个“颜色/绘制”对象,画的时候委托给它。
这样加新形状只加形状子类,加新颜色只加颜色子类,两维独立变化。
1.3 适用场景(什么时候用?)
| 场景 | 说明 |
|---|---|
| 多个维度独立变化 | 如形状×颜色、设备×遥控方式、消息×发送渠道,不想用继承枚举所有组合。 |
| 抽象和实现都要能独立扩展 | 希望加新“类型”时不影响另一维的类。 |
| 避免永久绑定 | 运行时可以换“实现”(如换一种绘制方式、换一种发送方式)。 |
简单记忆:有两个(或更多)会独立变化的维度,且不想写“维度1 × 维度2”那么多子类时,用桥接。
二、桥接模式的结构(四个角色)
| 角色 | 说明 |
|---|---|
| 抽象(Abstraction) | 高层概念,持有“实现”的引用,把部分工作委托给实现。如“形状”,持有“颜色/绘制”。 |
| refined 抽象(RefinedAbstraction) | 对抽象的扩展或具体化。如“圆形”“方形”。 |
| 实现者(Implementor) | 底层实现的接口,供抽象调用。如“颜色”或“绘制接口”。 |
| 具体实现(ConcreteImplementor) | 实现者的具体类。如“红色”“蓝色”。 |
关系可以理解为:
- 抽象 不直接干底层活,而是通过组合持有一个 实现者,需要时调用实现者的方法。
- refined 抽象 是抽象的子类,代表一种具体“类型”(如圆形);具体实现 是实现者的子类,代表一种具体“实现”(如红色)。
- 这样“圆形 + 红色” = 一个 RefinedAbstraction(圆形)里装一个 ConcreteImplementor(红色),不需要 RedCircle 这个类。
三、示例一:形状与颜色(最经典)
需求:形状有圆形、方形;颜色有红、蓝。用桥接让形状和颜色独立变化,任意组合。
3.1 实现者接口 + 具体实现(颜色/绘制)
“颜色”在这里就是“实现”这一维:负责“怎么画”(或提供颜色信息)。
from abc import ABC, abstractmethod
class Color(ABC):
"""实现者接口:颜色(或绘制实现)"""
@abstractmethod
def fill(self) -> str:
"""返回填充颜色名,或实际做绘制"""
pass
class Red(Color):
def fill(self) -> str:
return "红色"
class Blue(Color):
def fill(self) -> str:
return "蓝色"
3.2 抽象 + 精化抽象(形状)
“形状”是抽象:持有“颜色”,画的时候用颜色的 fill()。
class Shape(ABC):
"""抽象:形状,持有颜色(实现)"""
def __init__(self, color: Color):
self._color = color
@abstractmethod
def draw(self):
pass
class Circle(Shape):
"""精化抽象:圆形"""
def draw(self):
print(f"画一个{self._color.fill()}圆形")
class Square(Shape):
"""精化抽象:方形"""
def draw(self):
print(f"画一个{self._color.fill()}方形")
3.3 使用:任意组合,无新类
red_circle = Circle(Red())
blue_circle = Circle(Blue())
red_square = Square(Red())
blue_square = Square(Blue())
red_circle.draw() # 画一个红色圆形
blue_square.draw() # 画一个蓝色方形
要点:要“红色圆形”只需 Circle(Red()),不需要 RedCircle 类;加绿色只需加 Green(Color),加三角形只需加 Triangle(Shape),两维独立扩展。
四、示例二:遥控器与设备(抽象 = 遥控,实现 = 设备)
需求:遥控器有普通遥控、高级遥控;设备有电视、音响。遥控器要控制“开/关”等,但具体动作由设备执行。用桥接:抽象 = 遥控器,实现 = 设备接口。
4.1 实现者:设备接口 + 具体设备
class Device(ABC):
"""实现者:设备"""
@abstractmethod
def turn_on(self):
pass
@abstractmethod
def turn_off(self):
pass
class TV(Device):
def turn_on(self):
print("电视开机")
def turn_off(self):
print("电视关机")
class Radio(Device):
def turn_on(self):
print("音响开机")
def turn_off(self):
print("音响关机")
4.2 抽象:遥控器(持有设备)
class Remote(ABC):
"""抽象:遥控器,持有设备"""
def __init__(self, device: Device):
self._device = device
def power(self):
"""开/关切换:委托给设备"""
# 这里简化:只做一次开或关,实际可加状态
self._device.turn_on()
class BasicRemote(Remote):
"""普通遥控"""
def power(self):
print("[普通遥控] 按下电源键")
self._device.turn_on()
class AdvancedRemote(Remote):
"""高级遥控:多一个静音"""
def mute(self):
print("[高级遥控] 静音")
# 假设设备有 mute,这里仅示意
4.3 使用
tv = TV()
basic = BasicRemote(tv)
basic.power() # [普通遥控] 按下电源键 n 电视开机
radio = Radio()
adv = AdvancedRemote(radio)
adv.power() # 音响开机
遥控类型和设备类型可以任意组合,不需要 TVBasicRemote、TVAdvancedRemote、RadioBasicRemote… 那么多类。
五、示例三:消息与发送渠道(抽象 = 消息类型,实现 = 发送方式)
需求:消息有普通消息、紧急消息;发送方式有短信、邮件。用桥接:抽象 = 消息(持有“发送方式”),实现 = 发送渠道。
5.1 实现者:发送渠道
class Sender(ABC):
@abstractmethod
def send(self, content: str, to: str):
pass
class SmsSender(Sender):
def send(self, content: str, to: str):
print(f"[短信] 发给 {to}: {content}")
class EmailSender(Sender):
def send(self, content: str, to: str):
print(f"[邮件] 发给 {to}: {content}")
5.2 抽象:消息类型(持有发送渠道)
class Message(ABC):
def __init__(self, sender: Sender):
self._sender = sender
@abstractmethod
def notify(self, to: str, content: str):
pass
class NormalMessage(Message):
def notify(self, to: str, content: str):
self._sender.send(content, to)
class UrgentMessage(Message):
def notify(self, to: str, content: str):
self._sender.send(f"[紧急] {content}", to)
5.3 使用
sms = SmsSender()
email = EmailSender()
NormalMessage(sms).notify("13800138000", "你好")
UrgentMessage(email).notify("user@example.com", "请尽快处理")
# 任意 消息类型 × 发送渠道 组合,无需 N×M 个类
六、桥接 vs 继承枚举
| 方式 | 类数量 | 扩展 |
|---|---|---|
| 继承枚举所有组合 | 维度1 × 维度2 × … | 加一维就要乘一批类 |
| 桥接(抽象 + 实现分离) | 维度1 的类数 + 维度2 的类数 | 各维独立加类即可 |
记忆:桥接用组合把两个维度连起来,用继承分别扩展每一维,避免“乘起来”的子类。
七、桥接 vs 其他模式
| 模式 | 目的 | 与桥接的区别 |
|---|---|---|
| 桥接 | 抽象与实现分离,两维独立变化 | 强调“两个维度”,用组合连接 |
| 适配器 | 让不兼容接口能一起用 | 侧重接口转译,通常不涉及“两维独立扩展” |
| 策略 | 算法可替换 | 往往只有一个“策略”维度,桥接常有两维(抽象 + 实现) |
八、常见问题与注意点
8.1 谁算“抽象”、谁算“实现”?
抽象:更偏业务、高层、会“用”底层能力的那一侧(形状、遥控器、消息类型)。
实现:更偏技术、底层、真正干活的那一侧(颜色/绘制、设备、发送渠道)。
同一种划分在不同业务里可以互换,只要保证“两维独立变化、用组合连接”即可。
8.2 抽象和实现都要接口吗?
在 Python 里不强制用 ABC;只要“抽象侧”依赖的是“某一类能力”(如会 fill、会 turn_on),实现侧提供这些能力即可。用 ABC 可以让“抽象/实现”的约定更清晰。
8.3 运行时换“实现”
因为实现是组合进来的,所以可以在运行时换:例如 shape._color = Green(),就变成“绿色形状”;不必新增类。
九、小结
- 桥接模式:把抽象(高层)和实现(底层)分离,通过组合连接,使两维能独立变化,避免类爆炸。
- 四个角色:抽象、精化抽象、实现者、具体实现;抽象持有实现者,调用时委托给实现者。
- 典型用途:形状×颜色、遥控×设备、消息×发送渠道等“多维度独立变化”的场景。
- 核心:用组合代替“继承出所有组合”,类数从乘变加。
建议先写“形状×颜色”一例,再试“遥控×设备”或“消息×发送”,体会“加一个颜色/加一个形状”只加一个类、不互相乘,这样对桥接模式会掌握得比较扎实。