Boolan C++设计模式 第二周笔记

来源:互联网 发布:杀破狼js 无损 编辑:程序博客网 时间:2024/05/29 05:55


23种设计模式


“对象创建”模式

通过“对象创建”模式绕开new,来避免对象创建(new)过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象之后的第一步工作。

典型模式:

  • Factory Method
  • Abstract Factory
  • Prototype
  • Builder

6. Factory Method工厂方法


模式定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。

Factory Method使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。

                                                                                             --《设计模式》Gof

动机(Motivation)
  • 在软件系统中,经常面临这创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。
  • 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合。
在先前的课程中,我们分析过一个文件分割的例子,如果文件分割的算法有很多种的话,如何实现灵活的切换呢?
先看一下代码:
class ISplitter{public:    virtual void split()=0;    virtual ~ISplitter(){}};class BinarySplitter : public ISplitter{};class TxtSplitter: public ISplitter{};class PictureSplitter: public ISplitter{};class VideoSplitter: public ISplitter{};class MainForm : public Form{    TextBox* txtFilePath;    TextBox* txtFileNumber;    ProgressBar* progressBar;public:    void Button1_Click(){        ISplitter * splitter=            new BinarySplitter();//依赖具体类        splitter->split();    }};
从面向对象设计的思想出发,我们设计一个抽象基类(稳定),子类(变化)在创建对象过程中使用基类对象来创建,这样可以让我们将对象的类型判断放到运行时去决定。
这种编程方法我们称为面向接口编程,不过我们会发现一个问题:抽象类不能直接用于对象创建,因此在创建对象的时候,需要在指出具体的实现细节,例如:
ISplitter * splitter=new BinarySplitter();
这样的创建方法显然违背了之前提过的依赖倒置原则,那是否存在一种方法来绕开这种细节依赖呢?我们是否可以参考上面的分割算法实现方法,通过一个抽象基类来实现对象创建?
首先,来看一下创建对象的三种方法:
  • 直接创建:BinarySplitter bs();
  • 直接调用new函数;
  • 用“绕”的方法,让函数返回一个对象;
第一种和第二种方法都依赖于具体实现细节,因此我们通过第三种方法来创建对象。这里我们使用抽象基类来实现创建对象细节依赖的延迟,将细节依赖排除在我们的程序之外,让它在其他地方实现细节依赖。具体代码如下:
//抽象类class ISplitter{public:    virtual void split()=0;    virtual ~ISplitter(){}};//工厂基类class SplitterFactory{public:    virtual ISplitter* CreateSplitter()=0;    virtual ~SplitterFactory(){}};//具体类class BinarySplitter : public ISplitter{};class TxtSplitter: public ISplitter{};class PictureSplitter: public ISplitter{};class VideoSplitter: public ISplitter{};//具体工厂class BinarySplitterFactory: public SplitterFactory{public:    virtual ISplitter* CreateSplitter(){        return new BinarySplitter();    }};class TxtSplitterFactory: public SplitterFactory{public:    virtual ISplitter* CreateSplitter(){        return new TxtSplitter();    }};class PictureSplitterFactory: public SplitterFactory{public:    virtual ISplitter* CreateSplitter(){        return new PictureSplitter();    }};class VideoSplitterFactory: public SplitterFactory{public:    virtual ISplitter* CreateSplitter(){        return new VideoSplitter();    }};class MainForm : public Form{    SplitterFactory*  factory;//工厂public:    MainForm(SplitterFactory*  factory){        this->factory=factory;    }    void Button1_Click(){        ISplitter * splitter=            factory->CreateSplitter(); //多态new        splitter->split();    }};
SplitterFactory*  factory;//工厂ISplitter * splitter=factory->CreateSplitter(); //多态new
我们将创建对象这种方法直接抽象到了一个抽象基类(工厂基类)中,同时子类通过各自的具体方法(细节依赖)来实现对象创建。
通过这种方法,我们在创建对象的时候,不再依赖具体的类,只有在运行时才进行具体类型的判断,从而实现依赖倒置原则。
上述的每个具体工厂中使用new函数来实现对象创建,这里并不违背依赖倒置原则,因为具体对象的创建最后还是要依赖细节实现,我们不可能做到将这些变化消灭掉,而是将变化约束到一个更加可控的区域。

结构:



要点总结:

