美文网首页C++
(Boolan)C++设计模式 <七> ——工厂方法

(Boolan)C++设计模式 <七> ——工厂方法

作者: 故事狗 | 来源:发表于2017-07-04 20:58 被阅读88次

    “对象创建”模式

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

    • 典型模式
      1. Factory Method
      • Abstract Factory
      • Prototype
      • Builder

    工厂方法 (Factory Method)

    定义一个用于创建对象的接口,让子类决定实例化哪个类。Factory Method 使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类。
    ——《设计模式》GoF

    • 动机
      在软件系统中,经常会面临着创建对象的工作,由于需求变化,需要创建的对象的具体类型也会经常的变化

    假设还是拿之前的文件分割器作为例子,如果不使用具体的设计模式,那么在具体创建对象的时候,需要在初始化对象的时候指定出具体的实现细节,虽然ISplitter * splitter = new BinarySplitter(); 是面向接口的编程,将变量生命为了抽象的基类,但在实际new时,依赖了一个具体的实现类。实际上这里违反了依赖导致原则。如果这时候具体的Splitter还未完成实现,那么编译时会报错,如果需求发生变更那么此时,也需要重新修改和编译源代码。

    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();这里是核心的问题。那么如何才能绕开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();
    
        }
    };
    

    现在的工厂通过MainForm的构造函数传入其中,依赖其实依旧存在,但是MainForm并没有具体类型的依赖,而依赖的都是抽象类。其实对于设计模式来说,并没有办法消除依赖,而是将依赖控制在了最可控的地方。

    UML

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

    抽象工厂 (Abstract Factory)

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

    • 动机
      在软件系统中,经常会面临着“一系列相互依赖的对象”的创建工作,同时由于需求的变化,往往存在着更多系列对象的创建工作。

    假设需要写一个数据访问层,那么需要创建一系列对象,比如需要connection对象、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 Server
    class SqlConnection: public IDBConnection{
        
    };
    class SqlConnectionFactory:public IDBConnectionFactory{
        
    };
    
    
    class SqlCommand: public IDBCommand{
        
    };
    class SqlCommandFactory:public IDBCommandFactory{
        
    };
    
    
    class SqlDataReader: public IDataReader{
        
    };
    class SqlDataReaderFactory:public IDataReaderFactory{
        
    };
    
    //支持Oracle
    class 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()){
    
            }
    
        }
    };
    
    

    对于上面的代码来说,把所有的数据库访问有关的对象都创建了基类,如果为了实现其他的数据库,只需要针对对应的基类派生出子类。那么,其实也就是每个对象的创建都使用了工厂模式。

    现在已经能够很好的解决变化的问题,但是这时候存在一个新的问题,那就是,这一系列对象并不是彼此分隔的,比如MySQL的Connection对象对应着MySQL的Commend,如果使用错了,那么肯定会在运行时报错,无法完成我们想要达到的目的。那么这个问题该如何解决呢?

    我们可以尝试把对应的一系列的对象放在同一个工厂类中,以此来避免出现这样的问题。

    
    //数据库访问有关的基类
    class IDBConnection{
        
    };
    
    class IDBCommand{
        
    };
    
    class IDataReader{
        
    };
    
    
    class IDBFactory{
    public:
        virtual IDBConnection* CreateDBConnection()=0;
        virtual IDBCommand* CreateDBCommand()=0;
        virtual IDataReader* CreateDataReader()=0;
        
    };
    
    
    //支持SQL Server
    class 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;
     
    };
    
    //支持Oracle
    class 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的模式,其实他的名字如果是Family Factory好像更加合适一点。

    Abstract Factory UML

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

    原型模式(Prototype)

    使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。
    ——《设计模式》GoF

    • 动机
      在软件系统中,经常面临着“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有着比较稳定一直的接口。

    假设将工厂模式中的ISplitter和SplitterFactor两个基类进行合并。并添加一个克隆自己的方式来构建对象的虚方法。使用的时候应该是通过原型对象构造的对象来clone出所需要的对象。

    
    //抽象类
    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 UML

    这时候有一个问题,就是原型模式通过克隆的方式获得对象的目的到底是什么呢?这样能够解决什么样的问题呢?
    对于某些结构复杂的对象的创建。在某些状态下的工厂会变得比较复杂,并且,创建出来的对象不一定是我们想要的,那么我们只需要使用clone方法就可以得到我们需要的对象。
    那么简单一句话就是:本就是你需要从A的实例得到一份与A内容相同,但是又互不干扰的实例的话,就需要使用原型模式。

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

    构建器(Builder)

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

    • 动机
      在软件系统中,有时候面临着“一个复杂对象”的创建工作,期通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将他们组合在一起的算法却相对稳定。

    Builder其实和Template Method有一些相似,而Builder主要是为了解决对象创建的问题。

    假设需要构建一个房屋的对象,这个房子的建造过程相对固定的,比如打地基、修墙、装门等等,虽然步骤相对固定,但是具体的墙、门、窗可能都存在差异。
    那么使用如下的代码,把固定的部分实现为虚函数的流程(这里的做法很像Template Method)。
    具体的房子只需要继承House的类,并重写那些虚方法就行了。

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

    相关文章

      网友评论

        本文标题:(Boolan)C++设计模式 <七> ——工厂方法

        本文链接:https://www.haomeiwen.com/subject/inzthxtx.html