桥接模式(Bridge Pattern)详解
一、核心概念
桥接模式将抽象部分 与实现部分 分离,使它们可以独立变化。这里的"抽象"指的是高层逻辑(如业务接口),"实现"指的是底层实现(如具体平台或算法)。通过组合而非继承,桥接模式避免了抽象与实现的紧耦合,支持两者独立扩展。
核心组件:
- 抽象(Abstraction):定义高层接口,持有实现部分的引用。
- 细化抽象(Refined Abstraction):扩展抽象接口,调用实现部分的方法。
- 实现接口(Implementor):定义实现部分的接口,供具体实现类实现。
- 具体实现(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;
}
三、桥接模式的优势
-
分离抽象与实现:
- 抽象部分(如形状)和实现部分(如绘制API)可独立演化,互不影响。
-
多维度扩展:
- 可独立增加新的抽象(如三角形)或新的实现(如macOS绘制API)。
-
降低耦合度:
- 通过组合而非继承,避免了类爆炸问题(如
WindowsCircle
、LinuxCircle
等)。
- 通过组合而非继承,避免了类爆炸问题(如
-
动态切换实现:
- 运行时可通过修改引用动态切换实现(如从Windows切换到Linux绘制API)。
四、与其他模式的对比
-
与策略模式的区别:
- 桥接模式关注"抽象"与"实现"的分离,支持两者独立扩展;
- 策略模式关注"算法"的替换,客户端需明确知道不同策略的存在。
-
与适配器模式的结合:
- 适配器模式用于兼容已有的不兼容接口,而桥接模式用于设计阶段的解耦。
-
与装饰器模式的区别:
- 装饰器模式增强对象功能,桥接模式分离抽象与实现。
五、适用场景
-
需要避免抽象与实现永久绑定:
- 如跨平台应用、数据库驱动切换。
-
抽象和实现都可扩展:
- 如UI组件支持多种渲染引擎(DirectX、OpenGL)。
-
需要在多个维度扩展系统:
- 如形状(圆形、矩形)与颜色(红、蓝)的组合。
-
需要动态切换实现:
- 如运行时选择加密算法(MD5、SHA256)。
六、注意事项
-
接口设计复杂度:
- 需合理设计实现接口,避免接口过于庞大。
-
过度使用风险:
- 简单场景下使用桥接模式可能增加系统复杂度。
-
与继承的权衡:
- 若抽象和实现的变化维度不多,继承可能更简单直接。
桥接模式通过组合替代继承,将抽象与实现分离,使系统更具灵活性和可扩展性。在需要处理多维度变化的场景中,桥接模式是一种优雅的解决方案,它能有效避免继承导致的类爆炸问题,让代码结构更加清晰。
烧烤案例中的设计模式解析
一、案例背景与问题分析
在烧烤场景中:
- 行为请求者:顾客(提出烤肉需求)。
- 行为实现者:烤肉师傅(执行烤肉操作)。
- 核心问题 :请求者与实现者直接交互会导致紧耦合,难以处理排队、日志记录、撤销/重做等功能。
现实中,门店通过服务员作为中间层解耦顾客和师傅:
- 顾客向服务员下单(请求)。
- 服务员记录订单、排队、传递请求给师傅。
- 师傅专注烤肉,不关心谁点了什么。
二、涉及的设计模式
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;
}
};
三、设计模式的协同作用
-
解耦请求者与实现者:
- 命令模式通过封装请求,使顾客(请求者)与师傅(实现者)不直接交互。
-
支持排队和日志:
- 服务员(Invoker)管理命令队列,可实现订单排序、记录日志。
-
撤销/重做功能:
- 命令对象实现
undo()
方法,结合备忘录模式保存订单状态。
- 命令对象实现
-
灵活扩展:
- 新增菜品只需添加新的命令类,无需修改服务员和师傅代码。
四、现实场景中的应用
-
餐厅系统 :
订单管理、厨房显示系统(KDS)使用命令模式解耦顾客与厨师。
-
电商平台 :
订单处理、物流跟踪系统使用责任链模式分配任务。
-
文本编辑器 :
撤销/重做功能使用备忘录模式保存文档状态。
通过引入中间层(服务员)和设计模式,烧烤案例实现了:
- 请求者与实现者的解耦。
- 订单的集中管理(排队、日志)。
- 操作的可撤销性(重烤、取消订单)。
- 系统的可扩展性(新增菜品、师傅)。