桥接模式练习——形状与颜色、遥控与设备、消息与渠道
按《Python 设计模式——桥接模式》的建议,通过三道练习巩固:① 形状与颜色(抽象持实现,任意组合);② 遥控器与设备;③ 消息类型与发送渠道。每步都有完整可运行代码和验证要点。
练习一:形状与颜色(抽象 + 实现分离)
目的
体会抽象(形状)持有实现(颜色),画的时候委托给颜色;圆形/方形和红/蓝两维独立,要“红色圆形”只需 Circle(Red()),不需要 RedCircle 类;加新颜色或新形状只加一个类,不互相乘。
要求
- 定义实现者 Color 抽象类,有方法 fill() -> str(返回颜色名)。
- 实现 Red、Blue,fill 分别返回
"红色"、"蓝色"。 - 定义抽象 Shape:构造时接收 color: Color,保存为 self._color;有抽象方法 draw()。
- 实现 Circle、Square(精化抽象):draw 时打印“画一个{self._color.fill()}圆形/方形”。
- 客户代码:创建 Circle(Red())、Square(Blue())、Circle(Blue()),分别调用 draw(),验证输出正确且没有 RedCircle、BlueSquare 等类。
参考答案
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 "蓝色"
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()}方形")
# 使用
Circle(Red()).draw() # 画一个红色圆形
Square(Blue()).draw() # 画一个蓝色方形
Circle(Blue()).draw() # 画一个蓝色圆形
验证要点
- Circle(Red()).draw() 输出 画一个红色圆形;Square(Blue()).draw() 输出 画一个蓝色方形;Circle(Blue()).draw() 输出 画一个蓝色圆形。
- 确认:没有 RedCircle、BlueSquare、BlueCircle 等类,只有 Shape 系(Circle、Square) 和 Color 系(Red、Blue),组合通过 Shape(color) 完成。
- 理解:若加“绿色”,只加 Green(Color);若加“三角形”,只加 Triangle(Shape),类数量是加而不是乘。
练习二:遥控器与设备(抽象 + 实现)
目的
遥控器(抽象)持有设备(实现);开机/关机等操作委托给设备。不同遥控(如普通遥控、高级遥控)和不同设备(电视、音响)可任意组合,无需为每种组合写一个类。
要求
- 定义实现者 Device 抽象类,有 turn_on()、turn_off() 抽象方法。
- 实现 TV、Radio,分别打印“电视开机/关机”“音响开机/关机”。
- 定义抽象 Remote:构造时接收 device: Device,保存为 self._device;有方法 power(),内部调 self._device.turn_on()(或根据状态切换 on/off,这里简化为只调 turn_on)。
- 实现 BasicRemote(精化抽象):继承 Remote,power 时先打印“[普通遥控] 按下电源”,再调 self._device.turn_on()。
- 客户代码:创建 BasicRemote(TV()) 和 BasicRemote(Radio()),分别调用 power(),验证输出正确;确认没有 TVRemote、RadioRemote 等组合类。
参考答案
from abc import ABC, abstractmethod
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(" 音响关机")
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()
# 使用
BasicRemote(TV()).power() # [普通遥控] 按下电源 / 电视开机
BasicRemote(Radio()).power() # [普通遥控] 按下电源 / 音响开机
验证要点
- BasicRemote(TV()).power() 输出 [普通遥控] 按下电源 和 电视开机;BasicRemote(Radio()).power() 输出 [普通遥控] 按下电源 和 音响开机。
- 确认:没有 TVRemote、RadioRemote 类;遥控类型(BasicRemote)和设备类型(TV、Radio)是两维,通过 Remote(device) 组合。
练习三:消息类型与发送渠道(一族抽象 + 一族实现)
目的
消息类型(抽象)持有发送渠道(实现);同一条消息可以用不同渠道发(短信、邮件),同一渠道可以发不同类型消息(普通、紧急)。两维独立,用桥接组合。
要求
- 定义实现者 Sender 抽象类,有 send(content: str, to: str) 抽象方法。
- 实现 SmsSender、EmailSender,send 时分别打印“[短信] 发给 to: content”“[邮件] 发给 to: content”。
- 定义抽象 Message:构造时接收 sender: Sender,保存为 self._sender;有抽象方法 notify(to: str, content: str)。
- 实现 NormalMessage、UrgentMessage(精化抽象):notify 时 NormalMessage 直接 self._sender.send(content, to),UrgentMessage 在 content 前加 “[紧急]” 再 send。
- 客户代码:用 NormalMessage(SmsSender()) 和 UrgentMessage(EmailSender()) 各调一次 notify,验证输出;确认“消息类型”和“发送渠道”可任意组合,无需为每种组合写类。
参考答案
from abc import ABC, abstractmethod
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}")
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)
# 使用
NormalMessage(SmsSender()).notify("13800138000", "您好")
# [短信] 发给 13800138000: 您好
UrgentMessage(EmailSender()).notify("user@a.com", "请处理")
# [邮件] 发给 user@a.com: [紧急] 请处理
验证要点
- NormalMessage(SmsSender()).notify(…) 输出短信格式且无“[紧急]”;UrgentMessage(EmailSender()).notify(…) 输出邮件格式且内容带 [紧急]。
- 确认:消息类型(Normal/Urgent)和发送渠道(Sms/Email)是两维,通过 Message(sender) 组合;要“普通+邮件”或“紧急+短信”只需 NormalMessage(EmailSender())、UrgentMessage(SmsSender()),不需要 NormalSmsMessage 等 2×2 个类。
三步汇总与自检
| 练习 | 重点 | 关键点 |
|---|---|---|
| 一 | 形状×颜色 | 形状持 Color,draw 时用 color.fill();Circle(Red())、Square(Blue()) 等任意组合,无 RedCircle 类。 |
| 二 | 遥控×设备 | 遥控持 Device,power 时委托 device.turn_on;BasicRemote(TV()) 等组合,无 TVRemote 类。 |
| 三 | 消息×渠道 | 消息持 Sender,notify 时委托 sender.send;NormalMessage(SmsSender()) 等组合,无 NormalSmsMessage 类。 |
自检问题
-
桥接中“抽象”和“实现”是怎么连在一起的?
通过组合:抽象类(如 Shape、Remote、Message)的构造或属性里持有一个实现者(Color、Device、Sender),在行为方法里委托给实现者(如 self._color.fill()、self._device.turn_on())。 -
为什么要避免“维度1 × 维度2”的子类?
用继承枚举所有组合会导致类爆炸(如 RedCircle、BlueCircle、RedSquare、BlueSquare…),加一维就乘一批类;桥接用两维独立的类(形状类、颜色类),组合得到效果,加一维只加少量类,不乘。 -
若要多一个“绿色”,需要改形状类吗?
不需要。只新增 Green(Color),原有 Circle、Square 不用改,直接 Circle(Green()) 即可;这就是两维独立扩展。
做完以上三道练习,再对照《桥接模式》文档,对“抽象持实现、两维独立组合”会掌握得比较扎实。建议先写形状与颜色,再写遥控与设备、消息与渠道,体会“组合代替继承乘”,这样对桥接模式会掌握得比较扎实。