【设计模式】 桥梁模式

时间:2021-12-15 16:00:09

1、定义

1.1 标准定义

桥梁模式( Bridge Pattern) 也叫做桥接模式, 是一个比较简单的模式, 其定义如下:
Decouple an abstraction from its implementation so that the two can vary independently.( 将抽象和实现解耦, 使得两者可以独立地变化。 )
桥梁模式的重点是在解耦上, 如何让它们两者解耦是我们要了解的重点。

1.2 通用类图

【设计模式】 桥梁模式

● Abstraction——抽象化角色
它的主要职责是定义出该角色的行为, 同时保存一个对实现化角色的引用, 该角色一般是抽象类。
● Implementor——实现化角色
它是接口或者抽象类, 定义角色必需的行为和属性。
● RefinedAbstraction——修正抽象化角色
它引用实现化角色对抽象化角色进行修正。
● ConcreteImplementor——具体实现化角色
它实现接口或抽象类定义的方法和属性。

2、实现

2.1 类图

【设计模式】 桥梁模式

Abstraction::Operation():定义要实现的操作接口。
AbstractionImplement::Operation():实现抽象类Abstaction所定义操作的接口,由其具体派生类ConcreteImplemenA、ConcreteImplemenA或者其他派生类实现。
在Abstraction::Operation()中根据不同的指针多态调用AbstractionImplement::Operation()函数。

Bridge用于将表示和实现解耦,两者可以独立的变化.在Abstraction类中维护一个AbstractionImplement类指针,需要采用不同的实现方式的时候只需要传入不同的AbstractionImplement派生类就可以了。

桥接模式就将实现与抽象分离开来,使得RefinedAbstraction依赖于抽象的实现,这样实现了依赖倒转原则,而不管左边的抽象如何变化,只要实现方法不变,右边的具体实现就不需要修改,而右边的具体实现方法发生变化,只要接口不变,左边的抽象也不需要修改。

2.2 代码

2.2.1 抽象操作

// Abstraction.h

#ifndef _ABSTRACTION_H_
#define _ABSTRACTION_H_

class AbstractionImplement;

class Abstraction
{
public:
virtual void Operation()=0;//定义接口,表示该类所支持的操作
virtual ~Abstraction();
protected:
Abstraction();
};

class RefinedAbstractionA:public Abstraction
{
public:
RefinedAbstractionA(AbstractionImplement
* imp);//构造函数
virtual void Operation();//实现接口
virtual ~RefinedAbstractionA();//析构函数
private:
AbstractionImplement
* _imp;//私有成员
};

class RefinedAbstractionB:public Abstraction
{
public:
RefinedAbstractionB(AbstractionImplement
* imp);//构造函数
virtual void Operation();//实现接口
virtual ~RefinedAbstractionB();//析构函数
private:
AbstractionImplement
* _imp;//私有成员
};

#endif
// Abstraction.cpp

#include
"Abstraction.h"
#include
"AbstractionImplement.h"
#include
<iostream>

using namespace std;

Abstraction::Abstraction(){}

Abstraction::
~Abstraction(){}

RefinedAbstractionA::RefinedAbstractionA(AbstractionImplement
* imp)
{
this->_imp = imp;
}

RefinedAbstractionA::
~RefinedAbstractionA()
{
delete this->_imp;
this->_imp = NULL;
}

void RefinedAbstractionA::Operation()
{
cout
<< "RefinedAbstractionA::Operation" << endl;
this->_imp->Operation();
}

RefinedAbstractionB::RefinedAbstractionB(AbstractionImplement
* imp)
{
this->_imp = imp;
}

RefinedAbstractionB::
~RefinedAbstractionB()
{
delete this->_imp;
this->_imp = NULL;
}

void RefinedAbstractionB::Operation()
{
cout
<< "RefinedAbstractionB::Operation" << endl;
this->_imp->Operation();
}

2.2.3 具体操作

// AbstractImplement.h

#ifndef _ABSTRACTIONIMPLEMENT_H_
#define _ABSTRACTIONIMPLEMENT_H_

