一、桥接模式概述
桥接模式是一种结构型设计模式,将抽象部分与实现部分分离,使它们都可以独立地变化,降低了抽象和实现这两个可变维度的耦合度。
桥接模式的核心思想在于将抽象化和实现化解耦,通过组合关系而非继承关系来连接这两个部分。例如在 JDBC 驱动程序中,Driver 接口就相当于抽象部分,而不同数据库的具体驱动实现(如 MySQL 的 Driver、Oracle 的 Driver)则是实现部分。Driver 和 Connection 之间通过 DriverManager 类进行桥连接,这样就可以在不影响抽象部分的情况下,轻松地切换不同的数据库实现。
在实际编程中,桥接模式有很多优点。首先,它实现了抽象和实现部分的分离,极大地提高了系统的灵活性。对于系统的高层部分,只需要知道抽象部分和实现部分的接口就可以了,其它的部分由具体业务来完成。其次,桥接模式可以减少子类的个数,降低系统的管理和维护成本。比如在手机的样式和品牌的架构设计中,如果用传统方式,每增加一种手机样式或品牌,就需要增加大量的子类,而使用桥接模式,可以将手机样式和品牌分离,独立变化,避免了类爆炸的问题。
此外,桥接模式也有一定的局限性。它要求正确识别出系统中两个独立变化的维度,这在实际应用中可能并不容易。而且,由于聚合关联关系建立在抽象层,要求开发者针对抽象进行设计和编程,增加了系统的理解和设计难度。
总的来说,桥接模式是一种非常有用的设计模式,在很多场景下都能发挥重要作用。比如当一个类存在两个独立变化的维度,且这两个维度都需要进行扩展时;当一个系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时;当一个系统需要在构件的抽象化角色和具体化角色之间增加更多的灵活性时,都可以考虑使用桥接模式。
二、桥接模式的实现步骤
(一)定义抽象接口
在 C++ 中,抽象接口可以是一个包含纯虚函数的类。例如:
class Abstraction
{
public:
virtual void operation() = 0;
virtual ~Abstraction() = default;
};
这个抽象接口声明了一个纯虚函数operation,具体的实现将由后续的实现类提供。
(二)定义实现接口
接着定义实现接口,比如:
class Implementor
{
public:
virtual void implement() = 0;
virtual ~Implementor() = default;
};
这个实现接口定义了一个方法implement,用于实现抽象接口中声明的方法。
(三)实现类
创建实现了抽象接口和实现接口的类,如:
class ConcreteImplementorA : public Implementor
{
public:
void implement() override
{
std::cout << "ConcreteImplementorA::implement()" << std::endl;
}
};
class ConcreteImplementorB : public Implementor
{
public:
void implement() override
{
std::cout << "ConcreteImplementorB::implement()" << std::endl;
}
};
这些实现类提供了抽象接口中声明的方法的具体实现。
(四)引用类(桥接类)
创建引用类,持有对实现类对象的引用,例如:
class RefinedAbstraction : public Abstraction
{
private:
std::unique_ptr<Implementor> implementor;
public:
RefinedAbstraction(std::unique_ptr<Implementor> impl) : implementor(std::move(impl)) {}
void operation() override
{
implementor->implement();
// 可能还有其他操作
}
};
这个引用类将客户端代码与具体实现解耦,客户端只与抽象接口打交道。
(五)客户端代码
在客户端代码中,可以使用引用类的对象来调用抽象接口的方法,具体实现由实现类提供。
int main()
{
// 创建具体实现类的智能指针对象
auto implA = std::make_unique<ConcreteImplementorA>();
auto implB = std::make_unique<ConcreteImplementorB>();
// 创建引用类的对象,并将具体实现对象的智能指针传递给它
std::unique_ptr<Abstraction> abstractionA = std::make_unique<RefinedAbstraction>(std::move(implA));
std::unique_ptr<Abstraction> abstractionB = std::make_unique<RefinedAbstraction>(std::move(implB));
// 使用引用类的对象调用方法
abstractionA->operation();
abstractionB->operation();
// 智能指针会自动管理内存,无需手动清理
return 0;
}
在客户端代码中,首先创建具体实现类的对象,然后将其传递给引用类,最后通过引用类调用抽象接口的方法,实现了抽象与实现的解耦。
三、桥接模式的应用场景
(一)提高系统灵活性
当系统需要在抽象化角色和具体化角色之间增加更多的灵活性,避免静态的继承联系时,可以使用桥接模式。例如在图形绘制软件中,抽象部分可以是各种图形的形状,如圆形、矩形等,而实现部分可以是不同的绘制风格,如实线、虚线等。通过桥接模式,可以将形状和绘制风格分离,使得在不影响其他部分的情况下,可以轻松地添加新的形状或绘制风格。这样就增加了系统的灵活性,使得软件能够更好地适应不同的需求。
(二)降低类之间的耦合度
当系统不希望使用继承或因为多层次继承导致系统类的个数急剧增加时,桥接模式是一个很好的选择。以一个跨平台的游戏开发为例,抽象部分可以是游戏的逻辑,如角色移动、攻击等,而实现部分可以是不同的平台,如 Windows、Linux、Mac 等。如果使用继承的方式,每增加一个平台,就需要为每个游戏逻辑类创建一个子类,这样会导致类的个数急剧增加。而使用桥接模式,可以将游戏逻辑和平台分离,使得游戏逻辑可以独立于平台进行开发和维护,降低了类之间的耦合度。
(三)处理多个独立变化维度
当一个类存在两个或多个独立变化的维度,且这些维度都需要进行扩展时,桥接模式是一个理想的选择。比如在一个电商系统中,抽象部分可以是商品的种类,如服装、食品等,而实现部分可以是不同的销售渠道,如线上商店、线下门店等。通过桥接模式,可以将商品种类和销售渠道分离,使得在不影响其他部分的情况下,可以轻松地添加新的商品种类或销售渠道。据统计,采用桥接模式的电商系统在面对市场变化时,能够更快地进行扩展和调整,提高了系统的适应性和竞争力。
四、桥接模式的具体应用实例
(一)跨平台图像浏览系统
在跨平台图像浏览系统中,Image 充当抽象类,其子类如 JPGImage、PNGImage、BMPImage 和 GIFImage 充当扩充抽象类;ImageImp 充当实现类接口,其子类如 WindowsImp、LinuxImp 和 UnixImp 充当具体实现类。这样的设计将操作系统和图像文件格式两个维度分离,使得系统在面对新的图像格式或操作系统时,只需增加相应的子类,而不会影响到其他部分。例如,如果要支持新的图像格式 TIF,只需创建一个 TIFImage 类,而无需修改现有的操作系统相关类。同样,如果要支持新的操作系统 Mac OS,只需创建一个 MacOSImp 类,而无需修改现有的图像格式相关类。这种分离大大提高了系统的扩展性,减少了子类的数目。
(二)JDBC 规范与数据库厂商驱动
在 JDBC 中,驱动接口(如 java.sql.Driver)是抽象部分,每个数据库厂商都有自己的实现。例如,MySQL 有 com.mysql.cj.jdbc.Driver,Oracle 有 oracle.jdbc.driver.OracleDriver 等。这些具体的驱动实现可以独立变化,而不影响 JDBC 的整体架构。JDBC 中的驱动管理器(DriverManager)通过组合的方式,持有注册进来的 Driver 对象,对应用系统提供数据库操作的 API。例如,通过 DriverManager.getConnection 方法可以获取数据库连接。DriverManager 也可以根据需求独立变化,比如在不同的 Java 版本中,DriverManager 的实现可能会有所不同,但不会影响到具体的数据库驱动实现。
(三)网络桥接模式
在编程领域,可以使用各种编程语言和网络库来实现网络桥接模式。以 Python 为例,使用 socket 库可以方便地创建网络连接和传输数据。网络桥接模式允许将两个或多个网络连接在一起,以便它们能够共享数据和通信。在这种模式下,桥接设备充当连接不同网络的中间桥梁,它能够将数据包从一个网络传输到另一个网络,同时保持网络的透明性和互操作性。例如,在一个企业网络中,可能需要将不同部门的局域网连接起来,以便实现资源共享和数据交换。这时可以使用网络桥接模式,将各个局域网通过桥接设备连接在一起,实现网络的互联互通。
(四)编程中的套路 —— 桥接模式
桥接模式可以将两个原本不相关的类结合在一起,利用两个类中的方法和属性,输出一份新的结果。例如,有一个图形绘制类和一个颜色选择类,通过桥接模式,可以将这两个类结合起来,实现不同颜色的图形绘制。具体实现可以是在图形绘制类中持有一个颜色选择类的对象,通过调用颜色选择类的方法来确定图形的颜色,然后进行绘制。这样的设计可以使得图形绘制和颜色选择独立变化,提高了系统的灵活性和可扩展性。