Boolan网——C++微专业(设计模式)第一周学习笔记

(1)设计模式简介
以下代码能够实现对图形的绘制:

class Point{
public:
    int x;
    int y;
};

class Line{
public:
    Point start;
    Point end;

    Line(const Point& start, const Point& end){
        this->start = start;
        this->end = end;
    }

};

class Rect{
public:
    Point leftUp;
    int width;
    int height;

    Rect(const Point& leftUp, int width, int height){
        this->leftUp = leftUp;
        this->width = width;
        this->height = height;
    }

};



class MainForm : public Form {
private:
    Point p1;
    Point p2;

    vector<Line> lineVector;
    vector<Rect> rectVector;

public:
    MainForm(){
        //...
    }
protected:

    virtual void OnMouseDown(const MouseEventArgs& e);
    virtual void OnMouseUp(const MouseEventArgs& e);
    virtual void OnPaint(const PaintEventArgs& e);
};


void MainForm::OnMouseDown(const MouseEventArgs& e){
    p1.x = e.X;
    p1.y = e.Y;

    //...
    Form::OnMouseDown(e);
}

void MainForm::OnMouseUp(const MouseEventArgs& e){
    p2.x = e.X;
    p2.y = e.Y;

    if (rdoLine.Checked){
        Line line(p1, p2);
        lineVector.push_back(line);
    }
    else if (rdoRect.Checked){
        int width = abs(p2.x - p1.x);
        int height = abs(p2.y - p1.y);
        Rect rect(p1, width, height);
        rectVector.push_back(rect);
    }

    //...
    this->Refresh();

    Form::OnMouseUp(e);
}

void MainForm::OnPaint(const PaintEventArgs& e){

    //针对直线
    for (int i = 0; i < lineVector.size(); i++){
        e.Graphics.DrawLine(Pens.Red,
            lineVector[i].start.x,
            lineVector[i].start.y,
            lineVector[i].end.x,
            lineVector[i].end.y);
    }

    //针对矩形
    for (int i = 0; i < rectVector.size(); i++){
        e.Graphics.DrawRectangle(Pens.Red,
            rectVector[i].leftUp,
            rectVector[i].width,
            rectVector[i].height);
    }

    //...
    Form::OnPaint(e);
}

上述代码体现了一种分而治之的思想,在MainForm的类里面对显示界面的每个形状都进行了维护和处理。
在上述代码中只是对于矩形、直线和点三种形式的图形进行了绘制方式的设定,若此时想绘制一个圆,此时代码不能提供相对应的方法,这也就意味着代码将会发生变化。
需增加一个圆的类class Circle,同时还需要进行如下更改:

class MainForm : public Form {
private:
    Point p1;
    Point p2;

    vector<Line> lineVector;
    vector<Rect> rectVector;
    //改变
    vector<Circle> circleVector;

public:
    MainForm(){
        //...
    }
protected:

    virtual void OnMouseDown(const MouseEventArgs& e);
    virtual void OnMouseUp(const MouseEventArgs& e);
    virtual void OnPaint(const PaintEventArgs& e);
};


void MainForm::OnMouseDown(const MouseEventArgs& e){
    p1.x = e.X;
    p1.y = e.Y;

    //...
    Form::OnMouseDown(e);
}

void MainForm::OnMouseUp(const MouseEventArgs& e){
    p2.x = e.X;
    p2.y = e.Y;

    if (rdoLine.Checked){
        Line line(p1, p2);
        lineVector.push_back(line);
    }
    else if (rdoRect.Checked){
        int width = abs(p2.x - p1.x);
        int height = abs(p2.y - p1.y);
        Rect rect(p1, width, height);
        rectVector.push_back(rect);
    }
    //改变
    else if (...){
        //...
        circleVector.push_back(circle);
    }

    //...
    this->Refresh();

    Form::OnMouseUp(e);
}

void MainForm::OnPaint(const PaintEventArgs& e){

    //针对直线
    for (int i = 0; i < lineVector.size(); i++){
        e.Graphics.DrawLine(Pens.Red,
            lineVector[i].start.x,
            lineVector[i].start.y,
            lineVector[i].end.x,
            lineVector[i].end.y);
    }

    //针对矩形
    for (int i = 0; i < rectVector.size(); i++){
        e.Graphics.DrawRectangle(Pens.Red,
            rectVector[i].leftUp,
            rectVector[i].width,
            rectVector[i].height);
    }

    //改变
    //针对圆形
    for (int i = 0; i < circleVector.size(); i++){
        e.Graphics.DrawCircle(Pens.Red,
            circleVector[i]);
    }

    //...
    Form::OnPaint(e);
}

上述修改的代码段比较分散,如果站在软件工程的角度上来看,对于修改过的部分都需要在重新测试等一些列的操作,其实对于功能扩充来说其实面对着后期大量的工作。

关于抽象的设计思路:

class Shape{
public:
    virtual void Draw(const Graphics& g)=0;
    virtual ~Shape() { }
};


class Point{
public:
    int x;
    int y;
};

class Line: public Shape{
public:
    Point start;
    Point end;

    Line(const Point& start, const Point& end){
        this->start = start;
        this->end = end;
    }