  • Factory Method 模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。
  • Factory Method模式通过面对对象的手法,将所要创建的对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种紧耦合关系。
  • Factory Method模式解决“单个对象”的需求变化。缺点在于要求创建方法/参数相同。


7.抽象工厂 (Abstract Factory)


模式定义

提供一个接口,让该接口负责创建一系列“性关系或相互依赖的对象”,无需指定他们的具体类。

                                                                                                      --《设计模式》Gof

动机(Motivation)
  • 在软件系统中,经常面临着“一系列相互依赖的对象”的创建工作;同时,由于需求的变化,往往存在更多系列对象的创建工作。
  • 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合。

抽象工厂(Abstract Factory)是从工厂模式中衍生出来的,工厂模式是对单一对象的创建进行处理,实现依赖倒置,而抽象工厂模式需要处理“一系列相互依赖的对象”的创建工作。

下面以数据连接系统为例进行设计模式的说明,要实现数据连接系统需要connecttion对象,command对象等等,同时不同的数据库所需的对象也会有变化。
如果我们不采用任何设计模式,那么代码应该如下所示:
class EmployeeDAO{    public:    vector<EmployeeDO> GetEmployees(){        SqlConnection* connection =            new SqlConnection();        connection->ConnectionString = "...";        SqlCommand* command =            new SqlCommand();        command->CommandText="...";        command->SetConnection(connection);        SqlDataReader* reader = command->ExecuteReader();        while (reader->Read()){        }    }};
很明显上面的代码不满足依赖倒置原则,难以满足变化的要求。
参考工厂模式,我们很快就能找到解决方法,将数据库的具体实现向上抽象,创建抽象基类,具体代码如下:

//数据库访问有关的基类class IDBConnection{    };class IDBConnectionFactory{public:    virtual IDBConnection* CreateDBConnection()=0;};class IDBCommand{    };class IDBCommandFactory{public:    virtual IDBCommand* CreateDBCommand()=0;};class IDataReader{    };class IDataReaderFactory{public:    virtual IDataReader* CreateDataReader()=0;};//支持SQL Serverclass SqlConnection: public IDBConnection{    };class SqlConnectionFactory:public IDBConnectionFactory{    };class SqlCommand: public IDBCommand{    };class SqlCommandFactory:public IDBCommandFactory{    };class SqlDataReader: public IDataReader{    };class SqlDataReaderFactory:public IDataReaderFactory{    };//支持Oracleclass OracleConnection: public IDBConnection{    };class OracleCommand: public IDBCommand{    };class OracleDataReader: public IDataReader{    };class EmployeeDAO{    IDBConnectionFactory* dbConnectionFactory;    IDBCommandFactory* dbCommandFactory;    IDataReaderFactory* dataReaderFactory;        public:    vector<EmployeeDO> GetEmployees(){        IDBConnection* connection =            dbConnectionFactory->CreateDBConnection();        connection->ConnectionString("...");        IDBCommand* command =            dbCommandFactory->CreateDBCommand();        command->CommandText("...");        command->SetConnection(connection); //关联性        IDBDataReader* reader = command->ExecuteReader(); //关联性        while (reader->Read()){        }    }};

上面的代码将数据连接系统的具体实现用工厂模式实现,为所有与数据库访问相关的对象创建了抽象基类,从而使得这些对象的创建实现多态性,但是这时候出现了一个新问题:
这些对象需要一起配套使用,也就是说SQL的connection对象必须和SQL的command对象一起使用,不然就会在运行时报错。
既然它们必须在一起使用,那我们就把它们合并就可以了,具体代码如下:

//数据库访问有关的基类class IDBConnection{    };class IDBCommand{    };class IDataReader{    };class IDBFactory{public:    virtual IDBConnection* CreateDBConnection()=0;    virtual IDBCommand* CreateDBCommand()=0;    virtual IDataReader* CreateDataReader()=0;    };//支持SQL Serverclass SqlConnection: public IDBConnection{    };class SqlCommand: public IDBCommand{    };class SqlDataReader: public IDataReader{    };class SqlDBFactory:public IDBFactory{public:    virtual IDBConnection* CreateDBConnection()=0;    virtual IDBCommand* CreateDBCommand()=0;    virtual IDataReader* CreateDataReader()=0; };//支持Oracleclass OracleConnection: public IDBConnection{    };class OracleCommand: public IDBCommand{    };class OracleDataReader: public IDataReader{    };class EmployeeDAO{    IDBFactory* dbFactory;    public:    vector<EmployeeDO> GetEmployees(){        IDBConnection* connection =            dbFactory->CreateDBConnection();        connection->ConnectionString("...");        IDBCommand* command =            dbFactory->CreateDBCommand();        command->CommandText("...");        command->SetConnection(connection); //关联性        IDBDataReader* reader = command->ExecuteReader(); //关联性        while (reader->Read()){        }    }};

从上面的代码可以看出,我们将配套的一系列对象,打包到一个工厂中进行封装,其实就是工厂模式的一个特化,用于处理一系列相互依赖的对象。


结构:


要点总结:
  • 如果没有应对“多系列对象构建”的需求变化,则没有必要使用 Abstract Factory 模式,这时候使用简单的工厂完全可以。
  • “系列对象”指的是在某一特定系列下的对象之间具有相互依赖或作用的关系。不同系列的对象之间不能相互依赖。
  • Abstract Factory 模式主要在于应对“新系列”的需求变动,其缺点在于难以应对“新对象”的需求变动。

8. 原型模式(Prototype)

模式定义

使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。

