- 由于某些类型的固有的实现逻辑,使得它们具有两个变化的维度,乃至多个纬度的变化。
- 如何应对这种“多维度的变化”?如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化,而不引入额外的复杂度?
将抽象部分(业务功能)与实现部分(平台实现)分离,使它们都可以独立地变化。 ——《设计模式》GoF
解决了子类快速膨胀的场景,在功能存在多维度扩展设计场景下类的功能不具有单一职责的问题
1、抽象(Abstraction): 定义了客户使用的接口,维护一个指向实现化对象的引用。
class MessagerImp{
public:
virtual void PlaySound()=0;
virtual void DrawShape()=0;
virtual void WriteText()=0;
virtual void Connect()=0;
MessagerImp(){}
};
2、扩展抽象(Refined Abstraction): 扩展抽象类,添加了更多的业务方法。
class PCMessagerImp : public MessagerImp{
public:
virtual void PlaySound(){
//**********
}
virtual void DrawShape(){
//**********
}
virtual void WriteText(){
//**********
}
virtual void Connect(){
//**********
}
};
3、实现化(Implementor): 定义了实现化的接口,它不被抽象类直接使用,而是仅作为扩展抽象类的一部分。
class Messager
{
protected:
MessagerImp *messagerImp; //...
public:
virtual void Login(string username, string password) = 0;
virtual void SendMessage(string message) = 0;
virtual void SendPicture(Image image) = 0;
Messager(MessagerImp *pMessImp)
: messagerImp(pMessImp)
{
}
virtual ~Messager() {}
};
4、具体实现化(Concrete Implementor): 实现化接口的具体类,包含了实现化的具体业务。
class MessagerLite : public Messager
{
public:
MessagerLite(MessagerImp *pMess)
: Messager(pMess)
{
}
virtual void Login(string username, string password)
{
messagerImp->Connect();
//........
}
virtual void SendMessage(string message)
{
messagerImp->WriteText();
//........
}
virtual void SendPicture(Image image)
{
messagerImp->DrawShape();
//........
}
};
5、使用实例
void Process(){
//运行时装配
MessagerImp* mImp=new PCMessagerImp();
Messager *m =new MessagerLite(mImp);
}
通过实现抽象类或者抽象接口来实现不同维度的扩展,使用时进行组合装配
在实际项目中,桥接模式(Bridge Pattern)可以应用于多种场景,尤其是当需要将一个类的抽象部分与它的实现部分分离,以便它们可以独立地变化和扩展时。以下是一些具体的应用实例:
1、图形界面库开发:
在开发图形界面库时,可以使用桥接模式将界面的绘制(抽象部分)与具体的控件类型(实现部分)分离。这样,增加新的控件类型或改变绘制方式时,不需要修改已有的代码。 支付系统设计:
2、在支付系统中,支付方式(如信用卡、PayPal、微信支付)可以作为实现化角色,而支付接口作为抽象部分。这样,添加新的支付方式或修改现有支付逻辑时互不影响。 游戏角色和行为分离:
3、在游戏开发中,角色(如战士、法师)可以有一个抽象类,而具体的行为(如攻击、防御、移动)可以作为实现化角色。这样,可以灵活地为不同角色分配不同的行为。 硬件设备的驱动程序开发:
4、当开发硬件设备的驱动程序时,可以使用桥接模式将设备的硬件控制(抽象部分)与具体的操作(实现部分)分离。这样,更换设备或更新操作逻辑时更加灵活。 网络应用的协议处理:
5、在网络通信中,可以使用桥接模式将协议的抽象处理与具体的协议实现分离。这样,增加新的协议或修改现有协议的处理逻辑时,不需要修改核心的协议处理代码。 报告生成系统:
6、在报告生成系统中,报告的格式(如PDF、Excel、HTML)可以作为实现化角色,而报告的内容生成逻辑作为抽象部分。这样,添加新的报告格式或修改报告内容时互不干扰。 多语言支持:
7、在需要多语言支持的应用程序中,可以使用桥接模式将用户界面的元素(抽象部分)与不同的语言资源(实现部分)分离。这样,添加新的语言或更新翻译时更加方便。 软件的插件架构:
8、当软件采用插件架构时,可以使用桥接模式将插件的接口(抽象部分)与具体的插件实现(实现部分)分离。这样,开发新的插件或更新现有插件时互不影响。 资源管理器:
9、在资源管理器中,可以使用桥接模式将资源的访问方式(抽象部分)与具体的资源类型(实现部分)分离。这样,增加新的资源类型或改变资源访问方式时更加灵活。
通过这些应用实例,我们可以看到桥接模式在实际项目中的价值,它有助于提高代码的可维护性、可扩展性和灵活性。在设计系统时,如果预见到某个类可能会有多个变化维度,考虑使用桥接模式来避免未来代码的复杂性。
在实际项目中,确保桥接模式的实现与抽象部分的解耦,可以遵循以下几个关键步骤:
- 明确角色职责:
清晰定义抽象部分(Abstraction)和实现部分(Implementor)的职责,确保它们之间的接口是明确的,并且没有依赖关系。
- 使用接口或抽象类:
为实现部分定义一个接口或抽象类,这个接口或抽象类将被抽象部分所依赖,而不是具体的实现类。
- 依赖关系反向:
确保抽象部分不直接依赖具体实现类,而是依赖实现部分的接口或抽象类。
- 通过组合实现解耦:
在抽象部分中,使用一个成员变量来组合实现部分的接口或抽象类,而不是具体实现类。
- 避免直接引用实现类:
在抽象部分的代码中,避免直接引用任何实现类的实例,所有的操作都通过实现部分的接口或抽象类来进行。
- 使用工厂模式:
可以使用工厂模式来创建实现部分的对象,这样可以进一步解耦实现部分的具体类和抽象部分。
- 定义清晰的协议:
为实现部分定义清晰的协议或接口,确保所有实现类都遵循这个协议,这样抽象部分不依赖于任何特定的实现细节。
- 单一职责原则:
遵循单一职责原则,确保抽象类和实现类都只处理它们应该处理的职责。
- 编写单元测试:
为抽象部分和实现部分编写单元测试,确保它们可以独立于彼此进行测试。
- 代码审查:
通过代码审查来确保实现遵循桥接模式的原则,没有不当的依赖关系。
- 文档和注释:
编写清晰的文档和注释,说明抽象部分和实现部分的接口和职责。
- 逐步集成:
在集成过程中,逐步将抽象部分和实现部分结合起来,确保它们可以协同工作,同时保持各自的独立性。
- 使用依赖注入:
利用依赖注入(Dependency Injection)来动态地将实现部分注入到抽象部分中,而不是在抽象部分中创建具体实现的实例。
- 避免条件语句:
避免在抽象部分中使用条件语句来判断具体的实现类型,这有助于保持解耦。
通过这些步骤,可以确保在实际项目中实现桥接模式时,抽象部分和实现部分能够保持高度的解耦,从而提高代码的灵活性和可维护性
你可以创建与平台无关的类和程序。 客户端代码仅与高层抽象部分进行互动, 不会接触到平台的详细信息。 开闭原则。 你可以新增抽象部分和实现部分, 且它们之间不会相互影响。 单一职责原则。 抽象部分专注于处理高层逻辑, 实现部分处理平台细节。
对高内聚的类使用该模式可能会让代码更加复杂。
- Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自纬度的变化,即“子类化”它们。
- Bridge模式有时候类似于多继承方案,但是多继承方案往往违背单一职责原则(即一个类只有一个变化的原因),复用性比较差。Bridge模式是比多继承方案更好的解决方法。
- Bridge模式的应用一般在“两个非常强的变化维度”,有时一个类也有多于两个的变化维度,这时可以使用Bridge的扩展模式。