    //实现自己的Draw,负责画自己
    virtual void Draw(const Graphics& g){
        g.DrawLine(Pens.Red,
            start.x, start.y,end.x, end.y);
    }

};

class Rect: public Shape{
public:
    Point leftUp;
    int width;
    int height;

    Rect(const Point& leftUp, int width, int height){
        this->leftUp = leftUp;
        this->width = width;
        this->height = height;
    }

    //实现自己的Draw,负责画自己
    virtual void Draw(const Graphics& g){
        g.DrawRectangle(Pens.Red,
            leftUp,width,height);
    }

};


class MainForm : public Form {
private:
    Point p1;
    Point p2;

    //针对所有形状
    vector<Shape*> shapeVector;

public:
    MainForm(){
        //...
    }
protected:

    virtual void OnMouseDown(const MouseEventArgs& e);
    virtual void OnMouseUp(const MouseEventArgs& e);
    virtual void OnPaint(const PaintEventArgs& e);
};


void MainForm::OnMouseDown(const MouseEventArgs& e){
    p1.x = e.X;
    p1.y = e.Y;

    //...
    Form::OnMouseDown(e);
}

void MainForm::OnMouseUp(const MouseEventArgs& e){
    p2.x = e.X;
    p2.y = e.Y;

    if (rdoLine.Checked){
        shapeVector.push_back(new Line(p1,p2));
    }
    else if (rdoRect.Checked){
        int width = abs(p2.x - p1.x);
        int height = abs(p2.y - p1.y);
        shapeVector.push_back(new Rect(p1, width, height));
    }
    else if (...){
        //...
        shapeVector.push_back(circle);
    }

    //...
    this->Refresh();

    Form::OnMouseUp(e);
}

void MainForm::OnPaint(const PaintEventArgs& e){

    //针对所有形状
    for (int i = 0; i < shapeVector.size(); i++){

        shapeVector[i]->Draw(e.Graphics); //多态调用,各负其责
    }

    //...
    Form::OnPaint(e);
}

对于每个形状,都增加了一个共同的父类Shape,同时对于图形来说应该如何绘制自己的这样的形状的函数都是由自己来实现的(Draw()函数)。MainForm只需要管理Shape的指针,而不再具体管理某一个具体的形状,实现了一次向上抽象的管理。同时onPaint的方法来说,也不在直接管理绘制的问题,而是调用了Shape中的虚函数Draw,通过多态调用来实现了MainForm来调用了自己绘制自己的方法。
此时若再次需要添加一个对圆形的处理,则其伪码如下:

class Shape{
public:
    virtual void Draw(const Graphics& g)=0;
    virtual ~Shape() { }
};


class Point{
public:
    int x;
    int y;
};

class Line: public Shape{
public:
    Point start;
    Point end;

    Line(const Point& start, const Point& end){
        this->start = start;
        this->end = end;
    }

    //实现自己的Draw,负责画自己
    virtual void Draw(const Graphics& g){
        g.DrawLine(Pens.Red,
            start.x, start.y,end.x, end.y);
    }

};

class Rect: public Shape{
public:
    Point leftUp;
    int width;
    int height;

    Rect(const Point& leftUp, int width, int height){
        this->leftUp = leftUp;
        this->width = width;
        this->height = height;
    }

    //实现自己的Draw,负责画自己
    virtual void Draw(const Graphics& g){
        g.DrawRectangle(Pens.Red,
            leftUp,width,height);
    }

};

//增加
class Circle : public Shape{
public:
    //实现自己的Draw,负责画自己
    virtual void Draw(const Graphics& g){
        g.DrawCircle(Pens.Red,
            ...);
    }

};

class MainForm : public Form {
private:
    Point p1;
    Point p2;

    //针对所有形状
    vector<Shape*> shapeVector;

public:
    MainForm(){
        //...
    }
protected:

    virtual void OnMouseDown(const MouseEventArgs& e);
    virtual void OnMouseUp(const MouseEventArgs& e);
    virtual void OnPaint(const PaintEventArgs& e);
};


void MainForm::OnMouseDown(const MouseEventArgs& e){
    p1.x = e.X;
    p1.y = e.Y;

    //...
    Form::OnMouseDown(e);
}

void MainForm::OnMouseUp(const MouseEventArgs& e){
    p2.x = e.X;
    p2.y = e.Y;

    if (rdoLine.Checked){
        shapeVector.push_back(new Line(p1,p2));
    }
    else if (rdoRect.Checked){
        int width = abs(p2.x - p1.x);
        int height = abs(p2.y - p1.y);
        shapeVector.push_back(new Rect(p1, width, height));
    }
    //改变
    else if (...){
        //...
        shapeVector.push_back(circle);
    }

    //...
    this->Refresh();

    Form::OnMouseUp(e);
}

void MainForm::OnPaint(const PaintEventArgs& e){

    //针对所有形状
    for (int i = 0; i < shapeVector.size(); i++){

        shapeVector[i]->Draw(e.Graphics); //多态调用,各负其责
    }

    //...
    Form::OnPaint(e);
}