                                                                                                      --《设计模式》Gof

动机(Motivation)
  • 在软件系统中,经常面临着“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有着比较稳定一致的接口。
  • 如何应对这种变化?如何向“客户程序(使用这些对象的程序)”隔离出“这些易变对象”,从而使得“依赖这些易变对象的客户程序”不随着需求改变而改变?
原型模式也是属于对象创建模式,它用来处理某些结构复杂对象的创建工作,这也是与工厂模式最大的区别。
如果我们要创建的对象比较简单的话,使用工厂模式new一下就可以了,但是当对象比较复杂,它的初始状态不是我们最想用的时候,我们可以调用拷贝构造函数来深拷贝某个比较符合我们要求的对象。当然所谓的复杂是相对而言的,应当根据实际情况确定。
具体代码如下:
//抽象类class ISplitter{public:    virtual void split()=0;    virtual ISplitter* clone()=0; //通过克隆自己来创建对象        virtual ~ISplitter(){}};//具体类class BinarySplitter : public ISplitter{public:    virtual ISplitter* clone(){        return new BinarySplitter(*this);    }};class TxtSplitter: public ISplitter{public:    virtual ISplitter* clone(){        return new TxtSplitter(*this);    }};class PictureSplitter: public ISplitter{public:    virtual ISplitter* clone(){        return new PictureSplitter(*this);    }};class VideoSplitter: public ISplitter{public:    virtual ISplitter* clone(){        return new VideoSplitter(*this);    }};class MainForm : public Form{    ISplitter*  prototype;//原型对象public:        MainForm(ISplitter*  prototype){        this->prototype=prototype;    }    void Button1_Click(){ISplitter * splitter=            prototype->clone(); //克隆原型                splitter->split();        }};

由上面的代码可以看出,原型模式主要是通过子类重写拷贝构造函数来实现对象的创建。

结构:



要点总结:

  • Prototype模式同样用于隔离类对象的使用者和具体类型(易变类)直接的耦合关系,他同样要求这些“易变类”拥有“稳定的接口”。
  • Prototype模式对于“如何创建易变类的实体对象”采用“原型克隆”的方法来做,它使得我们可以非常灵活地“动态创建”拥有某些稳定接口的新对象——所需工作仅仅是注册一个新类的对象(即原型),然后在任何需要的地方clone。
  • Prototype模式中的Clone方法可以利用某些框架中的序列化来实现深拷贝。

9. 构建器(Builder)


模式定义

将一个复杂对象的构建与其表示相分离,使得同样的构建过程(稳定)可以创建不同的表示(变化)。

