实战设计模式之桥接模式
概述
与上一篇介绍的适配器模式一样,桥接模式也是一种结构型设计模式。它旨在将抽象部分与其实现部分分离,使它们可以独立变化。通过桥接模式,我们可以让一个类的功能模块化,并且可以在不修改其他模块的情况下进行扩展或修改。这种设计思路有助于创建更加灵活、易于维护的代码库。
为了更好地理解桥接模式,我们以现实生活中的蜡笔和毛笔为例。假如我们需要大、中、小三种规格的蜡笔和毛笔,每种规格的笔需要支持红、黄、蓝三种颜色。对于蜡笔,我们可以生产大规格红色、大规格黄色、大规格蓝色、中规格红色、中规格黄色、中规格蓝色、小规格红色、小规格黄色、小规格蓝色共计9种类型的笔。而对于毛笔,我们可以仅生产大、中、小3种规格的笔,然后提供红、黄、蓝3种颜色的颜料即可,一共是6个对象。
可以看到,通过桥接模式,我们创建了一个桥梁,让毛笔的尺寸和颜色能够独立变化。如果我们想要增加一个新的颜色,我们不需要为每种尺寸都创建一个新的类或产品;相反,我们只需要添加一个颜色的实现即可。同理,如果要添加新的尺寸,也只需要增加一个尺寸的抽象即可。
基本原理
桥接模式的核心思想是:通过将一个大对象拆分成两个或多个小对象,来降低系统的复杂度。具体来说,它允许我们将某个类的接口与其具体实现分离开来,从而使得两者可以独立演化。这不仅提高了代码的灵活性,还增强了可维护性和扩展性。桥接模式主要由以下四个核心组件构成。
1、抽象。定义了客户端所使用的接口,它是高层的逻辑抽象,定义了基本操作以及与实现部分交互的方法。
2、细化抽象。扩展了抽象类,提供了更具体的实现。它可以包含额外的行为或者属性,以满足特定的需求。
3、实现者。定义了所有具体实现类的接口,它提供了一组基础方法,供各个具体实现类去实现。
4、具体实现者。实现了实现者的接口,并提供了具体的实现细节,每个具体实现者都可以有不同的行为和特性。
基于上面的核心组件,桥接模式的实现主要有以下五个步骤。
1、定义实现者接口。定义一个接口或抽象类,作为所有具体实现者的基类。这个接口应该包含所有具体实现者需要实现的方法,作用是为不同的实现提供一个统一的操作入口。
2、创建具体实现者。基于第一步中定义的接口或抽象类,创建多个具体实现者类,每个具体实现者类负责提供一种特定的实现方式。这些类将具体化接口中声明的行为,并可以独立于其他组件进行扩展或修改。
3、定义抽象类。定义一个抽象类,该类持有一个指向实现者接口的引用。这个抽象类定义了高层逻辑和客户端交互所需的接口,但并不直接依赖于任何具体的实现细节。通过这种方式,抽象部分与实现部分得以分离,允许两者独立变化。
4、创建细化抽象。在抽象类的基础上,进一步创建细化抽象类。这些细化抽象类继承自抽象类,并根据具体需求添加额外的功能或属性。它们可以通过组合的方式使用实现者提供的功能,从而实现行为的具体化。
5、使用桥接模式。客户端只需要知道抽象类及其接口,而不需要关心具体的实现细节。当需要扩展功能或改变行为时,只需新增具体的实现者或细化抽象,而不必修改现有代码。
实战解析
在下面的实战代码中,我们使用桥接模式来实现上面提到的毛笔。该毛笔支持大、中、小三种规格,以及红、黄、蓝三种颜色。
首先,我们定义颜料接口CInk类。CInk是一个抽象基类,它定义了一个纯虚函数ApplyColor,这表示所有继承自CInk的具体颜料类都必须实现这个方法。具体颜料类有CRedInk、CBlueInk、CGreenInk,它们从CInk继承,并实现了各自的ApplyColor方法,用于输出特定颜色的应用信息。
然后,我们定义毛笔接口CBrush类。CBrush也是一个抽象基类,它持有一个指向CInk对象的指针成员变量m_pInk。这意味着,任何CBrush的子类都可以与任意CInk类型的对象组合使用。
接下来,我们定义细化的毛笔类CLargeBrush、CMediumBrush、CSmallBrush。这些类分别代表不同尺寸的毛笔,它们继承自CBrush并且覆盖了Draw函数。在Draw函数中,先打印出关于毛笔尺寸的信息,然后调用m_pInk的ApplyColor函数来应用颜色。
最后,在main函数中,我们创建了三种不同颜色的颜料实例。接着,使用这些颜料实例创建了不同尺寸的毛笔对象,每一个毛笔对象都关联了一种特定的颜色。我们对每个毛笔对象调用了Draw方法,执行绘制操作,同时输出了使用的毛笔大小和所应用的颜色。
#include <iostream>
using namespace std;
// 实现者:定义颜料的接口
class CInk
{
public:
virtual void ApplyColor() = 0;
virtual ~CInk() {}
};
// 具体实现者:红色颜料
class CRedInk : public CInk
{
public:
void ApplyColor() override
{
cout << "Applying red ink" << endl;
}
};
// 具体实现者:蓝色颜料
class CBlueInk : public CInk
{
public:
void ApplyColor() override
{
cout << "Applying blue ink" << endl;
}
};
// 具体实现者:绿色颜料
class CGreenInk : public CInk
{
public:
void ApplyColor() override
{
cout << "Applying green ink" << endl;
}
};
// 抽象:定义毛笔的接口
class CBrush
{
public:
CBrush(CInk* pInk) : m_pInk(pInk) {}
virtual ~CBrush() { delete m_pInk; }
virtual void Draw() = 0;
protected:
// 组合关系,持有颜料对象
CInk* m_pInk;
};
// 细化抽象:大号毛笔
class CLargeBrush : public CBrush
{
public:
CLargeBrush(CInk* pInk) : CBrush(pInk) {}
void Draw() override
{
cout << "Drawing with large brush..." << endl;
m_pInk->ApplyColor();
}
};
// 细化抽象:中号毛笔
class CMediumBrush : public CBrush
{
public:
CMediumBrush(CInk* pInk) : CBrush(pInk) {}
void Draw() override
{
cout << "Drawing with medium brush..." << endl;
m_pInk->ApplyColor();
}
};
// 细化抽象:小号毛笔
class CSmallBrush : public CBrush
{
public:
CSmallBrush(CInk* pInk) : CBrush(pInk) {}
void Draw() override
{
cout << "Drawing with small brush..." << endl;
m_pInk->ApplyColor();
}
};
int main()
{
// 创建不同颜色的颜料
CInk* pRedInk = new CRedInk();
CInk* pBlueInk = new CBlueInk();
CInk* pGreenInk = new CGreenInk();
// 使用桥接模式创建不同尺寸的毛笔并设置颜色
CBrush* pLargeRedBrush = new CLargeBrush(pRedInk);
CBrush* pMediumBlueBrush = new CMediumBrush(pBlueInk);
CBrush* pSmallGreenBrush = new CSmallBrush(pGreenInk);
// 调用绘制方法
pLargeRedBrush->Draw();
pMediumBlueBrush->Draw();
pSmallGreenBrush->Draw();
// 清理资源
delete pLargeRedBrush;
delete pMediumBlueBrush;
delete pSmallGreenBrush;
return 0;
}
总结
通过上面的实战代码可以看到,桥接模式使得抽象和实现可以独立变化,减少了两者之间的依赖关系。用户可以在运行时动态地选择或切换实现,这增加了程序的灵活性,并允许更容易地扩展系统功能。在某些情况下,使用继承可能会导致“继承地狱”,即子类爆炸式增长。桥接模式通过组合代替继承来解决这个问题,简化了系统的维护和管理。
但对于简单的系统,引入桥接模式可能会增加不必要的复杂性。如果不需要频繁更改或扩展抽象和实现,则可能不值得使用此模式。