
前言桥接模式Bridge pattern要解决的问题是「两个维度同时变化导致的类爆炸」。典型例子是一个遥控器要控制多种设备同时又要有普通遥控器和高级遥控器两种形态。如果按继承来做就会得到「普通电视遥控器」「高级电视遥控器」「普通收音机遥控器」「高级收音机遥控器」四个类每增加一种设备或一种遥控器类的数量按乘法增长。桥接模式的做法是把这两个维度拆开遥控器这一侧叫抽象部分Abstraction设备这一侧叫实现部分Implementor。抽象部分持有一个实现部分的指针把具体工作委托给它。两个维度各自用继承扩展类的数量从 M×N 降为 MN。常见的误解有两个。第一把桥接和策略模式混为一谈——两者的类图很像都是一个对象持有另一个接口但意图不同策略是运行期替换算法桥接是让两个维度独立演化通常在设计中就固定了组合关系。第二以为只要用了「组合代替继承」就是桥接——桥接的关键是两个维度都能独立扩展如果只有一侧会变那只是普通的组合或策略。本文用遥控器与设备的例子讲结构再给出 C17 的完整实现最后与策略、适配器对比说明什么时候值得引入桥接、什么时候是过度设计。一、继承方案的问题假设有 2 种设备电视、收音机和 2 种遥控器普通、高级用继承实现RemoteControl├── TVRemote│ ├── BasicTVRemote│ └── AdvancedTVRemote└── RadioRemote├── BasicRadioRemote└── AdvancedRadioRemote4 个叶子类。如果设备增加到 5 种、遥控器增加到 3 种就需要 15 个类每加一种设备所有遥控器形态都要再写一遍。这种结构的问题不是代码重复这么简单而是两个本应独立的需求被绑死在一起遥控器的「高级功能」和设备的「具体型号」在编译期就固定了组合。桥接把继承树拆成两棵Abstraction遥控器 Implementor设备├── RemoteControl ├── TV└── AdvancedRemote └── Radio│└── 持有 Implementor*遥控器不认识「电视」或「收音机」只认识Device接口设备也不认识遥控器只提供开关、音量等原语。组合发生在运行时通过构造函数注入。二、参与角色角色职责本例Abstraction定义高层的控制接口持有 Implementor 的引用RemoteControlRefinedAbstraction扩展抽象接口不直接实现底层细节AdvancedRemoteImplementor定义实现类的接口通常是纯虚函数DeviceConcreteImplementor实现具体平台/设备的操作TV、RadioClient把具体的 Implementor 注入 Abstractionmain关键点在于Abstraction 中的方法不直接做事而是把调用转发给 Implementor。RemoteControl::volumeUp()不知道音量是怎么调的它只调用device_-setVolume(...)。这样新增设备只要实现Device新增遥控器只要继承RemoteControl两边互不影响。三、完整实现// 适用于 C17#include algorithm#include iostream#include memory#include string#include utility// 实现部分设备class Device {public:virtual ~Device() default;virtual void enable() 0;virtual void disable() 0;virtual void setVolume(int percent) 0;virtual int volume() const 0;virtual std::string name() const 0;};class TV : public Device {public:void enable() override { on_ true; }void disable() override { on_ false; }void setVolume(int percent) override {volume_ std::max(0, std::min(100, percent));}int volume() const override { return volume_; }std::string name() const override { return on_ ? TV(on) : TV(off); }private:bool on_ false;int volume_ 30;};class Radio : public Device {public:void enable() override { on_ true; }void disable() override { on_ false; }void setVolume(int percent) override {volume_ std::max(0, std::min(100, percent));}int volume() const override { return volume_; }std::string name() const override { return on_ ? Radio(on) : Radio(off); }private:bool on_ false;int volume_ 50;};设备接口的析构函数必须是virtual。客户端通过std::unique_ptrDevice持有对象销毁时经基类指针调用析构函数如果基类析构不是虚函数通过基类指针delete派生对象是未定义行为undefined behavior, UB。接着是抽象部分// 适用于 C17class RemoteControl {public:explicit RemoteControl(std::unique_ptrDevice device): device_(std::move(device)) {}virtual ~RemoteControl() default;RemoteControl(const RemoteControl) delete;RemoteControl operator(const RemoteControl) delete;void togglePower() {if (on_) {device_-disable();on_ false;} else {device_-enable();on_ true;}}void volumeUp() {device_-setVolume(std::min(100, device_-volume() 10));}void volumeDown() {device_-setVolume(std::max(0, device_-volume() - 10));}virtual std::string status() const {return device_-name() vol std::to_string(device_-volume());}protected:Device device() { return *device_; }private:std::unique_ptrDevice device_;bool on_ false;};class AdvancedRemote : public RemoteControl {public:using RemoteControl::RemoteControl; // 继承构造函数void mute() {device().setVolume(0);}std::string status() const override {return Advanced: RemoteControl::status();}};RemoteControl持有std::unique_ptrDevice表示它对设备的独占所有权。拷贝构造被删除因为unique_ptr不可拷贝如果确实需要复制遥控器应当想清楚复制后是否共享同一个设备这属于业务语义不能靠默认行为决定。AdvancedRemote用using RemoteControl::RemoteControl;继承基类构造函数这样AdvancedRemote r(std::make_uniqueTV());可以直接构造不必手写转发构造函数。继承来的构造函数只初始化基类部分AdvancedRemote没有额外的数据成员所以够用如果它有自己的成员需要在类内给默认初始化或另写构造函数。device()是 protected 的访问器供派生类调用底层原语。mute()直接用setVolume(0)而不是调用volumeDown()若干次避免依赖步长。使用// 适用于 C17int main() {RemoteControl basic(std::make_uniqueTV());basic.togglePower();basic.volumeUp();std::cout basic.status() \n;AdvancedRemote advanced(std::make_uniqueRadio());advanced.togglePower();advanced.mute();std::cout advanced.status() \n;return 0;}客户端只依赖RemoteControl、AdvancedRemote、Device及其实现。要新增一种设备例如Projector只需实现Device接口要新增一种遥控器例如带定时功能的TimedRemote只需继承RemoteControl。两边都不需要修改对方的代码类的数量按加法增长。四、与策略、适配器的区别模式目的结构变化维度桥接让抽象与实现独立演化抽象持有实现接口两个维度设计期确定策略运行期替换算法上下文持有策略接口一个维度运行期切换适配器让不兼容接口协同工作包装已有对象转换接口不改被适配者抽象工厂创建一系列相关对象工厂接口 具体工厂产品族桥接和策略在代码层面几乎一样都是「A 持有 B 的指针调用转发给 B」。区别在意图和变化的方向。策略强调「同一个任务有多种算法可以在运行时换」桥接强调「有两套继承体系各自扩展互不依赖」。如果只有一个维度会变化用策略就够了硬套桥接会增加一层不必要的抽象。适配器则是「事后补救」两个已有接口不兼容用一个包装类转换。桥接是「事先设计」预见到两个维度都会扩展提前拆开。把适配器当成桥接或者把桥接当成适配器都会让设计意图变得模糊。五、代价与适用条件桥接不是没有成本多了一层间接调用RemoteControl的方法要转发给Device调用链变长。编译器在能看到具体类型时可能内联但通过基类指针的虚调用通常无法完全消除是否可接受要看性能要求。接口设计更难。Device提供哪些原语决定了遥控器能实现什么功能原语太少高级遥控器做不了复杂操作原语太多又等于把具体设备的细节泄漏到抽象层。增加了类的数量。虽然从乘法变成加法但仍然是两套继承体系加一个组合关系简单场景下反而更难读。适用条件可以概括为确实存在两个独立变化的维度且两个维度都需要扩展。如果只有一个维度会变或者变化是运行期的算法替换优先考虑策略如果只是想把接口和实现分开可以先问「实现是否真的会独立演化」答案是否定的话普通虚函数加继承就够了。常见坑点1. Implementor 析构不是虚函数❌class Device { public: ~Device() default; ... };通过Device*删除派生对象是 UB。 ✅virtual ~Device() default;。2. 抽象部分拷贝导致所有权混乱❌RemoteControl持有裸指针默认拷贝构造让两个遥控器指向同一设备析构时重复释放。 ✅ 用unique_ptr并禁用拷贝或明确实现共享语义shared_ptr。3. 在抽象部分直接 new 具体实现❌RemoteControl() : device_(new TV) {}—— 抽象重新耦合到具体设备桥接失去意义。 ✅ 由构造函数参数注入客户端决定组合。4. 把桥接写成策略❌ 只有一个「算法」维度却硬拆出抽象与实现两套继承。 ✅ 单维度用策略只有两个维度都独立变化时才用桥接。5. 实现接口泄漏具体类型❌ 在RemoteControl里dynamic_castTV*(device_.get())做特判。 ✅ 把差异抽象成Device的虚函数必须特判说明接口设计不完整。6. 派生类忘记转发或状态不同步❌AdvancedRemote::mute()直接改自己的成员没有调用device().setVolume(0)。 ✅ 所有底层操作都通过 Implementor 接口完成。7. 用裸指针且不写所有权❌Device* device_;构造时赋值析构时不释放或释放了但调用方还在用。 ✅std::unique_ptrDevice表达独占需要共享时用std::shared_ptr并写清生命周期。8. 过度设计❌ 只有一种设备和一种遥控器却引入四个角色和两层继承。 ✅ 先写简单实现等第二个维度真的出现再重构模式是应对变化的手段不是目的。总结角色本例职责AbstractionRemoteControl高层控制接口持有设备RefinedAbstractionAdvancedRemote扩展功能仍通过设备接口实现ImplementorDevice定义开关、音量等原语ConcreteImplementorTV、Radio具体设备的实现Clientmain组装遥控器与设备桥接模式的核心不是「组合代替继承」这句口号而是识别出两个会独立演化的维度并把它们之间的依赖从编译期移到运行期的组合。识别错了维度或者只有一个维度会变引入桥接只会增加抽象层。判断标准很实际新增一种设备时是否需要修改遥控器代码新增一种遥控器时是否需要修改设备代码两个答案都是「不需要」桥接才真正发挥作用。