                                                                                                                --《设计模式》Gof

动机(Motivation)
  • 在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将他们组合在一起的算法却相对稳定。
  • 如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变。
构建器解决的问题是一个复杂对象的创建工作,其通常由各个部分的子对象用一定算法构成。下面以游戏中房子的构建为例进行说明,这个房子的建造过程是相对固定的,比如大地节,墙,门,窗的构建等等,但是具体的墙,门,窗可能不同。因此将稳定的部分用抽象基类来实现,具体的房子即子类只需重写这些虚函数即可。
具体代码如下:
class House{    //....};class HouseBuilder {public:    House* GetResult(){        return pHouse;    }    virtual ~HouseBuilder(){}protected:        House* pHouse;virtual void BuildPart1()=0;    virtual void BuildPart2()=0;    virtual void BuildPart3()=0;    virtual void BuildPart4()=0;    virtual void BuildPart5()=0;};class StoneHouse: public House{    };class StoneHouseBuilder: public HouseBuilder{protected:        virtual void BuildPart1(){        //pHouse->Part1 = ...;    }    virtual void BuildPart2(){            }    virtual void BuildPart3(){            }    virtual void BuildPart4(){            }    virtual void BuildPart5(){            }    };class HouseDirector{    public:    HouseBuilder* pHouseBuilder;        HouseDirector(HouseBuilder* pHouseBuilder){        this->pHouseBuilder=pHouseBuilder;    }        House* Construct(){                pHouseBuilder->BuildPart1();                for (int i = 0; i < 4; i++){            pHouseBuilder->BuildPart2();        }                bool flag=pHouseBuilder->BuildPart3();                if(flag){            pHouseBuilder->BuildPart4();        }                pHouseBuilder->BuildPart5();                return pHouseBuilder->GetResult();    }};

结构:



要点总结:

