C++桥接模式的核心设计思路是将抽象部分的接口和具体实现部分分离,通过组合关系替代继承关系,让抽象层和实现层可以独立进行扩展,避免类爆炸问题。这种设计方式在需要多维度扩展的场景中非常实用,比如图形绘制系统中,图形类型和操作系统渲染逻辑就是两个独立的扩展维度。
桥接模式的核心角色划分
桥接模式主要包含四个核心角色,其中接口与实现类相关的角色是设计的核心:
- 抽象接口(Abstraction):定义抽象部分的接口,内部持有一个实现类接口的引用,将客户端的请求转发给实现类对象
- 扩充抽象接口(RefinedAbstraction):对抽象接口进行扩展,实现更具体的抽象功能
- 实现类接口(Implementor):定义实现部分的接口,供抽象接口层调用,一般是较为基础的操作定义
- 具体实现类(ConcreteImplementor):实现实现类接口的具体逻辑,完成基础操作的落地
实现类接口设计方法
实现类接口的设计需要遵循面向接口编程的原则,只定义最基础、最通用的操作,不需要关注上层抽象的业务逻辑。接口中的方法应该是实现层能够独立提供的功能,不依赖抽象层的具体业务。
比如我们设计一个跨平台的消息发送系统,实现类接口可以定义为不同平台的消息发送基础能力:
// 实现类接口:消息发送基础能力接口
class MessageSender {
public:
// 纯虚函数,定义发送消息的基础方法
virtual void send(const std::string& content) = 0;
// 虚析构函数,保证派生类析构正确调用
virtual ~MessageSender() = default;
};
实现类接口的设计注意点:
- 接口中只定义最基础的操作,不要包含和业务抽象相关的逻辑
- 所有方法都声明为纯虚函数,接口本身不能实例化
- 必须定义虚析构函数,避免派生类对象析构时出现内存泄漏
- 接口命名要清晰,体现实现层的职责,比如这里的MessageSender明确表示消息发送能力
具体实现类设计方法
具体实现类需要继承实现类接口,重写接口中定义的纯虚函数,完成具体场景下的功能实现。每个具体实现类对应实现层的一个具体扩展维度,彼此之间相互独立,新增实现类不会影响其他实现类或抽象层。
我们继续以上面的消息发送系统为例,实现两个不同平台的消息发送具体类:
// 具体实现类:邮件发送实现
class EmailSender : public MessageSender {
public:
void send(const std::string& content) override {
// 模拟邮件发送逻辑
std::cout << "通过邮件发送消息,内容:" << content << std::endl;
}
};
// 具体实现类:短信发送实现
class SmsSender : public MessageSender {
public:
void send(const std::string& content) override {
// 模拟短信发送逻辑
std::cout << "通过短信发送消息,内容:" << content << std::endl;
}
};
具体实现类的设计注意点:
- 必须完整重写实现类接口中的所有纯虚函数,否则类仍然是抽象类,无法实例化
- 每个具体实现类只负责一个具体的实现场景,符合单一职责原则
- 实现类的逻辑可以独立修改,只要不改变接口定义,就不会影响上层抽象的使用
抽象接口与扩充抽象类设计
抽象接口层需要持有实现类接口的指针或引用,通过组合的方式关联实现层,而不是通过继承关联。抽象接口定义上层业务的操作方法,内部调用实现类接口的方法完成具体功能。
继续我们的消息发送系统示例,抽象接口定义消息的类型,内部持有消息发送器的引用:
// 抽象接口:消息抽象类
class Message {
protected:
// 持有实现类接口的指针,通过组合关联实现层
MessageSender* sender;
public:
// 构造函数传入具体的实现类对象
Message(MessageSender* s) : sender(s) {}
// 定义抽象的发送消息方法
virtual void sendMessage(const std::string& content) = 0;
virtual ~Message() {
// 这里不负责释放sender,释放逻辑由外部管理,避免双重释放问题
}
};
扩充抽象类对抽象接口进行扩展,实现具体的业务逻辑:
// 扩充抽象类:普通消息
class CommonMessage : public Message {
public:
CommonMessage(MessageSender* s) : Message(s) {}
void sendMessage(const std::string& content) override {
// 普通消息直接发送,调用实现层的send方法
sender->send(content);
}
};
// 扩充抽象类:加急消息
class UrgentMessage : public Message {
public:
UrgentMessage(MessageSender* s) : Message(s) {}
void sendMessage(const std::string& content) override {
// 加急消息添加前缀后发送
std::string urgentContent = "【加急】" + content;
sender->send(urgentContent);
}
};
桥接模式完整使用示例
我们将上面的接口和实现类组合起来,展示桥接模式的使用方式:
#include <iostream>
#include <string>
// 实现类接口
class MessageSender {
public:
virtual void send(const std::string& content) = 0;
virtual ~MessageSender() = default;
};
// 具体实现类:邮件发送
class EmailSender : public MessageSender {
public:
void send(const std::string& content) override {
std::cout << "通过邮件发送消息,内容:" << content << std::endl;
}
};
// 具体实现类:短信发送
class SmsSender : public MessageSender {
public:
void send(const std::string& content) override {
std::cout << "通过短信发送消息,内容:" << content << std::endl;
}
};
// 抽象接口
class Message {
protected:
MessageSender* sender;
public:
Message(MessageSender* s) : sender(s) {}
virtual void sendMessage(const std::string& content) = 0;
virtual ~Message() {}
};
// 扩充抽象类:普通消息
class CommonMessage : public Message {
public:
CommonMessage(MessageSender* s) : Message(s) {}
void sendMessage(const std::string& content) override {
sender->send(content);
}
};
// 扩充抽象类:加急消息
class UrgentMessage : public Message {
public:
UrgentMessage(MessageSender* s) : Message(s) {}
void sendMessage(const std::string& content) override {
std::string urgentContent = "【加急】" + content;
sender->send(urgentContent);
}
};
int main() {
// 创建具体实现类对象
MessageSender* emailSender = new EmailSender();
MessageSender* smsSender = new SmsSender();
// 组合抽象类和实现类,普通消息用邮件发送
Message* commonEmailMsg = new CommonMessage(emailSender);
commonEmailMsg->sendMessage("本周周报已提交");
// 加急消息用短信发送
Message* urgentSmsMsg = new UrgentMessage(smsSender);
urgentSmsMsg->sendMessage("服务器出现异常");
// 释放内存
delete commonEmailMsg;
delete urgentSmsMsg;
delete emailSender;
delete smsSender;
return 0;
}
设计注意事项
在实际使用C++设计桥接模式的接口和实现类时,还需要注意以下几点:
- 抽象层持有的实现类引用建议使用智能指针管理,避免手动管理内存带来的泄漏风险,比如可以用
std::unique_ptr<MessageSender>替代裸指针 - 实现类接口的方法设计要足够稳定,一旦确定后尽量不要修改,否则所有具体实现类都需要同步修改
- 如果两个维度的扩展需求并不明确,不需要强行使用桥接模式,避免增加不必要的设计复杂度
- 抽象层的业务方法可以根据需求添加,只要不直接依赖具体实现类的逻辑即可,保持抽象层的可扩展性
桥接模式与适配器模式的区别
很多开发者容易混淆桥接模式和适配器模式,两者的核心区别如下:
| 对比维度 | 桥接模式 | 适配器模式 |
|---|---|---|
| 设计目的 | 分离抽象与实现,让两者独立扩展 | 将一个类的接口转换成客户端期望的接口,解决接口不匹配问题 |
| 关系性质 | 抽象层与实现层是平等的组合关系,设计阶段就确定 | 适配器与被适配者是事后补救的关系,通常是已有类接口不兼容时使用 |
| 接口设计 | 实现类接口是专门为桥接模式设计的,和抽象层接口匹配 | 被适配者的接口是已有的,适配器需要适配已有接口 |
桥接模式的接口与实现类设计核心在于分离职责,实现层提供基础能力,抽象层基于基础能力组合出上层业务,两者通过组合关联,最终实现两个维度的独立扩展,这也是桥接模式相比多继承方案的核心优势。