前言

尊重原创,本系列文本解析部分主要基于Candidate for Master’s Degree School of Computer Wuhan University的K_Eckel([email protected])《设计模式精解-GoF 23 种设计模式解析附 C++实现源码》。为了避免重造轮子,本系列博文对源码在ubuntu16.04下做了验证并上传到了gitee,再次感谢。

如有问题,可邮件联系我([email protected])并共同探讨解决方案。

目录

创建型模式(Creating Pattern)

Factory 模式 | AbstactFactory 模式 | Singleton 模式 | Builder 模式 | Prototype 模式

结构型模式(Structrual Pattern)

Bridge 模式 | Adapter 模式 | Decorator 模式 | Composite 模式 | Flyweight 模式 | Facade 模式 | Proxy 模式

行为型模式(Behavioral Pattern)

Template 模式 | Strategy 模式 | State 模式 | Observer 模式 | Memento 模式 | Mediator 模式 | Command 模式 | Visitor 模式 | Iterator 模式 | Interpreter 模式 | Chain of Responsibility 模式

Bridge 模式简介:

桥接(Bridge)是用于把抽象化与实现化解耦,使得二者可以独立变化。这种类型的设计模式属于结构型模式,它通过提供抽象化和实现化之间的桥接结构,来实现二者的解耦。

这种模式涉及到一个作为桥接的接口,使得实体类的功能独立于接口实现类。这两种类型的类可被结构化改变而互不影响。

设想如果要绘制矩形、圆形、椭圆、正方形,我们至少需要4个形状类,但是如果绘制的图形需要具有不同的颜色,如红色、绿色、蓝色等,此时至少有如下两种设计方案:

  • 第一种设计方案是为每一种形状都提供一套各种颜色的版本。
  • 第二种设计方案是根据实际需要对形状和颜色进行组合

对于有两个变化维度(即两个变化的原因)的系统,采用方案二来进行设计系统中类的个数更少,且系统扩展更为方便。设计方案二即是桥接模式的应用。桥接模式将继承关系转换为关联关系,从而降低了类与类之间的耦合,减少了代码编写量。

问题

总结面向对象实际上就两句话:一是松耦合(Coupling),二是高内(Cohesion)。面向对象系统追求的目标就是尽可能地提高系统模块内部的内聚(Cohesion)、尽可能降低模块间的耦合(Coupling)。然而这也是面向对象设计过程中最为难把握的部分,大家肯定在OO 系统的开发过程中遇到这样的问题:

  1. 客户给了你一个需求,于是使用一个类来实现(A);
  2. 客户需求变化,有两个算法实现功能,于是改变设计,我们通过一个抽象的基类,再定义两个具体类实现两个不同的算法(A1A2);
  3. 客户又告诉我们说对于不同的操作系统,于是再抽象一个层次,作为一个抽象基类A0,在分别为每个操作系统派生具体类(A00A01,其中 A00 表示原来的类 A)实现不同操作系统上的客户需求,这样我们就有了一共 4 个类。
  4. 可能用户的需求又有变化,比如说又有了一种新的算法…
  5. 我们陷入了一个需求变化的郁闷当中,也因此带来了类的迅速膨胀。

Bridge 模式则正是解决了这类问题。

模式选择

Bridge 模式典型的结构图为:

图 1:Bridge Pattern 结构图

在 Bridge 模式的结构图中可以看到,系统被分为两个相对独立的部分,左边是抽象部分,右边是实现部分,这两个部分可以互相独立地进行修改:例如上面问题中的客户需求变化,当用户需求需要从 Abstraction 派生一个具体子类时候,并不需要像上面通过继承方式实现时候需要添加子类 A1A2 了。另外当上面问题中由于算法添加也只用改变右边实现(添加一个具体化子类),而右边不用在变化,也不用添加具体子类了。

一切都变得 elegant!

实现

完整代码示例(code)

Bridge 模式的实现并不是特别困难,这里为了方便初学者的学习和参考,将给出完整的实现代码(所有代码采用 C++实现,并在 Visual Studio Code,Version: 1.36.1 下测试运行)。

源码gitee地址:点击这里

代码目录结构:

patten@patten-hp:~/workspace/others/cpp/designPatterns/structuralPattern/Bridge/src$ tree ../
../
├── include
│   ├── Abstraction.h
│   └── AbstractionImp.h
└── src
    ├── Abstraction.cpp
    ├── AbstractionImp.cpp
    └── main.cpp

2 directories, 5 files

Abstraction.h:

// Abstraction.h

#ifndef _ABSTRACTION_H_
#define _ABSTRACTION_H_

class AbstractionImp;

class Abstraction {
 private:
  /* data */

 protected:
  Abstraction(/* args */);

 public:
  virtual ~Abstraction();
  virtual void Operation() = 0;
};

class RefinedAbstraction: public Abstraction
{
private:
    AbstractionImp* _imp;

public:
    RefinedAbstraction(AbstractionImp* imp);
    ~RefinedAbstraction();
    void Operation();
};

