设计模式(八)

桥接模式(Bridge Pattern)详解

一、核心概念

桥接模式将抽象部分实现部分 分离,使它们可以独立变化。这里的"抽象"指的是高层逻辑(如业务接口),"实现"指的是底层实现(如具体平台或算法)。通过组合而非继承,桥接模式避免了抽象与实现的紧耦合,支持两者独立扩展。

核心组件

  1. 抽象(Abstraction):定义高层接口,持有实现部分的引用。
  2. 细化抽象(Refined Abstraction):扩展抽象接口,调用实现部分的方法。
  3. 实现接口(Implementor):定义实现部分的接口,供具体实现类实现。
  4. 具体实现(Concrete Implementor):实现实现接口的具体类。
二、代码示例:跨平台图形绘制

场景:设计一个跨平台图形绘制系统,支持在不同操作系统(Windows、Linux)上绘制不同形状(圆形、矩形)。

cpp 复制代码
#include <iostream>
#include <memory>

// 实现接口:绘制API
class DrawingAPI {
public:
    virtual void drawCircle(double x, double y, double radius) = 0;
    virtual void drawRectangle(double x1, double y1, double x2, double y2) = 0;
    virtual ~DrawingAPI() = default;
};

// 具体实现:Windows绘制API
class WindowsDrawingAPI : public DrawingAPI {
public:
    void drawCircle(double x, double y, double radius) override {
        std::cout << "Windows绘制圆形:圆心(" << x << "," << y 
                  << "),半径" << radius << std::endl;
    }

    void drawRectangle(double x1, double y1, double x2, double y2) override {
        std::cout << "Windows绘制矩形:左上角(" << x1 << "," << y1 
                  << "),右下角(" << x2 << "," << y2 << ")" << std::endl;
    }
};

// 具体实现:Linux绘制API
class LinuxDrawingAPI : public DrawingAPI {
public:
    void drawCircle(double x, double y, double radius) override {
        std::cout << "Linux绘制圆形:圆心(" << x << "," << y 
                  << "),半径" << radius << std::endl;
    }

    void drawRectangle(double x1, double y1, double x2, double y2) override {
        std::cout << "Linux绘制矩形:左上角(" << x1 << "," << y1 
                  << "),右下角(" << x2 << "," << y2 << ")" << std::endl;
    }
};

// 抽象:形状
class Shape {
protected:
    std::unique_ptr<DrawingAPI> drawingAPI;  // 组合实现接口

public:
    explicit Shape(std::unique_ptr<DrawingAPI> api) 
        : drawingAPI(std::move(api)) {}

    virtual void draw() = 0;  // 委托给实现部分
    virtual ~Shape() = default;
};

// 细化抽象:圆形
class Circle : public Shape {
private:
    double x, y, radius;

public:
    Circle(double x, double y, double radius, std::unique_ptr<DrawingAPI> api)
        : Shape(std::move(api)), x(x), y(y), radius(radius) {}

    void draw() override {
        drawingAPI->drawCircle(x, y, radius);  // 委托给实现
    }
};

// 细化抽象:矩形
class Rectangle : public Shape {
private:
    double x1, y1, x2, y2;

public:
    Rectangle(double x1, double y1, double x2, double y2, std::unique_ptr<DrawingAPI> api)
        : Shape(std::move(api)), x1(x1), y1(y1), x2(x2), y2(y2) {}

    void draw() override {
        drawingAPI->drawRectangle(x1, y1, x2, y2);  // 委托给实现
    }
};

// 客户端代码
int main() {
    // 创建Windows平台的圆形
    auto windowsCircle = std::make_unique<Circle>(
        100, 100, 50, std::make_unique<WindowsDrawingAPI>()
    );
    windowsCircle->draw();

    // 创建Linux平台的矩形
    auto linuxRectangle = std::make_unique<Rectangle>(
        200, 200, 300, 300, std::make_unique<LinuxDrawingAPI>()
    );
    linuxRectangle->draw();

    // 动态切换实现:在Linux上绘制圆形
    auto linuxCircle = std::make_unique<Circle>(
        400, 400, 100, std::make_unique<LinuxDrawingAPI>()
    );
    linuxCircle->draw();

    return 0;
}
三、桥接模式的优势
  1. 分离抽象与实现

    • 抽象部分(如形状)和实现部分(如绘制API)可独立演化,互不影响。
  2. 多维度扩展

    • 可独立增加新的抽象(如三角形)或新的实现(如macOS绘制API)。
  3. 降低耦合度

    • 通过组合而非继承,避免了类爆炸问题(如WindowsCircleLinuxCircle等)。
  4. 动态切换实现

    • 运行时可通过修改引用动态切换实现(如从Windows切换到Linux绘制API)。