以上可以看出解决了之前代码所存在问题,由于这样的抽象的思想,使得代码的变更更加容易,重用性得到了提升。
什么是好的软件设计呢?软件设计的金科玉律:复用。
变化是复用最大的天敌!面向对象的最大优势就在于:抵御变化。
(2)面向对象设计原则
<1>依赖倒置原则(DIP)
1)高层模块(稳定的)不应该依赖于低层模块(容易变化的),二者都应该依赖于抽象(稳定的);
2)抽象(稳定的)不应该依赖于实现细节(容易变化的),实现细节应该依赖于抽象(稳定的)。

<2>开放封闭原则(OCP)
1)对扩展开放,对更改封闭;
2)模块应该是可扩展的,但是不可修改。

<3>单一指责原则(SRP)
1)一个类应该仅有一个引起它变化的原因;
2)变化的方向隐含着类的责任。

<4>Liskov替换原则(LSP)
1)子类必须能够替换他们的基类(is-a);
2)集成表达抽象类型。

<5>接口隔离原则(ISP)
1)不应该强迫客户程序依赖他们不用的方法;
2)接口应该小而完备。

<6>优先使用对象组合,而不是类继承
1)类继承通常为“白盒复用”,对象组合通常为“黑箱复用”;
2)继承在某种程度上破坏了封装性,子类父类耦合度高;
3)而对象组合则只要求被组合的对象具有良好定义的接口,耦合度低。

<7>封装变化点
使用封装来创建对象之间的分界层,让设计者可以在分界层的一侧进行修改,而不会对另一侧产生不良的影响,从而实现层次间的松耦合。

<8>针对接口编程,而不是针对实现编程
1)不讲变量类型声明为某个特定的具体类,而是声明为某个接口;
2)客户程序无需获知对象的具体类型,只需要知道对象所具有的接口;
3)减少系统中个部分的依赖关系,从而实现“高内聚、松耦合”的类型设计方案。

产业强盛的标志:接口的标准化

GOF-23模式分类方式:
<1>依据目的分类
1)创建型(Creational)模式
2)结构型(Structural)模式
3)行为型(Behavioral)模式
<2>依据范围分类
1)类模式处理与子类的静态关系
2)对象模式处理间的动态关系
(3)模板方法
定一个操作中的算法的骨架(稳定的),而将一些步骤延迟(容易变化的)到子类中。Template Method使得子类可以不改变(复用)一个算法的结构即可重新定义(Override覆写)该算法的某写特定步骤
——《设计模式(GoF)》
对于库的开发人员来说,已经开发完成了所需要功能的1、3、5三个步骤;而如果要实现这个完整功能,需要应用程序的开发人员实现其中的2和4这两步。同时还需要应用程序的开发者在使用的时候依次来调用库和自己开发的这几个函数。

//程序库开发人员
class Library{

public:
    void Step1(){
        //...
    }

    void Step3(){
        //...
    }

    void Step5(){
        //...
    }
};

//应用程序开发人员
class Application{
public:
    bool Step2(){
        //...
    }

    void Step4(){
        //...
    }
};

int main()
{
    Library lib();
    Application app();

    lib.Step1();

    if (app.Step2()){
        lib.Step3();
    }

    for (int i = 0; i < 4; i++){
        app.Step4();
    }

    lib.Step5();

}

上述开发方式存在着以下问题:
<1>对应用程序的开发人员来说,他需要自己开发其中的第二和第五步的函数的开发。对于应用程序的开发者来说,要求是比较高的,必须对库中的函数情况比较了解,重写的两个函数的难度也相对较大,对于函数整体执行流程也不被库函数的开发人员所控制。
<2>库开发人员和应用程序的开发人员所开发的内容的耦合度很高,彼此相互交织在一起,还需要由用开发人员来组织整体调用流程。未来程序的扩展性和可维护性的难度都比较大。
通过模板方法可以解决以上问题:

//程序库开发人员
class Library{
public:
    //稳定 template method
    void Run(){

        Step1();

        if (Step2()) { //支持变化 ==> 虚函数的多态调用
            Step3();
        }

        for (int i = 0; i < 4; i++){
            Step4(); //支持变化 ==> 虚函数的多态调用
        }

        Step5();

    }
    virtual ~Library(){ }

protected:

    void Step1() { //稳定
        //.....
    }
    void Step3() {//稳定
        //.....
    }
    void Step5() { //稳定
        //.....
    }

    virtual bool Step2() = 0;//变化
    virtual void Step4() =0; //变化
};

//应用程序开发人员
class Application : public Library {
protected:
    virtual bool Step2(){
        //... 子类重写实现
    }

    virtual void Step4() {
        //... 子类重写实现
    }
};

int main()
    {
        Library* pLib=new Application();
        lib->Run();

        delete pLib;
    }
}

