桥模式
软件组件的设计中,如果责任划分的不清晰,使继承得到的结果往往是随着需求的变化,子类急剧膨胀,同时充斥着重复代码。
动机
由于某些固定的固有实现逻辑,使得它们具有两个变化的维度,乃至多个维度的变化。
如何应对”多维度的变化“?如何利用面对对象关系技术来使得类型可以轻松沿着两个乃至更多的方向变化而不引入额外的复杂度。
定义
将抽象部分(业务功能)与实现部分(平台实现)分离,是它们都可以独立变化。
示例代码
class Messager {
public:
// 业务功能
virtual void Login(string username, string password) = 0;
virtual void SendMessage(string message) = 0;
virtual void SendPicture(Image image) = 0;
// 功能实现
virtual void PlaySound() = 0;
virtual void DrawShape() = 0;
virtual void WriteText() = 0;
virtual void Connect() = 0;
virtual ~Messager() {}
};
假设现在我们有这样一个 Messager 基类,其中定义了业务功能相关的接口,还有功能实现的接口。现在我们要基于不同的平台来进行实现,例如 PC 端和手机端,其中主要是实现了功能实现的接口:
// 平台实现
class PCMessagerBase : public Messager {
public:
virtual void PlaySound() { // ... }
virtual void DrawShape() { // ... }
virtual void WriteText() { // ... }
virtual void Connect() { // ... }
};
class MobileMessagerBase : public Messager {
public:
virtual void PlaySound() { // === }
virtual void DrawShape() { // === }
virtual void WriteText() { // === }
virtual void Connect() { // === }
};
而在业务抽象上,我们又需要提供不同的版本,精简版或者是高配版,以 PC 端为例:
class PCMessagerLite : public PCMessagerBase {
public:
virtual void Login(string username, string password){
PCMessagerBase::Connect();
// ........
}
virtual void SendMessage(string message){
PCMessagerBase::WriteText();
// ........
}
virtual void SendPicture(Image image){
PCMessagerBase::DrawShape();
// ........
}
};
class PCMessagerPerfect : public PCMessagerBase {
public:
virtual void Login(string username, string password){
PCMessagerBase::PlaySound();
//*********
PCMessagerBase::Connect();
// ........
}
virtual void SendMessage(string message){
PCMessagerBase::PlaySound();
//*********
PCMessagerBase::WriteText();
// ........
}
virtual void SendPicture(Image image){
PCMessagerBase::PlaySound();
//*********
PCMessagerBase::DrawShape();
// ........
}
};
PCMessagerPerfect 类相当于 PCMessagerLite 的高配版,接口里实现了更丰富的内容。MobileMessagerLite 和 MobileMessagerPerfect 的子类,这里也会出现子类膨胀的问题。有 n 个平台,每个平台有 m 个版本,那么最后就会有 1+n+m*n 个类需要写。然后使用的角度就是:
void Process() {
// 编译时装配
Messager *m = new PCMessagerPerfect();
}
根据前面装饰模式中描述的,一个有效防止子类膨胀的方式是就是继承转组合。下面以各个平台的 Lite 版本为例:
class PCMessagerLite {
PCMessagerBase* message; // 可以跟 MobileMessagerBase 使用父类表示
public:
virtual void Login(string username, string password){
message->Connect();
// ........
}
virtual void SendMessage(string message){
message->WriteText();
// ........
}
virtual void SendPicture(Image image){
message->DrawShape();
// ........
}
};
class MobileMessagerLite {
MobileMessagerBase* message;
public:
virtual void Login(string username, string password){
message->Connect();
// ........
}
virtual void SendMessage(string message){
message->WriteText();
// ........
}
virtual void SendPicture(Image image){
message->DrawShape();
// ........
}
};
可以看到改完之后两个的形式几乎一模一样,就是差在 message 成员变量上,它们又同属于一个基类,所以两个可以合二为一:
class MessagerLite {
Messager* message;
public:
MessagerLite(Messager *m) : message(m) {}
virtual void Login(string username, string password){
message->Connect();
// ........
}
virtual void SendMessage(string message){
message->WriteText();
// ........
}
virtual void SendPicture(Image image){
message->DrawShape();
// ........
}
};
同样的,这里为了虚函数接口规范,MessagerLite 需要继承一个接口基类。在 Decorator 装饰模式中,可以直接继承基类 Messager,但是这里因为业务只实现了部分虚基类接口,而功能实现的子类也是子实现了部分虚函数接口,所以直接继承就不是很合适。这时需要做的就是拆分原先的 Messager 基类,实现与业务拆成两个类。
class Messager {
public:
// 业务功能
virtual void Login(string username, string password) = 0;
virtual void SendMessage(string message) = 0;
virtual void SendPicture(Image image) = 0;
virtual ~Messager() {}
};
class MessagerImp {
public:
virtual void PlaySound() = 0;
virtual void DrawShape() = 0;
virtual void WriteText() = 0;
virtual void Connect() = 0;
virtual ~MessagerImp() {}
};
MessagerLite 类就可以放心地继承 Messager 基类了。最后修改完的代码就是:
class Messager {
MessagerImp* messageImp; // 子类上移
public:
Messager(MessagerImp* imp): messageImp(imp){}
// 业务功能
virtual void Login(string username, string password) = 0;
virtual void SendMessage(string message) = 0;
virtual void SendPicture(Image image) = 0;
virtual ~Messager() {}
};
class MessagerImp {
public:
virtual void PlaySound() = 0;
virtual void DrawShape() = 0;
virtual void WriteText() = 0;
virtual void Connect() = 0;
virtual ~MessagerImp() {}
};
class PCMessagerImp : public MessagerImp {
public:
virtual void PlaySound() { // ... }
virtual void DrawShape() { // ... }
virtual void WriteText() { // ... }
virtual void Connect() { // ... }
};
class MobileMessagerImp : public MessagerImp {
public:
virtual void PlaySound() { // === }
virtual void DrawShape() { // === }
virtual void WriteText() { // === }
virtual void Connect() { // === }
};
class MessagerLite : public Messager {
// MessagerImp* messageImp; 跟 MessagerPerfect 可以共用,上移到父类
public:
virtual void Login(string username, string password){
messageImp->Connect();
// ........
}
virtual void SendMessage(string message){
messageImp->WriteText();
// ........
}
virtual void SendPicture(Image image){
messageImp->DrawShape();
// ........
}
};
class MessagerPerfect : public Messager {
// MessagerImp* message; 跟 MessagerLite 可以共用,上移到父类
public:
virtual void Login(string username, string password){
messageImp->PlaySound();
//*********
messageImp->Connect();
// ........
}
virtual void SendMessage(string message){
messageImp->PlaySound();
//*********
messageImp->WriteText();
// ........
}
virtual void SendPicture(Image image){
messageImp->PlaySound();
//*********
messageImp->DrawShape();
// ........
}
};
从使用的角度:
void Process() {
// 运行时装配
MessagerImp *imp = new PCMessagerImp();
Messager *m = new MessagerPerfect(imp);
}
以上就是桥模式的示例过程。经过桥模式的修改,这里的类数就由原来的 1+ n + m*n 变成了 1+n+m 了。
结构图
![](https://img-blog.csdnimg.cn/a9998567679f4a6989b6742f5a4c9682.png)
总结
-
看过前面 Decorator 装饰模式的同学肯定对这部分内容很熟悉,重构手法都是类似,只是这里不同的改变方向都被放在了同一个基类里,导致没办法直接继承,需要有一个拆分基类的过程,然后再分别继承,同样进行装配。
-
Bridge 桥模式是引用"对象间组合关系"(组合)解耦了抽象和实现之间固有的绑定关系,是的抽象和实现可以沿着各自的维度来变化,即分别子类化它们。
-
Bridge 桥模式类似于多继承方案,但多继承往往违背了单一职责原则,复用性比较差,这里采用组合的方式是比多继承更好的解决方法。
-
Bridge 桥模式应用在两个非常强的变化维度上,有时一个类也有多余两个的变换维度,这时就可以用 Bridge 的扩展模式。
其他设计模式汇总:
[设计模式] —— 设计模式的介绍及分类