四、与其他模式的对比
  1. 与策略模式的区别

    • 桥接模式关注"抽象"与"实现"的分离,支持两者独立扩展;
    • 策略模式关注"算法"的替换,客户端需明确知道不同策略的存在。
  2. 与适配器模式的结合

    • 适配器模式用于兼容已有的不兼容接口,而桥接模式用于设计阶段的解耦。
  3. 与装饰器模式的区别

    • 装饰器模式增强对象功能,桥接模式分离抽象与实现。
五、适用场景
  1. 需要避免抽象与实现永久绑定

    • 如跨平台应用、数据库驱动切换。
  2. 抽象和实现都可扩展

    • 如UI组件支持多种渲染引擎(DirectX、OpenGL)。
  3. 需要在多个维度扩展系统

    • 如形状(圆形、矩形)与颜色(红、蓝)的组合。
  4. 需要动态切换实现

    • 如运行时选择加密算法(MD5、SHA256)。
六、注意事项
  1. 接口设计复杂度

    • 需合理设计实现接口,避免接口过于庞大。
  2. 过度使用风险

    • 简单场景下使用桥接模式可能增加系统复杂度。
  3. 与继承的权衡

    • 若抽象和实现的变化维度不多,继承可能更简单直接。

桥接模式通过组合替代继承,将抽象与实现分离,使系统更具灵活性和可扩展性。在需要处理多维度变化的场景中,桥接模式是一种优雅的解决方案,它能有效避免继承导致的类爆炸问题,让代码结构更加清晰。

烧烤案例中的设计模式解析

一、案例背景与问题分析

在烧烤场景中:

  • 行为请求者:顾客(提出烤肉需求)。
  • 行为实现者:烤肉师傅(执行烤肉操作)。
  • 核心问题 :请求者与实现者直接交互会导致紧耦合,难以处理排队、日志记录、撤销/重做等功能。

现实中,门店通过服务员作为中间层解耦顾客和师傅:

  • 顾客向服务员下单(请求)。
  • 服务员记录订单、排队、传递请求给师傅。
  • 师傅专注烤肉,不关心谁点了什么。
二、涉及的设计模式
1. 命令模式(Command Pattern)

命令模式(Command)​,将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作。​

命令模式(Command)结构图

核心思想:将请求封装为对象,使请求者与实现者解耦。

烧烤场景应用

  • 命令对象:每个订单(如"两串羊肉串,微辣")封装为命令。
  • 服务员:接收命令,排序、记录日志、执行命令。
  • 师傅:根据命令执行具体操作。

代码示例

cpp 复制代码
#include <iostream>
#include <vector>
#include <memory>

// 烤肉师傅(Receiver)
class BarbecueChef {
public:
    void makeMutton(int count, bool spicy) {
        std::cout << "制作" << count << "串" << (spicy ? "辣" : "不辣") << "羊肉串" << std::endl;
    }

    void makeChickenWing(int count, bool spicy) {
        std::cout << "制作" << count << "串" << (spicy ? "辣" : "不辣") << "烤鸡翅" << std::endl;
    }
};

// 命令接口
class Command {
protected:
    BarbecueChef* chef;

public:
    explicit Command(BarbecueChef* chef) : chef(chef) {}
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};

// 具体命令:羊肉串命令
class MuttonCommand : public Command {
private:
    int count;
    bool spicy;
    bool executed = false;

public:
    MuttonCommand(BarbecueChef* chef, int count, bool spicy) 
        : Command(chef), count(count), spicy(spicy) {}

    void execute() override {
        chef->makeMutton(count, spicy);
        executed = true;
    }

    void undo() override {
        if (executed) {
            std::cout << "撤销:取消" << count << "串" << (spicy ? "辣" : "不辣") << "羊肉串" << std::endl;
            executed = false;
        }
    }
};

// 服务员(Invoker)
class Waiter {
private:
    std::vector<std::shared_ptr<Command>> orders;

public:
    void takeOrder(std::shared_ptr<Command> order) {
        orders.push_back(order);
        std::cout << "服务员记录订单" << std::endl;
    }

    void placeOrders() {
        std::cout << "服务员将订单发送给厨师" << std::endl;
        for (auto& order : orders) {
            order->execute();
        }
    }