能够开发库的开发人员能增加了两个虚函数,同时库的开发人员定义了一个run方法,在run方法中,按照步骤调用了按照规则来调用的几个方法。
此时应用程序的开发人员只需要重写库函数中定义的函数。这样不仅避免了可能出现的缺陷,同时对于应用程序来说使用库的流程变得极其简单,只需要调用run方法即可,而不需要考虑具体的调用流程和规则。
对于Template Method来说,有一个前提,就是Run()方法必须要是稳定的。如果Run()不稳定,那么没有一个稳定的软件的骨架,就不存在这样一种设计模式。假定,所有方式都是稳定,那么其实也没有必要使用设计模式。
Template Method是一种非常基础性的设计模式,在面向对象的系统中有着大量的应用。他用最简洁的机制(虚函数的多态性)为很多应用程序的框架提供了灵活的扩展点,是代码复用方面的基本实现结构。
除了可以灵活对应子步骤的变化外,“不要调用我,让我来调用你”的反向控制结构是Template Method的典型应用。
在具体实现方面,被Template Method调用的虚方法可以具有实现,也可以没有任何实现(抽象方法、纯虚方法),一般推荐将他们设置为protected方法。
(4)策略模式
定义一系列算法,把他们一个个封装起来,并且是他们可以互相替换(变化)。该模式似的算法可以独立于使用它的客户程序(稳定的)而变化(扩展,子类化)。
——《设计模式》GoF
策略模式的动机为:在软件构建的过程中,某些对象使用的算法可能是多种多样的,经常改变,如果将这些算法都编码到对象中,将会使对象变得异常复杂;而且有时候支持不使用的算法也是一种性能负担。
比如当前有一种税种的逻辑处理机制:

enum TaxBase {
    CN_Tax,
    US_Tax,
    DE_Tax,
};

class SalesOrder{
    TaxBase tax;
public:
    double CalculateTax(){
        //...

        if (tax == CN_Tax){
            //CN***********
        }
        else if (tax == US_Tax){
            //US***********
        }
        else if (tax == DE_Tax){
            //DE***********
        }
        //....
     }

};

若此时需要增加一种税种:

enum TaxBase {
    CN_Tax,
    US_Tax,
    DE_Tax,
    FR_Tax       //更改
};

class SalesOrder{
    TaxBase tax;
public:
    double CalculateTax(){
        //...

        if (tax == CN_Tax){
            //CN***********
        }
        else if (tax == US_Tax){
            //US***********
        }
        else if (tax == DE_Tax){
            //DE***********
        }
        else if (tax == FR_Tax){  //更改
            //...
        }

        //....
     }

};

不但需要修改TaxBase中的参数,还需要在SalseOrder中添加关于新增加税种的一个判断,然后才能进行相应的计算。此时违反了对开闭原则,同时也违反了反向依赖的原则。
使用策略模式:

class TaxStrategy{
public:
    virtual double Calculate(const Context& context)=0;
    virtual ~TaxStrategy(){}
};


class CNTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};

class USTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};

class DETax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};



class SalesOrder{
private:
    TaxStrategy* strategy;

public:
    SalesOrder(StrategyFactory* strategyFactory){
        this->strategy = strategyFactory->NewStrategy();
    }
    ~SalesOrder(){
        delete this->strategy;
    }

    public double CalculateTax(){
        //...
        Context context();

        double val =
            strategy->Calculate(context); //多态调用
        //...
    }

};
此时再增加一种税种:
class TaxStrategy{
public:
    virtual double Calculate(const Context& context)=0;
    virtual ~TaxStrategy(){}
};


class CNTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};

class USTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};

class DETax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //***********
    }
};



//扩展
//*********************************
class FRTax : public TaxStrategy{
public:
    virtual double Calculate(const Context& context){
        //.........
    }
};


class SalesOrder{
private:
    TaxStrategy* strategy;

public:
    SalesOrder(StrategyFactory* strategyFactory){
        this->strategy = strategyFactory->NewStrategy();
    }
    ~SalesOrder(){
        delete this->strategy;
    }

    public double CalculateTax(){
        //...
        Context context();

        double val =
            strategy->Calculate(context); //多态调用
        //...
    }

};

SalesOrder的CalculateTax函数多态调用了TaxStrategy类中的Calucate方法。而TaxStrategy的每个子类都是一种新的税种计算方式,每个税种计算中还实现了自己对应的税种计算的方法。
在扩展的时候,需要增加TaxStrategy的子类就行了,不需要在改变SalesOrder的中的代码了。
总结:
<1>Strategy及其子类为组件提供了一系列可重用的算法,从而可以使得类型在运行时方便的根据需要在各个算法之间进行切换。
<2>Strategy模式提供了用条件判断语句的另一种选择,消除条件判断语句,就是在解耦合。含有许多条件判断语句的代码通常都需要Strategy模式。尤其是条件判断语句在未来会有增加可能性的时候,应该优先考虑Strategy模式。
<3>如果Strategy对象没有实例变量,那么各个上下文可以共享同一个Strategy对象,从而节省对象的开销。
(5)观察者模式
观察者模式的动机:
在软件构建的过程中,我们需要为某些对象建立一种“通知依赖关系”——一个对象(目标对象)的状态发生改变,所有的以来对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使得软件不能很好的抵御变化。
使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
以下是一个文件分割器的伪码,其中存在一个进度条:

class FileSplitter
{
    string m_filePath;
    int m_fileNumber;
    ProgressBar* m_progressBar;     // 产生编译式依赖,实际是一个通知

public:
    FileSplitter(const string& filePath, int fileNumber, ProgressBar* progressBar) :
        m_filePath(filePath),
        m_fileNumber(fileNumber),
        m_progressBar(progressBar){

    }

    void split(){

        //1.读取大文件

        //2.分批次向小文件中写入
        for (int i = 0; i < m_fileNumber; i++){
            //...
    // 更新进度条数据
            float progressValue = m_fileNumber;
            progressValue = (i + 1) / progressValue;
            m_progressBar->setValue(progressValue);
        }

    }
};