//抽象基类,定义了实现的接口
class AbstractionImplement
{
public:
virtual void Operation()=0;//定义操作接口
virtual ~AbstractionImplement();
protected:
AbstractionImplement();
};

// 继承自AbstractionImplement,是AbstractionImplement的不同实现之一
class ConcreteAbstractionImplementA:public AbstractionImplement
{
public:
ConcreteAbstractionImplementA();
void Operation();//实现操作
~ConcreteAbstractionImplementA();
protected:
};

// 继承自AbstractionImplement,是AbstractionImplement的不同实现之一
class ConcreteAbstractionImplementB:public AbstractionImplement
{
public:
ConcreteAbstractionImplementB();
void Operation();//实现操作
~ConcreteAbstractionImplementB();
protected:
};

#endif
// AbstractImplement.cpp

#include
"AbstractionImplement.h"
#include
<iostream>

using namespace std;

AbstractionImplement::AbstractionImplement(){}

AbstractionImplement::
~AbstractionImplement(){}

ConcreteAbstractionImplementA::ConcreteAbstractionImplementA(){}

ConcreteAbstractionImplementA::
~ConcreteAbstractionImplementA(){}

void ConcreteAbstractionImplementA::Operation()
{
cout
<< "ConcreteAbstractionImplementA Operation" << endl;
}

ConcreteAbstractionImplementB::ConcreteAbstractionImplementB(){}

ConcreteAbstractionImplementB::
~ConcreteAbstractionImplementB(){}

void ConcreteAbstractionImplementB::Operation()
{
cout
<< "ConcreteAbstractionImplementB Operation" << endl;
}

2.2.4 调用

// main.cpp

#include
"Abstraction.h"
#include
"AbstractionImplement.h"
#include
<iostream>

using namespace std;

int main()
{
/* 将抽象部分与它的实现部分分离,使得它们可以独立地变化

1、抽象Abstraction与实现AbstractionImplement分离;

2、抽象部分Abstraction可以变化,如new RefinedAbstractionA(imp)、new RefinedAbstractionB(imp2);

3、实现部分AbstractionImplement也可以变化,如new ConcreteAbstractionImplementA()、new ConcreteAbstractionImplementB();

*/

AbstractionImplement
* imp = new ConcreteAbstractionImplementA(); //实现部分ConcreteAbstractionImplementA
Abstraction* abs = new RefinedAbstractionA(imp); //抽象部分RefinedAbstractionA
abs->Operation();

cout
<< "-----------------------------------------" << endl;

AbstractionImplement
* imp1 = new ConcreteAbstractionImplementB(); //实现部分ConcreteAbstractionImplementB
Abstraction* abs1 = new RefinedAbstractionA(imp1); //抽象部分RefinedAbstractionA
abs1->Operation();

cout
<< "-----------------------------------------" << endl;

AbstractionImplement
* imp2 = new ConcreteAbstractionImplementA(); //实现部分ConcreteAbstractionImplementA
Abstraction* abs2 = new RefinedAbstractionB(imp2); //抽象部分RefinedAbstractionB
abs2->Operation();

cout
<< "-----------------------------------------" << endl;

AbstractionImplement
* imp3 = new ConcreteAbstractionImplementB(); //实现部分ConcreteAbstractionImplementB
Abstraction* abs3 = new RefinedAbstractionB(imp3); //抽象部分RefinedAbstractionB
abs3->Operation();

cout
<< endl;
return 0;
}

2.2.5 代码说明

Bridge模式将抽象和实现分别独立实现,在代码中就是Abstraction类和AbstractionImplement类。
使用组合(委托)的方式将抽象和实现彻底地解耦,这样的好处是抽象和实现可以分别独立地变化,系统的耦合性也得到了很好的降低。
GoF的那句话中的“实现”该怎么去理解:“实现”特别是和“抽象”放在一起的时候我们“默认”的理解是“实现”就是“抽象”的具体子类的实现,但是这里GoF所谓的“实现”的含义不是指抽象基类的具体子类对抽象基类中虚函数(接口)的实现,是和继承结合在一起的。而这里的“实现”的含义指的是怎么去实现用户的需求,并且指的是通过组合(委托)的方式实现的,因此这里的实现不是指的继承基类、实现基类接口,而是指的是通过对象组合实现用户的需求。
实际上上面使用Bridge模式和使用带来问题方式的解决方案的根本区别在于是通过继承还是通过组合的方式去实现一个功能需求。