    void cancelLastOrder() {
        if (!orders.empty()) {
            auto lastOrder = orders.back();
            lastOrder->undo();
            orders.pop_back();
            std::cout << "服务员取消最后一个订单" << std::endl;
        }
    }
};

// 客户端代码
int main() {
    auto chef = std::make_unique<BarbecueChef>();
    auto waiter = std::make_unique<Waiter>();

    // 顾客下单
    auto order1 = std::make_shared<MuttonCommand>(chef.get(), 2, true);
    auto order2 = std::make_shared<MuttonCommand>(chef.get(), 3, false);

    // 服务员记录订单
    waiter->takeOrder(order1);
    waiter->takeOrder(order2);

    // 执行订单
    waiter->placeOrders();

    // 顾客取消最后一个订单
    waiter->cancelLastOrder();

    return 0;
}
2. 责任链模式(Chain of Responsibility)

核心思想:将请求的发送者和接收者解耦,使多个对象都有机会处理请求。

烧烤场景应用

  • 预处理订单:服务员检查订单完整性。
  • 分配师傅:根据菜品类型分配给不同师傅(羊肉串师傅、鸡翅师傅)。
  • 处理异常:若师傅忙碌,将订单传递给备用师傅。

代码示例

cpp 复制代码
// 抽象处理者:厨师
class Chef {
protected:
    std::shared_ptr<Chef> nextChef;

public:
    virtual void setNextChef(std::shared_ptr<Chef> chef) {
        nextChef = chef;
    }

    virtual void processOrder(const std::string& dish) = 0;
    virtual ~Chef() = default;
};

// 具体处理者:羊肉串师傅
class MuttonChef : public Chef {
public:
    void processOrder(const std::string& dish) override {
        if (dish == "羊肉串") {
            std::cout << "羊肉串师傅处理订单" << std::endl;
        } else if (nextChef) {
            nextChef->processOrder(dish);
        } else {
            std::cout << "无人处理此订单" << std::endl;
        }
    }
};

// 具体处理者:鸡翅师傅
class ChickenWingChef : public Chef {
public:
    void processOrder(const std::string& dish) override {
        if (dish == "鸡翅") {
            std::cout << "鸡翅师傅处理订单" << std::endl;
        } else if (nextChef) {
            nextChef->processOrder(dish);
        } else {
            std::cout << "无人处理此订单" << std::endl;
        }
    }
};
3. 备忘录模式(Memento Pattern)

核心思想:保存对象状态,支持撤销/恢复。

烧烤场景应用

  • 保存订单状态:记录订单的初始状态(如已支付、已烤制)。
  • 重烤需求:恢复订单到未烤制状态。

代码示例

cpp 复制代码
// 订单备忘录
class OrderMemento {
private:
    bool paid;
    bool cooked;

public:
    OrderMemento(bool paid, bool cooked) : paid(paid), cooked(cooked) {}

    bool isPaid() const { return paid; }
    bool isCooked() const { return cooked; }
};

// 订单(原发器)
class Order {
private:
    bool paid = false;
    bool cooked = false;

public:
    void pay() { paid = true; }
    void cook() { cooked = true; }

    OrderMemento createMemento() const {
        return OrderMemento(paid, cooked);
    }

    void restoreFromMemento(const OrderMemento& memento) {
        paid = memento.isPaid();
        cooked = memento.isCooked();
    }

    void reCook() {
        std::cout << "订单重新烤制" << std::endl;
        cooked = false;
    }
};
三、设计模式的协同作用
  1. 解耦请求者与实现者

    • 命令模式通过封装请求,使顾客(请求者)与师傅(实现者)不直接交互。
  2. 支持排队和日志

    • 服务员(Invoker)管理命令队列,可实现订单排序、记录日志。
  3. 撤销/重做功能

    • 命令对象实现undo()方法,结合备忘录模式保存订单状态。
  4. 灵活扩展

    • 新增菜品只需添加新的命令类,无需修改服务员和师傅代码。
四、现实场景中的应用
  • 餐厅系统

    订单管理、厨房显示系统(KDS)使用命令模式解耦顾客与厨师。

  • 电商平台

    订单处理、物流跟踪系统使用责任链模式分配任务。

  • 文本编辑器

    撤销/重做功能使用备忘录模式保存文档状态。

通过引入中间层(服务员)和设计模式,烧烤案例实现了:

  • 请求者与实现者的解耦。
  • 订单的集中管理(排队、日志)。
  • 操作的可撤销性(重烤、取消订单)。
  • 系统的可扩展性(新增菜品、师傅)。