class MainForm : public Form
{
    TextBox* txtFilePath;
    TextBox* txtFileNumber;
    ProgressBar* progressBar;

public:
    void Button1_Click(){

        string filePath = txtFilePath->getText();
        int number = atoi(txtFileNumber->getText().c_str());

        FileSplitter splitter(filePath, number, progressBar);

        splitter.split();

    }
};

在以上的设计方法上,前端界面依赖了一个ProgressBar,是一种实现细节,而实现细节是极易变化的。同时其中的耦合性也很高,只要需要更改显示的进度的情况,就需要更改前端的界面。
由以上的依赖关系不难看出,对于分隔文件的算法相对来说稳定,但是却依赖了一个不稳定的ProgressBar。
那么如果对于界面修改的部分来说,他面对的是一个通知的数组,每个数组里面都是一个抽象的通知的接口,就可以使用多态性来解决解耦的问题。

class IProgress{
public:
    virtual void DoProgress(float value)=0;
    virtual ~IProgress(){}
};


class FileSplitter
{
    string m_filePath;
    int m_fileNumber;

    List<IProgress*>  m_iprogressList; // 抽象通知机制,支持多个观察者

public:
    FileSplitter(const string& filePath, int fileNumber) :
        m_filePath(filePath),
        m_fileNumber(fileNumber){

    }


    void split(){

        //1.读取大文件

        //2.分批次向小文件中写入
        for (int i = 0; i < m_fileNumber; i++){
            //...

            float progressValue = m_fileNumber;
            progressValue = (i + 1) / progressValue;
            onProgress(progressValue);//发送通知
        }

    }


    void addIProgress(IProgress* iprogress){
        m_iprogressList.push_back(iprogress);
    }

    void removeIProgress(IProgress* iprogress){
        m_iprogressList.remove(iprogress);
    }


protected:
    virtual void onProgress(float value){

        List<IProgress*>::iterator itor=m_iprogressList.begin();

        while (itor != m_iprogressList.end() )
            (*itor)->DoProgress(value); //更新进度条
            itor++;
        }
    }
};



class MainForm : public Form, public IProgress(抽象基类)
{
    TextBox* txtFilePath;
    TextBox* txtFileNumber;

    ProgressBar* progressBar;

public:
    void Button1_Click(){

        string filePath = txtFilePath->getText();
        int number = atoi(txtFileNumber->getText().c_str());

        ConsoleNotifier cn;

        FileSplitter splitter(filePath, number);

        splitter.addIProgress(this); //订阅通知
        splitter.addIProgress(&cn); //订阅通知

        splitter.split();

        splitter.removeIProgress(this);

    }

    virtual void DoProgress(float value){
        progressBar->setValue(value);
    }
};

class ConsoleNotifier : public IProgress {
public:
    virtual void DoProgress(float value){
        cout << ".";
    }
};

对于这个解决方案来说,将ProgressBar进行了抽象,对于FileSplitter只需要依赖一个list,由一个list来维护IProgressBar* 的列表,对于每个ProgressBar控件来说,只需要继承IProgressBar即可。
对于MainForm来说,只需要调用比较稳定的FileSplitter既可以来调用其中的分隔文件的方法,而在FileSplitter中已经实现了遍历list中所有IProgressBar并通知的功能,因此实现了观察者模式的要求。
要点总结:
使用面向对象的抽象,Observer模式使得我们可以独立地改变目标与观察者,从而使二者之间的依赖关系达到松耦合。目标发送通知时,无需制定观察者,通知(可以携带通知信息作为参数)会自动传播。观察者自己决定是否需要订阅通知,目标对象对此一无所知。Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。
(6)装饰模式
动态(组合)地给一个对象增加一些额外的指责。就增加功能而言,Decorator模式比生成子类(继承)更为灵活(消除重复代码、减少子类个数)。
——《设计模式》GoF
装饰模式动机:
在某些情况下,我们可能会“过度的使用继承来扩展对象的功能”,由于继承为类型引入静态特质,使得这种扩展方式缺乏灵活性;并且随着子类的增多(扩展功能的在增多),各种子类的组合(扩展功能的组合)会导致子类的膨胀。
在下面有一组伪代码,这组伪代码主要描述一些流的操作,比如文件流、网络流、内存流等,如果我们对于流提出更多的要求,比如需要对文件流加密、对网络流加密以及内存流的加密,同时如果考虑缓冲的情况下,那么这些流都可能会要缓冲,也可能继续的组合,产生加密缓冲网络流等等的操作。

//业务操作
class Stream{
public:
    virtual char Read(int number)=0;
    virtual void Seek(int position)=0;
    virtual void Write(char data)=0;

    virtual ~Stream(){}
};

//主体类
class FileStream: public Stream{
public:
    virtual char Read(int number){
        //读文件流
    }
    virtual void Seek(int position){
        //定位文件流
    }
    virtual void Write(char data){
        //写文件流
    }

};

class NetworkStream :public Stream{
public:
    virtual char Read(int number){
        //读网络流
    }
    virtual void Seek(int position){
        //定位网络流
    }
    virtual void Write(char data){
        //写网络流
    }

};