3、总结

3.1 优点

● 抽象和实现分离
这也是桥梁模式的主要特点, 它完全是为了解决继承的缺点而提出的设计模式。 在该模式下, 实现可以不受抽象的约束, 不用再绑定在一个固定的抽象层次上,将实现抽离出来,再实现抽象,使得对象的具体实现依赖于抽象,满足了依赖倒转原则。
● 优秀的扩充能力
看看我们的例子, 想增加实现? 没问题! 想增加抽象, 也没有问题! 只要对外暴露的接口层允许这样的变化, 我们已经把变化的可能性减到最小。
● 实现细节对客户透明
客户不用关心细节的实现, 它已经由抽象层通过聚合关系完成了封装。
● 减少代码重复
将可以共享的变化部分,抽离出来,减少了代码的重复信息。
● 更加灵活
对象的具体实现可以更加灵活,可以满足多个因素变化的要求。

3.2 缺点

客户必须知道选择哪一种类型的实现。

3.3 使用场景

● 不希望或不适用使用继承的场景
例如继承层次过渡、 无法更细化设计颗粒等场景, 需要多角度去分类实现对象,而只用继承会造成大量的类增加,不能满足开放-封闭原则时,就要考虑用Bridge桥接模式了。
● 接口或抽象类不稳定的场景
明知道接口不稳定还想通过实现或继承来实现业务需求, 那是得不偿失的, 也是比较失败的做法。
● 重用性要求较高的场景
设计的颗粒度越细, 则被重用的可能性就越大, 而采用继承则受父类的限制, 不可能出现太细的颗粒度。当多个变化因素在多个对象间共享时,考虑将这部分变化的部分抽象出来再聚合/合成进来。
● 变化较多的场景
当一个对象有多个变化因素的时候,考虑依赖于抽象的实现,而不是具体的实现。

3.4 注意事项

桥梁模式是非常简单的, 使用该模式时主要考虑如何拆分抽象和实现, 并不是一涉及继承就要考虑使用该模式, 那还要继承干什么呢? 桥梁模式的意图还是对变化的封装, 尽量把可能变化的因素封装到最细、 最小的逻辑单元中, 避免风险扩散。 因此读者在进行系统设计时, 发现类的继承有N层时, 可以考虑使用桥梁模式。

由于实现的方式有多种,桥接模式的核心就是把这些实现独立出来,让他们各自变化。

将抽象部分与它的实现部分分离:实现系统可能有多角度(维度)分类,每一种分类都可能变化,那么就把这种多角度分离出来让它们独立变化,减少它们之间的耦合。

合成/聚合复用原则:尽量使用合成/聚合,尽量不要使用类继承。
优先使用对象的合成/聚合将有助于保持每个类被封装,并被集中在单个任务上。这样类和类继承层次会保持较小规模,并且不太可能增长为不可控制的庞然大物。

3.5 桥梁模式VS建造者模式

Bridge的实现方式其实和Builde十分的相近,可以这么说:本质上是一样的,只是封装的东西不一样罢了。两者的实现都有如下的共同点:
抽象出来一个基类,这个基类里面定义了共有的一些行为,形成接口函数(对接口编程而不是对实现编程),这个接口函数在Buildier中是BuildePart函数在Bridge中是Operation函数;
其次,聚合一个基类的指针,如Builder模式中Director类聚合了一个Builder基类的指针,而Brige模式中Abstraction类聚合了一个AbstractionImplement基类的指针(优先采用聚合而不是继承);
而在使用的时候,都把对这个类的使用封装在一个函数中,在Bridge中是封装在Director::Construct函数中,因为装配不同部分的过程是一致的,而在Bridge模式中则是封装在Abstraction::Operation函数中,在这个函数中调用对应的AbstractionImplement::Operation函数.就两个模式而言,Builder封装了不同的生成组成部分的方式,而Bridge封装了不同的实现方式.