#endif //~_ABSTRACTION_H_

AbstractionImp.h:

// AbstractionImp.h

#ifndef _ABSTRACTIONIMP_H_
#define _ABSTRACTIONIMP_H_

class AbstractionImp {
 private:
  /* data */

 protected:
  AbstractionImp(/* args */);

 public:
  virtual ~AbstractionImp();
  virtual void Operation() = 0;
};

class ConcreteAbstractionImpA: public AbstractionImp
{
private:
    /* data */
public:
    ConcreteAbstractionImpA(/* args */);
    ~ConcreteAbstractionImpA();
    virtual void Operation();
};

class ConcreteAbstractionImpB: public AbstractionImp
{
private:
    /* data */
public:
    ConcreteAbstractionImpB(/* args */);
    ~ConcreteAbstractionImpB();
    virtual void Operation();
};

#endif //~_ABSTRACTIONIMP_H_

Abstraction.cpp:

//Abstraction.cpp

#include "../include/Abstraction.h"
#include "../include/AbstractionImp.h"

#include <iostream>
using namespace std;

Abstraction::Abstraction(/* args */) {}
Abstraction::~Abstraction() {}

RefinedAbstraction::RefinedAbstraction(AbstractionImp* imp) {
    _imp = imp;
}
RefinedAbstraction::~RefinedAbstraction() {}
void RefinedAbstraction::Operation(){
    _imp->Operation();
}

AbstractionImp.cpp:

// AbstractionImp.cpp

#include "../include/AbstractionImp.h"
#include <iostream>

using namespace std;

AbstractionImp::AbstractionImp(/* args */) {}
AbstractionImp::~AbstractionImp() {}
void AbstractionImp::Operation(){
    printf("AbstractionImp....imp... \n");
}

ConcreteAbstractionImpA::ConcreteAbstractionImpA(/* args */) {}
ConcreteAbstractionImpA::~ConcreteAbstractionImpA() {}
void ConcreteAbstractionImpA::Operation(){
    printf("ConcreteAbstractionImpA....imp... \n");
}

ConcreteAbstractionImpB::ConcreteAbstractionImpB(/* args */) {}
ConcreteAbstractionImpB::~ConcreteAbstractionImpB() {}
void ConcreteAbstractionImpB::Operation(){
    printf("ConcreteAbstractionImpB....imp... \n");
}

main.cpp:

// main.cpp

#include <iostream>
#include "../include/Abstraction.h"
#include "../include/AbstractionImp.h"

using namespace std;

int main(int argc, char* argv[]) {
  AbstractionImp* imp = new ConcreteAbstractionImpA();
  Abstraction* abs = new RefinedAbstraction(imp);
  abs->Operation();

  return 0;
}

代码说明

Bridge 模式将抽象和实现分别独立实现,在代码中就是 Abstraction 类和 AbstractionImp类。

编译运行结果:

patten@patten-hp:~/workspace/others/cpp/designPatterns/structuralPattern/Bridge/src$ g++ *.cpp -std=c++11
patten@patten-hp:~/workspace/others/cpp/designPatterns/structuralPattern/Bridge/src$ ./a.out 
ConcreteAbstractionImpA....imp... 
patten@patten-hp:~/workspace/others/cpp/designPatterns/structuralPattern/Bridge/src$

讨论

Bridge 是设计模式中比较复杂和难理解的模式之一,也是 OO 开发与设计中经常会用到的模式之一。使用组合(委托)的方式将抽象和实现彻底地解耦,这样的好处是抽象和实现可以分别独立地变化,系统的耦合性也得到了很好的降低。

GoF 在说明 Bridge 模式时,在意图中指出 Bridge 模式“将抽象部分与它的实现部分分离,使得它们可以独立地变化”。这句话很简单,但是也很复杂,连 Bruce Eckel 在他的大作《Thinking in Patterns》中说“Bridge 模式是 GoF 所讲述得最不好(Poorly-described)的模式”,个人觉得也正是如此。原因就在于 GoF 的那句话中的“实现”该怎么去理解:“实现”特别是和“抽象”放在一起的时候我们“默认”的理解是“实现”就是“抽象”的具体子类的实现,但是这里 GoF 所谓的“实现”的含义不是指抽象基类的具体子类对抽象基类中虚函数(接口)的实现,是和继承结合在一起的。而这里的“实现”的含义指的是怎么去实现用户的需求,并且指的是通过组合(委托)的方式实现的,因此这里的实现不是指的继承基类、实现基类接口,而是指的是通过对象组合实现用户的需求。理解了这一点也就理解了Bridge 模式,理解了 Bridge 模式,你的设计就会更加 Elegant 了。

实际上上面使用 Bridge 模式和使用带来问题方式的解决方案的根本区别在于是通过继承还是通过组合的方式去实现一个功能需求。因此面向对象分析和设计中有一个原则就是:Favor Composition Over Inheritance。其原因也正在这里。