class MemoryStream :public Stream{
public:
    virtual char Read(int number){
        //读内存流
    }
    virtual void Seek(int position){
        //定位内存流
    }
    virtual void Write(char data){
        //写内存流
    }

};

//扩展操作
class CryptoFileStream :public FileStream{
public:
    virtual char Read(int number){

        //额外的加密操作...
        FileStream::Read(number);//读文件流

    }
    virtual void Seek(int position){
        //额外的加密操作...
        FileStream::Seek(position);//定位文件流
        //额外的加密操作...
    }
    virtual void Write(byte data){
        //额外的加密操作...
        FileStream::Write(data);//写文件流
        //额外的加密操作...
    }
};

class CryptoNetworkStream : :public NetworkStream{
public:
    virtual char Read(int number){

        //额外的加密操作...
        NetworkStream::Read(number);//读网络流
    }
    virtual void Seek(int position){
        //额外的加密操作...
        NetworkStream::Seek(position);//定位网络流
        //额外的加密操作...
    }
    virtual void Write(byte data){
        //额外的加密操作...
        NetworkStream::Write(data);//写网络流
        //额外的加密操作...
    }
};

class CryptoMemoryStream : public MemoryStream{
public:
    virtual char Read(int number){

        //额外的加密操作...
        MemoryStream::Read(number);//读内存流
    }
    virtual void Seek(int position){
        //额外的加密操作...
        MemoryStream::Seek(position);//定位内存流
        //额外的加密操作...
    }
    virtual void Write(byte data){
        //额外的加密操作...
        MemoryStream::Write(data);//写内存流
        //额外的加密操作...
    }
};

class BufferedFileStream : public FileStream{
    //...
};

class BufferedNetworkStream : public NetworkStream{
    //...
};

class BufferedMemoryStream : public MemoryStream{
    //...
}




class CryptoBufferedFileStream :public FileStream{
public:
    virtual char Read(int number){

        //额外的加密操作...
        //额外的缓冲操作...
        FileStream::Read(number);//读文件流
    }
    virtual void Seek(int position){
        //额外的加密操作...
        //额外的缓冲操作...
        FileStream::Seek(position);//定位文件流
        //额外的加密操作...
        //额外的缓冲操作...
    }
    virtual void Write(byte data){
        //额外的加密操作...
        //额外的缓冲操作...
        FileStream::Write(data);//写文件流
        //额外的加密操作...
        //额外的缓冲操作...
    }
};



void Process(){

        //编译时装配
    CryptoFileStream *fs1 = new CryptoFileStream();

    BufferedFileStream *fs2 = new BufferedFileStream();

    CryptoBufferedFileStream *fs3 =new CryptoBufferedFileStream();

}

上述代码的复用性不高,对于每种方式都有大量的重复代码。这样的设计会导致class的数量激增。


如果把那些因为组合而产生的class(比如CryptoFileStream)的继承关系先打断,而是改为使用内部持有原先父类的指针,比如CryptoFileStream原先继承了FileStream,改为持有FileStream。这时候原class中的调用父类的函数全都可以改为使用自己持有的指针对象来调用。此时代码的重复性大幅度提高,只是每一个组合型的class中持有的那个指针不属于同一个(比如,持有的为FileStream,NetworkStream以及BufferStream的指针),如果观察一下,也不难发现,其实FileStream,NetworkStream以及BufferStream又都有同一个父类Stream,所以利用多态性,就可以将每个组合类型中的持有的指针变量,声明为他们共同的父类,利用多态性只需要让父类的指向子类即可(Stream stream = new FileStream();),这就实现了,编译时都为一样的,在运行时提供不同的对象。此时会发现具有大量重复的类,合并了同类项,减少很多不必要的组合类。

//业务操作
class Stream{

public:
    virtual char Read(int number)=0;
    virtual void Seek(int position)=0;
    virtual void Write(char data)=0;

    virtual ~Stream(){}
};

//主体类
class FileStream: public Stream{
public:
    virtual char Read(int number){
        //读文件流
    }
    virtual void Seek(int position){
        //定位文件流
    }
    virtual void Write(char data){
        //写文件流
    }

};

class NetworkStream :public Stream{
public:
    virtual char Read(int number){
        //读网络流
    }
    virtual void Seek(int position){
        //定位网络流
    }
    virtual void Write(char data){
        //写网络流
    }

};

class MemoryStream :public Stream{
public:
    virtual char Read(int number){
        //读内存流
    }
    virtual void Seek(int position){
        //定位内存流
    }
    virtual void Write(char data){
        //写内存流
    }

};

//扩展操作


class CryptoStream: public Stream {

    Stream* stream;//...

public:
    CryptoStream(Stream* stm):stream(stm){

    }


    virtual char Read(int number){

        //额外的加密操作...
        stream->Read(number);//读文件流
    }
    virtual void Seek(int position){
        //额外的加密操作...
        stream::Seek(position);//定位文件流
        //额外的加密操作...
    }
    virtual void Write(byte data){
        //额外的加密操作...
        stream::Write(data);//写文件流
        //额外的加密操作...
    }
};



class BufferedStream : public Stream{

    Stream* stream;//...

public:
    BufferedStream(Stream* stm):stream(stm){

    }
    //...
};





