桥接模式练习

桥接模式练习——形状与颜色、遥控与设备、消息与渠道

按《Python 设计模式——桥接模式》的建议,通过三道练习巩固:① 形状与颜色(抽象持实现,任意组合);② 遥控器与设备;③ 消息类型与发送渠道。每步都有完整可运行代码和验证要点。


练习一:形状与颜色(抽象 + 实现分离)

目的

体会抽象(形状)持有实现(颜色),画的时候委托给颜色;圆形/方形红/蓝两维独立,要“红色圆形”只需 Circle(Red())不需要 RedCircle 类;加新颜色或新形状只加一个类,不互相乘。

要求

  • 定义实现者 Color 抽象类,有方法 fill() -> str(返回颜色名)。
  • 实现 RedBlue,fill 分别返回 "红色""蓝色"
  • 定义抽象 Shape:构造时接收 color: Color,保存为 self._color;有抽象方法 draw()
  • 实现 CircleSquare(精化抽象):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() 抽象方法。
  • 实现 TVRadio,分别打印“电视开机/关机”“音响开机/关机”。
  • 定义抽象 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) 抽象方法。
  • 实现 SmsSenderEmailSender,send 时分别打印“[短信] 发给 to: content”“[邮件] 发给 to: content”。
  • 定义抽象 Message:构造时接收 sender: Sender,保存为 self._sender;有抽象方法 notify(to: str, content: str)
  • 实现 NormalMessageUrgentMessage(精化抽象):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 类。

自检问题

  1. 桥接中“抽象”和“实现”是怎么连在一起的?
    通过组合:抽象类(如 Shape、Remote、Message)的构造或属性里持有一个实现者(Color、Device、Sender),在行为方法里委托给实现者(如 self._color.fill()、self._device.turn_on())。

  2. 为什么要避免“维度1 × 维度2”的子类?
    用继承枚举所有组合会导致类爆炸(如 RedCircle、BlueCircle、RedSquare、BlueSquare…),加一维就乘一批类;桥接用两维独立的类(形状类、颜色类),组合得到效果,加一维只加少量类,不乘。

  3. 若要多一个“绿色”,需要改形状类吗?
    不需要。只新增 Green(Color),原有 Circle、Square 不用改,直接 Circle(Green()) 即可;这就是两维独立扩展

做完以上三道练习,再对照《桥接模式》文档,对“抽象持实现、两维独立组合”会掌握得比较扎实。建议先写形状与颜色,再写遥控与设备、消息与渠道,体会“组合代替继承乘”,这样对桥接模式会掌握得比较扎实。

发表评论