  • Builder模式主要用于“分步骤构建一个复杂对象”。在这其中“分步骤”是一个稳定算法,而复杂对象的各个部分则经常变化。
  • 变化的点在哪里,封装哪里——Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动。
  • 在Builder模式中,要注意不同语言中构造器内调用虚函数的差别(C++ vs. C#)。

“接口隔离”模式

在组建构建过程中,某些接口之间直接的依赖常常会带来很多问题、甚至根本无法实现。采用添加一层间接接口(稳定的),来隔离本来相互紧密关联的接口是一种常见的解决方案。

典型模式:

  • Façade
  • Proxy
  • Adapter
  • Mediator

10. Façade


模式定义

为子系统中的一组接口提供一个一致(稳定)的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用(复用)。

                                                                                                                                                     --《设计模式》Gof

系统间耦合的复杂度


动机(Motivation)
  • 上述方案A的问题在于组件的客户和组件中的各种复杂的子系统有了过多的耦合,随着外部客户程序和子系统的演化,这种过多的耦合面临很多变化的挑战。
  • 如何简化外部客户程序和系统间的交互接口?如何将外部客户程序的演化和内部子系统的变化之间的依赖相互解耦。

结构:


要点总结:

  • 从客户程序的角度来看, Façade 模式简化了整个组件系统的接口。对于内部和外部客户程序来说, Façade 模式实现了一种解耦的效果——即内部的子系统的任何变化不会影响到 Façade 接口的变化。
  • Façade 设计模式更注重从架构的层次去看待整个系统,而不是单个类的层次。Façade 很多时候更是一种架构设计模式。
  • Façade 设计模式并非是一个集装箱,可以任意的放入任何多个对象。Facade模式中组件的内部应该是“相互耦合关系比较大的一系列组件”,而不是简单的功能集合,以便能够实现松耦合,高内聚的特性。


11. 代理模式(Proxy)


模式定义

为其他对象提供一种代理以控制(隔离,使用接口)对这个对象的访问。

                                                                                                         --《设计模式》Gof

动机(Motivation)
  • 在面向对象系统中,有些对象由于某种原因(比如对象创建开销很大或者某些操作需要安全控制或者需要进程外访问等),直接访问会给使用者、或者系统结构带来很多的麻烦。
  • 如何在不失去透明操作对象的同时来管理/控制这些对象特有的复杂性?增加一层间接层是软件开发中常见的解决方法。

在软件的开发过程中,基于某种原因,直接访问有些对象会给使用者或者系统带来很多麻烦(比如创建对象的开销、相关的安全机制、进程之外的访问等等)。这样的情况下,如果我们希望按照原有的方式(透明操作)去访问这个对象,但又希望回避这些“麻烦”,我们就倾向于建立一个 Proxy 来作为原有对象和访问者的中间层。通过这个中间层,我们可以对相关的额外内容进行处理(比如添加一些安全性的验证等等),从而实现了在不失去对对象的透明操作的情况下,同时管理和控制这些对象特有的复杂性。
具体代码如下:
class ISubject{public:    virtual void process();};class RealSubject: public ISubject{public:    virtual void process(){        //....    }};class ClientApp{    ISubject* subject;public:    ClientApp(){        subject=new RealSubject();    }    void DoTask(){        //...        subject->process();        //....    }};class ISubject{public:    virtual void process();};//Proxy的设计class SubjectProxy: public ISubject{public:    virtual void process(){        //对RealSubject的一种间接访问        //....    }};class ClientApp{    ISubject* subject;public:    ClientApp(){        subject=new SubjectProxy();    }    void DoTask(){        //...        subject->process();        //....    }};

结构:

要点总结:

  • “增加一层间接层”是软件系统中对许多复杂问题的一种常见解决方案。在面向对象系统中,直接使用某些对象会带来很多问题,作为间接层的Proxy对象便是解决这一问题的常用手段。
  • 具体Proxy设计模式的实现方法、实现粒度都相差很大,有些可能对单个对象做细粒度的控制,如copy-on-write技术,有些可能对组建模块提供抽象代理层,在架构层次对对象做proxy
  • proxy并不一定要求保持接口完整的一致性,只要能够实现间接控制,有时候损及一些透明性是可以接受的。


12. 适配器(Adapter)

模式定义

将一个类的接口转换成客户希望的另一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

                                                                                                                        --《设计模式》Gof

动机(Motivation)
  • 在软件系统中,由于应用环境的变化,常常需要将“一些现存的对象”放在新的环境中应用,但是新环境要求的接口是这些现存对象所不满足的。
  • 如何应对这种“迁移的变化”?如何既能利用现有对象的良好实现,同时又能满足新的应用环境所要求的接口?
适配器模式可以通过将旧的类接口转化为新接口,从而解决新环境要求的接口问题。
具体代码如下:
//目标接口(新接口)class ITarget{public:    virtual void process()=0;};//遗留接口(老接口)class IAdaptee{public:    virtual void foo(int data)=0;    virtual int bar()=0;};//遗留类型class OldClass: public IAdaptee{    //....};//对象适配器class Adapter: public ITarget{ //继承protected:    IAdaptee* pAdaptee;//组合public:    Adapter(IAdaptee* pAdaptee){        this->pAdaptee=pAdaptee;    }    virtual void process(){        int data=pAdaptee->bar();        pAdaptee->foo(data);    }};//类适配器class Adapter: public ITarget,               protected OldClass{ //多继承}int main(){    IAdaptee* pAdaptee=new OldClass();    ITarget* pTarget=new Adapter(pAdaptee);    pTarget->process();}class stack{  //也符合Adapter的设计模式    deqeue container;};class queue{  //也符合Adapter的设计模式    deqeue container;};

结构:


要点总结:

  • Adapter模式主要应用于“希望复用一些现存的类,但是接口又与服用环境要求不一致的情况”,在遗留代码复用、类库迁移等方面非常有用。
  • GoF23定义了两种Adapter模式的结构实现:对象适配器好类适配器。但类适配器采用“多继承”的实现方式,一般不推荐使用。对象适配器采用“对象组合”的方式,更符合松耦合的精神。
  • Adapter模式可以实现的非常灵活,不必拘泥于GoF23中定义的两种结构。例如,完全可以将Adapter模式中的“现存对象”作为新的接口方法参数,来达到适配的目的。


13. 中介者(Mediator)


模式定义

用一个中介对象来封装(封装变化)一系列的对象交互。中介者使各个对象不需要显示的相互引用(编译时依赖 -> 运行时依赖),从而使其耦合松散(管理变化),而且可以独立的改变他们之间的交互。

                                                                                                                        --《设计模式》Gof

动机(Motivation)
  • 在软件构建的过程中,劲吹出现多个多项互相关联交互的情况,对象之间常常会维持一种复杂的引用关系,如果遇到了一些需求的更改,这种直接的饮用关系将面临不断的变化。在这种情况下,我们可以使用“中介对象”来管理对象间的关联关系,避免相互交互的对象之间的紧耦合引用关系,从而更好地抵御变化。
  • 在这种情况下,我们可使用一个“中介对象”来管理对象间的关联关系,避免相互交互的对象之间额紧耦合引用关系,从而更好地抵御变化。

结构:


要点总结:

  • 将多个对象间复杂的关联关系解耦,Mediator模式将多个对象间的控制逻辑进行集中管理,变“多个对象互相关联”为“多个对象和一个中介者关联”,简化了系统的维护,地狱了可能的变化。
  • 随着控制逻辑的复杂化,Mediator具体对象的实现可能相当复杂。这时候可以对Mediator对象进行分解处理。
  • Facade模式是解耦系统间(单向)的对象关联关系;Mediator模式是解耦系统内各个对象之间(双向)的关联关系。





原创粉丝点击