void Process(){

    //运行时装配
    FileStream* s1=new FileStream();
    CryptoStream* s2=new CryptoStream(s1);

    BufferedStream* s3=new BufferedStream(s1);

    BufferedStream* s4=new BufferedStream(s2);



}

观察上面的代码来说,其实有很多class里面都持有同样的变量stream,那么这些类可以向上再抽象一个父类,将strem 在父类中声明。
//业务操作
class Stream{

public:
    virtual char Read(int number)=0;
    virtual void Seek(int position)=0;
    virtual void Write(char data)=0;

    virtual ~Stream(){}
};

//主体类
class FileStream: public Stream{
public:
    virtual char Read(int number){
        //读文件流
    }
    virtual void Seek(int position){
        //定位文件流
    }
    virtual void Write(char data){
        //写文件流
    }

};

class NetworkStream :public Stream{
public:
    virtual char Read(int number){
        //读网络流
    }
    virtual void Seek(int position){
        //定位网络流
    }
    virtual void Write(char data){
        //写网络流
    }

};

class MemoryStream :public Stream{
public:
    virtual char Read(int number){
        //读内存流
    }
    virtual void Seek(int position){
        //定位内存流
    }
    virtual void Write(char data){
        //写内存流
    }

};

//扩展操作

DecoratorStream: public Stream{
protected:
    Stream* stream;//...

    DecoratorStream(Stream * stm):stream(stm){

    }

};

class CryptoStream: public DecoratorStream {

public:
    CryptoStream(Stream* stm):DecoratorStream(stm){

    }


    virtual char Read(int number){

        //额外的加密操作...
        stream->Read(number);//读文件流
    }
    virtual void Seek(int position){
        //额外的加密操作...
        stream::Seek(position);//定位文件流
        //额外的加密操作...
    }
    virtual void Write(byte data){
        //额外的加密操作...
        stream::Write(data);//写文件流
        //额外的加密操作...
    }
};

class BufferedStream : public DecoratorStream{

    Stream* stream;//...

public:
    BufferedStream(Stream* stm):DecoratorStream(stm){

    }
    //...
};
void Process(){
    //运行时装配
    FileStream* s1=new FileStream();

    CryptoStream* s2=new CryptoStream(s1);

    BufferedStream* s3=new BufferedStream(s1);

    BufferedStream* s4=new BufferedStream(s2);
}

FileStream、NetworkStream还有BufferStream可以独立执行任务,而加密和缓存其实就是对他们的增强。只需要对Stream(这三个类的共同父类)增加加密功能、增加缓冲的功能,而在使用的时候自由组合即可,而不需要把代码全都写出来。
总结:
<1>通过采用组合而非继承的手法,Decorator模式实现了在运动时动态扩展对象功能的能力,而且可以根据需要扩展多个功能。避免了使用集成带来的“灵活性差”和“多子类衍生的问题”。
<2>Decorator类在接口上变现为is-a Component的继承关系,即Decorator类集成了Component类所具有的接口。但在实现上又表现为has-a Component的组合关系,即Decorator类又使用了另外一个Component类。
<3>Decorator模式的目的并非解决“多子类衍生的多继承”问题,Decorator模式应用的重要点在于解决“主体类在多个方向上的扩展功能”——是为“装饰”的含义。
(7)桥模式
将抽象部分(业务功能)与实现部分(平台实现)分离,是他们都可以独立地变化。
——《设计模式》GoF

桥模式的动机:
由于某些类型的固有的实现逻辑,使得它们具有两个变化的维度,乃至多个维度的变化。

假设有如下的通信的伪码描述包括登陆、发信息、播放音乐等等的功能,同时也支持不同的平台的支持,比如PC、移动端等,其中版本也具有不同的功能,比如精简版和完整版。

class Messager{
public:
    virtual void Login(string username, string password)=0;
    virtual void SendMessage(string message)=0;
    virtual void SendPicture(Image image)=0;

    virtual void PlaySound()=0;
    virtual void DrawShape()=0;
    virtual void WriteText()=0;
    virtual void Connect()=0;

    virtual ~Messager(){}
};

//平台实现

class PCMessagerBase : public Messager{
public:

    virtual void PlaySound(){
        //**********
    }
    virtual void DrawShape(){
        //**********
    }
    virtual void WriteText(){
        //**********
    }
    virtual void Connect(){
        //**********
    }
};

class MobileMessagerBase : public Messager{
public:

    virtual void PlaySound(){
        //==========
    }
    virtual void DrawShape(){
        //==========
    }
    virtual void WriteText(){
        //==========
    }
    virtual void Connect(){
        //==========
    }
};

//业务抽象

class PCMessagerLite : public PCMessagerBase {
public:

    virtual void Login(string username, string password){

        PCMessagerBase::Connect();
        //........
    }
    virtual void SendMessage(string message){

        PCMessagerBase::WriteText();
        //........
    }
    virtual void SendPicture(Image image){

        PCMessagerBase::DrawShape();
        //........
    }
};

class PCMessagerPerfect : public PCMessagerBase {
public:

