【设计模式 - 结构型模式】2. 桥接模式

一、定义

考虑这样一个需求:绘制矩形、圆形、三角形这三种图案。按照面向对象的理念,我们至少需要个具体类,对应三种不同的图形。

// 抽象形状类IShape
class IShape {
public:
    void draw();
};

// 三个具体形状类:矩形、圆形、三角形
class Rectangle: public IShape {
public:
    void draw() {
        cout << "绘制矩形" << endl;
    }
};
class Round: public IShape {
public:
    void draw() {
        cout << "绘制圆形" << endl;
    }
};
class Triangle: public IShape {
public:
    void draw() {
        cout << "绘制三角形" << endl;
    }
};

接下来我们有了新的需求,每种形状都需要有四种不同的颜色:红、蓝、黄、绿。

这时我们很容易想到两种设计方案:

  • 为了复用形状类,将每种形状定义为父类,每种不同颜色的图形继承自其形状父类。此时一共有 12 个类。
  • 为了复用颜色类,将每种颜色定义为父类,每种不同颜色的图形继承自其颜色父类。此时一共有 12 个类。

乍一看没什么问题,我们使用了面向对象的继承特性,复用了父类的代码并扩展了新的功能。

但仔细想想,如果以后要増加一种颜色,比如黑色,那么我们就需要增加三个类;如果再要增加一种形状,我们又需要增加五个类,对应 5 种颜色。更不用说遇到增加 20 个形状,20 种颜色的需求,不同的排列组合将会使工作量变得无比的庞大。看来我们不得不重新思考设计方案。

形状和颜色,都是图形的两个属性。他们两者的关系是平等的,所以不属于继承关系。更好的的实现方式是:将形状和颜色分离,根据需要对形状和颜色进行组合,这就是桥接模式的思想。

桥接模式:将抽象部分与它的实现部分分离,使它们都可以独立地变化。它是一种对象结构型模式,又称为柄体模式或接口模式。

官方定义非常精准、简练,但却有点不易理解。通俗地说,如果一个对象有两种或者多种分类方式,并且两种分类方式都容易变化,比如本例中的形状和颜色。这时使用继承很容易造成子类越来越多,所以更好的做法是把这种分类方式分离出来,让他们独立变化,使用时将不同的分类进行组合即可。


二、结构

桥接(Bridge)模式包含以下主要角色。

  • 抽象化(Abstraction)角色:定义抽象类,并包含一个对实现化对象的引用。
  • 扩展抽象化(Refined Abstraction)角色:是抽象化角色的子类,实现父类中的业务方法,并通过组合关系调用实现化角色中的业务方法。
  • 实现化(Implementor)角色:定义实现化角色的接口,供扩展抽象化角色调用。
  • 具体实现化(Concrete Implementor)角色:给出实现化角色接口的具体实现。

其结构图如下图所示:
【设计模式 - 结构型模式】2. 桥接模式


二、程序实现

实现程序如下:

#include <iostream>
#include <string>

using namespace std;

// 实现化角色: 颜色类IColor
class IColor {
public:
    virtual string getColor() = 0;
};

// 具体实现化角色A
class Red: public IColor {
public:
    string getColor() {
        return "红";
    }
};

// 具体实现化角色B
class Blue: public IColor {
public:
    string getColor() {
        return "蓝";
    }
};

// 具体实现化角色C
class Yellow: public IColor {
public:
    string getColor() {
        return "黄";
    }
};

// 具体实现化角色D
class Green: public IColor {
public:
    string getColor() {
        return "绿";
    }
};

// 抽象化角色: 形状类IShape
class IShape {
public:
    virtual void setColor(IColor *color) = 0;
    virtual void draw() = 0;
};

// 扩展抽象化角色A
class Rectangle: public IShape {
public:
    void setColor(IColor *color) {
        this->m_color = color;
    }

    void draw() {
        cout << "绘制" << m_color->getColor() << "矩形" << endl;
    }

private:
    IColor *m_color;
};

// 扩展抽象化角色B
class Round: public IShape {
public:
    void setColor(IColor *color) {
        this->m_color = color;
    }

    void draw() {
        cout << "绘制" << m_color->getColor() << "圆形" << endl;
    }

private:
    IColor *m_color;
};

// 扩展抽象化角色C
class Triangle: public IShape {
public:
    void setColor(IColor *color) {
        this->m_color = color;
    }

    void draw() {
        cout << "绘制" << m_color->getColor() << "三角形" << endl;
    }

private:
    IColor *m_color;
};

int main()
{
    Rectangle *rectangle = new Rectangle();
    rectangle->setColor(new Red());
    rectangle->draw();

    Round *round = new Round();
    round->setColor(new Blue());
    round->draw();

    Triangle *triangle = new Triangle();
    triangle->setColor(new Yellow());
    triangle->draw();

    return 0;
}

输出如下:

绘制红矩形
绘制蓝圆形
绘制黄三角形

这时我们再来回顾一下官方定义:将抽象部分与它的实现部分分离,使它们都可以独立地变化。抽象部分指的是父类,对应本例中的形状类,实现部分指的是不同子类的区别之处。将子类的区别方式——也就是本例中的颜色——分离成接口,通过组合的方式桥接颜色和形状,这就是桥接模式,它主要用于两个或多个同等级的接口。


参考:

知乎 - 如何学习设计模式? 热门回答

菜鸟教程 - 设计模式篇

桥接模式(Bridge模式)详解


上一篇:Effective C++条款37:继承与面向对象——绝不重新定义继承而来的缺省参数值


下一篇:小程序canvas生成图片压缩和失真问题draw失效问题汇总