设计模式8-Bridge 桥模式
- 由来与目的
- 模式定义
- 结构
- 代码推导
- 1. 类和接口的定义
- 2. 平台实现
- 3. 业务抽象
- 4. 使用示例
- 总结
- 1. 类数量过多,复杂度高
- 2. 代码重复
- 3. 不符合单一职责原则
- 4. 缺乏扩展性
- 改进后的设计
- 1. 抽象和实现分离(桥接模式)
- 2. 抽象类
- 3. 使用装饰者模式添加功能
- 4. 使用示例
- 改进后的优点
- 疑问
- 1. `protected` 访问级别的意义
- 2. `protected` 的使用场景
- 具体分析代码中的选择
- 1. `protected` 访问级别的好处
- 2. 如果使用`private`
- 3. 如果使用`public`
- 总结
- 要点总结
桥模式也属于单一职责模式中的一种。
由来与目的
桥模式的由来以及目的:由于某些类型的固有的实现逻辑使他们具有两个变化的维度乃至多个维度的变化。那么此时如何应对这种多维度的变化,如何利用面向对象技术?来使得类型可以轻松地沿着两个乃至多个方向进行变化,而不引入额外的复杂度呢?那么乔模式就应运而生,他的存在就是为了解决此类问题。
模式定义
将抽象部分也就是业务功能,与实现部分分离,使他们都可以独立的变化。
结构
代码推导
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(){}
};//平台实现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(){//==========}
};//业务抽象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();//........}
};class MobileMessagerLite : public MobileMessagerBase {
public:virtual void Login(string username, string password){MobileMessagerBase::Connect();//........}virtual void SendMessage(string message){MobileMessagerBase::WriteText();//........}virtual void SendPicture(Image image){MobileMessagerBase::DrawShape();//........}
};class MobileMessagerPerfect : public MobileMessagerBase {
public:virtual void Login(string username, string password){MobileMessagerBase::PlaySound();//********MobileMessagerBase::Connect();//........}virtual void SendMessage(string message){MobileMessagerBase::PlaySound();//********MobileMessagerBase::WriteText();//........}virtual void SendPicture(Image image){MobileMessagerBase::PlaySound();//********MobileMessagerBase::DrawShape();//........}
};void Process(){//编译时装配Messager *m =new MobileMessagerPerfect();
}
这段代码实现了一个跨平台的消息传递系统,其中包含了多个层次的抽象和具体实现。它通过将平台相关的功能和业务逻辑分离来实现不同的消息传递方式。
1. 类和接口的定义
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
是一个抽象基类,定义了登录、发送消息、发送图片以及平台相关的功能(播放声音、绘制图形、写文本、连接)。
2. 平台实现
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() {//==========}
};
PCMessagerBase
和 MobileMessagerBase
是平台相关的具体实现类,分别实现了 PC 和移动平台上的功能。这两个类分别实现了播放声音、绘制图形、写文本和连接的方法。
3. 业务抽象
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();//........}
};class MobileMessagerLite : public MobileMessagerBase {
public:virtual void Login(string username, string password) {MobileMessagerBase::Connect();//........}virtual void SendMessage(string message) {MobileMessagerBase::WriteText();//........}virtual void SendPicture(Image image) {MobileMessagerBase::DrawShape();//........}
};class MobileMessagerPerfect : public MobileMessagerBase {
public:virtual void Login(string username, string password) {MobileMessagerBase::PlaySound();//********MobileMessagerBase::Connect();//........}virtual void SendMessage(string message) {MobileMessagerBase::PlaySound();//********MobileMessagerBase::WriteText();//........}virtual void SendPicture(Image image) {MobileMessagerBase::PlaySound();//********MobileMessagerBase::DrawShape();//........}
};
这些类继承自平台实现类,并且在每个方法中调用了相应的基类方法,添加了业务逻辑。具体而言,PCMessagerLite
和 MobileMessagerLite
是简化版的实现,而 PCMessagerPerfect
和 MobileMessagerPerfect
则是带有更多功能(如播放声音)的完整实现。
4. 使用示例
void Process() {//编译时装配Messager *m = new MobileMessagerPerfect();
}
Process
函数展示了如何创建一个 MobileMessagerPerfect
实例并赋值给 Messager
指针。
总结
这段代码通过将不同平台的实现和业务逻辑分开,展示了如何使用继承和多态来实现跨平台的消息传递系统。它遵循了开闭原则(对扩展开放,对修改关闭),使得添加新的平台或功能变得更容易。具体地,Messager
定义了接口,PCMessagerBase
和 MobileMessagerBase
实现了平台相关的功能,而 PCMessagerLite
、PCMessagerPerfect
、MobileMessagerLite
和 MobileMessagerPerfect
实现了具体的业务逻辑。
这段代码虽然展示了一个灵活的跨平台消息传递系统的设计,但仍然存在一些缺陷和改进空间。以下是一些主要的缺陷及其解决方案:
1. 类数量过多,复杂度高
缺陷:每添加一个新功能或新平台,都需要创建大量新的类,导致类数量爆炸,假设此时平台类数量为n ,平台实现功能类数量为m ,那么此时类的数量为1+n+m*n,增加了代码的复杂性和维护成本。
解决方案:可以使用桥接模式(Bridge Pattern),将抽象和实现分离,从而减少类的数量和复杂度。
2. 代码重复
缺陷:许多方法在不同类中有重复的实现,如 PlaySound()
、DrawShape()
、WriteText()
和 Connect()
方法在不同类中基本相同。
解决方案:将这些方法提取到一个单独的类中,或者使用组合而不是继承来减少代码重复。
3. 不符合单一职责原则
缺陷:Messager
类同时处理平台相关的实现和业务逻辑,违背了单一职责原则,使得类的职责不清晰。
解决方案:将平台相关的实现和业务逻辑分离,使用桥接模式或策略模式将这些职责分开。
4. 缺乏扩展性
缺陷:添加新功能(如加密、压缩等)需要创建大量新的类,缺乏灵活性。
解决方案:可以使用装饰者模式(Decorator Pattern)来动态地为对象添加职责,而不需要创建大量的子类。
改进后的设计
使用桥接模式和装饰者模式重新设计:
1. 抽象和实现分离(桥接模式)
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:void PlaySound() override {// PC平台实现}void DrawShape() override {// PC平台实现}void WriteText() override {// PC平台实现}void Connect() override {// PC平台实现}
};class MobileMessagerImp : public MessagerImp {
public:void PlaySound() override {// 移动平台实现}void DrawShape() override {// 移动平台实现}void WriteText() override {// 移动平台实现}void Connect() override {// 移动平台实现}
};
2. 抽象类
class Messager {
protected:MessagerImp* imp;
public:Messager(MessagerImp* imp) : imp(imp) {}virtual void Login(string username, string password) = 0;virtual void SendMessage(string message) = 0;virtual void SendPicture(Image image) = 0;virtual ~Messager() {}
};class MessagerLite : public Messager {
public:MessagerLite(MessagerImp* imp) : Messager(imp) {}void Login(string username, string password) override {imp->Connect();// Lite版特有的登录逻辑}void SendMessage(string message) override {imp->WriteText();// Lite版特有的发送消息逻辑}void SendPicture(Image image) override {imp->DrawShape();// Lite版特有的发送图片逻辑}
};class MessagerPerfect : public Messager {
public:MessagerPerfect(MessagerImp* imp) : Messager(imp) {}void Login(string username, string password) override {imp->PlaySound();imp->Connect();// Perfect版特有的登录逻辑}void SendMessage(string message) override {imp->PlaySound();imp->WriteText();// Perfect版特有的发送消息逻辑}void SendPicture(Image image) override {imp->PlaySound();imp->DrawShape();// Perfect版特有的发送图片逻辑}
};
3. 使用装饰者模式添加功能
class MessagerDecorator : public Messager {
protected:Messager* messager;
public:MessagerDecorator(Messager* messager) : Messager(messager->imp), messager(messager) {}void Login(string username, string password) override {messager->Login(username, password);}void SendMessage(string message) override {messager->SendMessage(message);}void SendPicture(Image image) override {messager->SendPicture(image);}
};class EncryptedMessager : public MessagerDecorator {
public:EncryptedMessager(Messager* messager) : MessagerDecorator(messager) {}void Login(string username, string password) override {// 加密登录MessagerDecorator::Login(username, password);}void SendMessage(string message) override {// 加密消息MessagerDecorator::SendMessage(message);}void SendPicture(Image image) override {// 加密图片MessagerDecorator::SendPicture(image);}
};
4. 使用示例
void Process() {MessagerImp* pcImp = new PCMessagerImp();Messager* m = new EncryptedMessager(new MessagerPerfect(pcImp));m->Login("user", "password");m->SendMessage("Hello, World!");// Clean updelete m;delete pcImp;
}
改进后的优点
- 减少类数量:通过桥接模式,将平台相关实现和业务逻辑分离,减少了类的数量,此时类的数量为(1+n+m) < (1+n+m*n)。
- 减少代码重复:将公共方法提取到实现类中,减少了代码重复。
- 职责清晰:将平台相关实现和业务逻辑分离,符合单一职责原则。
- 增加扩展性:通过装饰者模式,能够动态地添加新功能,而无需创建大量新的子类。
通过这些改进,可以显著提高代码的可维护性、可扩展性和清晰性。
疑问
在面向对象编程中,选择成员变量的访问级别时,需要考虑类的设计和使用场景。在这段代码中,Messager
类的imp
成员变量被声明为protected
,而不是private
或者public
,这是有特定原因的。
1. protected
访问级别的意义
protected
访问级别允许子类访问和修改基类中的成员变量,同时对类外部保持不可见。这样可以在保证类的封装性的同时,允许子类进行适当的扩展和修改。
2. protected
的使用场景
在这段代码中,Messager
类的设计是为了作为一个基类,它的主要作用是定义接口和提供基础的实现,而具体的实现细节由其子类来完成。因此,将imp
成员变量设为protected
可以让子类方便地访问和操作这个变量,而无需通过公共接口来访问。
具体分析代码中的选择
1. protected
访问级别的好处
class Messager {
protected:MessagerImp* imp;
public:Messager(MessagerImp* imp) : imp(imp) {}virtual void Login(string username, string password) = 0;virtual void SendMessage(string message) = 0;virtual void SendPicture(Image image) = 0;virtual ~Messager() {}
};class MessagerLite : public Messager {
public:MessagerLite(MessagerImp* imp) : Messager(imp) {}void Login(string username, string password) override {imp->Connect();// Lite版特有的登录逻辑}void SendMessage(string message) override {imp->WriteText();// Lite版特有的发送消息逻辑}void SendPicture(Image image) override {imp->DrawShape();// Lite版特有的发送图片逻辑}
};
在这种设计下,MessagerLite
和其他子类可以直接访问imp
,这使得子类可以轻松地调用平台相关的实现方法,如Connect()
、WriteText()
和DrawShape()
。
2. 如果使用private
如果将imp
设为private
,子类将无法直接访问imp
,需要通过公共的getter和setter方法。这增加了额外的复杂性,而且在某些情况下可能会影响性能和代码的可读性。
class Messager {
private:MessagerImp* imp;
public:Messager(MessagerImp* imp) : imp(imp) {}MessagerImp* getImp() { return imp; }virtual void Login(string username, string password) = 0;virtual void SendMessage(string message) = 0;virtual void SendPicture(Image image) = 0;virtual ~Messager() {}
};class MessagerLite : public Messager {
public:MessagerLite(MessagerImp* imp) : Messager(imp) {}void Login(string username, string password) override {getImp()->Connect();// Lite版特有的登录逻辑}void SendMessage(string message) override {getImp()->WriteText();// Lite版特有的发送消息逻辑}void SendPicture(Image image) override {getImp()->DrawShape();// Lite版特有的发送图片逻辑}
};
3. 如果使用public
如果将imp
设为public
,则任何外部代码都可以直接访问和修改imp
,这违反了封装原则,容易导致错误和不必要的依赖。
class Messager {
public:MessagerImp* imp;Messager(MessagerImp* imp) : imp(imp) {}virtual void Login(string username, string password) = 0;virtual void SendMessage(string message) = 0;virtual void SendPicture(Image image) = 0;virtual ~Messager() {}
};// 外部代码可以直接访问 imp
Messager* m = new MessagerLite(new PCMessagerImp());
m->imp->Connect(); // 不推荐
总结
将MessagerImp* imp
设为protected
是为了在保持封装性的同时,让子类能够方便地访问和使用该成员变量。这种设计既保证了基类和子类之间的良好协作,又避免了将实现细节暴露给类的外部,提高了代码的可维护性和扩展性。
要点总结
- 模式使用对象间的组合关系解耦合了。抽象和现实之间固有的绑定关系,使得抽象可实现可以沿着各自的维度来变化所,所谓抽象和实现沿着各自维度的变化,即子类化他们
- 强模式有时候类似于多继承方案。但是多继承方案往往违背单一职责,原则即一个类只有一个变化的原因,复用性比较差。桥模式是比多继承方案更好的解决方法。
- 乔模桥模式的应用一般在两个非常强的变化维度,有时一个内页。有。多于两个的变化维度,这时可以使用桥的扩展模式。