    virtual void Login(string username, string password){

        PCMessagerBase::PlaySound();
        //********
        PCMessagerBase::Connect();
        //........
    }
    virtual void SendMessage(string message){

        PCMessagerBase::PlaySound();
        //********
        PCMessagerBase::WriteText();
        //........
    }
    virtual void SendPicture(Image image){

        PCMessagerBase::PlaySound();
        //********
        PCMessagerBase::DrawShape();
        //........
    }
};

class MobileMessagerLite : public MobileMessagerBase {
public:

    virtual void Login(string username, string password){

        MobileMessagerBase::Connect();
        //........
    }
    virtual void SendMessage(string message){

        MobileMessagerBase::WriteText();
        //........
    }
    virtual void SendPicture(Image image){

        MobileMessagerBase::DrawShape();
        //........
    }
};

class MobileMessagerPerfect : public MobileMessagerBase {
public:

    virtual void Login(string username, string password){

        MobileMessagerBase::PlaySound();
        //********
        MobileMessagerBase::Connect();
        //........
    }
    virtual void SendMessage(string message){

        MobileMessagerBase::PlaySound();
        //********
        MobileMessagerBase::WriteText();
        //........
    }
    virtual void SendPicture(Image image){

        MobileMessagerBase::PlaySound();
        //********
        MobileMessagerBase::DrawShape();
        //........
    }
};

void Process(){
        //编译时装配
        Messager *m =
            new MobileMessagerPerfect();
}

对于以上那个代码来说,组合的过程中会存在大量的重复代码,同时在组合的过程中,会产生大量的类的组合而产生的类。
依靠Decorator的经验,把大量的继承改编为持有原父类的指针变量,并且可以声明为共同的父类,利用多态性将静态绑定改为动态绑定。
并且观察发现,对于Message类的功能应该拆分开。

class Messager{
protected:
     MessagerImp* messagerImp;//...
public:
    virtual void Login(string username, string password)=0;
    virtual void SendMessage(string message)=0;
    virtual void SendPicture(Image image)=0;

    virtual ~Messager(){}
};

class MessagerImp{
public:
    virtual void PlaySound()=0;
    virtual void DrawShape()=0;
    virtual void WriteText()=0;
    virtual void Connect()=0;

    virtual MessagerImp(){}
};

//平台实现 n
class PCMessagerImp : public MessagerImp{
public:

    virtual void PlaySound(){
        //**********
    }
    virtual void DrawShape(){
        //**********
    }
    virtual void WriteText(){
        //**********
    }
    virtual void Connect(){
        //**********
    }
};
class MobileMessagerImp : public MessagerImp{
public:

    virtual void PlaySound(){
        //==========
    }
    virtual void DrawShape(){
        //==========
    }
    virtual void WriteText(){
        //==========
    }
    virtual void Connect(){
        //==========
    }
};

//业务抽象 m

//类的数目:1+n+m

class MessagerLite :public Messager {


public:

    virtual void Login(string username, string password){

        messagerImp->Connect();
        //........
    }
    virtual void SendMessage(string message){

        messagerImp->WriteText();
        //........
    }
    virtual void SendPicture(Image image){

        messagerImp->DrawShape();
        //........
    }
};

class MessagerPerfect  :public Messager {
public:

    virtual void Login(string username, string password){

        messagerImp->PlaySound();
        //********
        messagerImp->Connect();
        //........
    }
    virtual void SendMessage(string message){

        messagerImp->PlaySound();
        //********
        messagerImp->WriteText();
        //........
    }
    virtual void SendPicture(Image image){

        messagerImp->PlaySound();
        //********
        messagerImp->DrawShape();
        //........
    }
};

void Process(){
    //运行时装配
    MessagerImp* mImp=new PCMessagerImp();
    Messager *m =new Messager(mImp);
}

要点总结:
Bridge模式使用“对象间的组合关系”解耦了抽象和实现之间固有的绑定关系,使得抽象和实现可以沿着各自的维度来变化。所谓抽象和实现沿着各自维度的变化,即“子类化”他们。
Bridge模式有时候类似于多继承方案,但是多继承方案往往违背单一指责原则(即一个类只有一个变化的原因),复用性较差。Bridge模式是比多继承方案更好的解决方法。
Bridge模式的应用一般在“两个非常强的变化维度”,有时一个类也有多于两个的变化维度,这是可以使用Bridge的扩展模式。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容

  • 本周课程主要内容为:C++设计模式简介、面向对象设计八大原则和DOF-23设计模式中的5中模式,包括templat...
    cayhw阅读 482评论 0 3
  • 本周内容(1)设计模式简介(2)面向对象设计原则(3)模板方法(4)策略模式(5)观察者模式(6)装饰模式(7)桥...
    小小出大炮阅读 928评论 0 2
  • 设计模式汇总 一、基础知识 1. 设计模式概述 定义:设计模式(Design Pattern)是一套被反复使用、多...
    MinoyJet阅读 3,879评论 1 15
  • 设计模式简介: 什么是设计模式?每一个模式描述了一个在我们周围不断重复发生的问题,以及该问题的解决方案的核心。这样...
    hjsadam阅读 328评论 0 2
  • 设计模式基本原则 开放-封闭原则(OCP),是说软件实体(类、模块、函数等等)应该可以拓展,但是不可修改。开-闭原...
    西山薄凉阅读 3,727评论 3 13