设计模式之“对象创建“模式:Factory Method、Abstract Factory、Prototype、Builder


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



★1. Factory Method 工厂模式


1.1 Factory Method 工厂模式动机

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

那么如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合


1.2 模式定义

定义一个用于创建对象的接口(工厂抽象基类,对应下图Creator),让子类(对应下图 ConcreteCreator)决定实例化哪一个类。 Factory Method 使得一个类的实例化延迟(目的:解耦,手段:虚函数)到子类
在这里插入图片描述


1.3 示例代码

组件协作中的Observer观察者模式 的示例代码FileSplitter为例。

假设现在 Splitter 按照所处理的文件的数据类型又可分为 BianrySplitter、TxtSplitter、VideoSplitter…,那么由 DIP(依赖倒置原则)可以容易想到将这些特定 Splitter 继承自抽象基类 ISplitter。在实现 MainForm 的过程中,在具化内含的 ISplitter 的时候,只能将动态类型显式指出,这样导致若是所具化的对象类型变化时需要改动 MainForm中的 new 语句,因为所依赖的是编译时装配。

FileSplitter1.cpp
MainForm1.cpp

若是应用 Factory Method 模式,将 new 这一具化步骤转化为 调用工厂抽象基类向下转化后所动态绑定的派生类FactoryMethod() 方法(实现中对应的是 createSplitter() 方法),可以避免编译时装配,改用为运行时装配,即让派生类决定 实例化/new 哪一种类型的对象(多态化new)。

FileSplitter2.cpp
MainForm2.cpp

注意到,采用了 Factory Method 模式之后,MainForm 的实现中,加入了一个 Factory 抽象类指针,该指针在 MainForm 构造的过程中实例化,然后在 button1 点击之后 多态地 创建具体类型的Splitter(BianrySplitter、TxtSplitter、VideoSplitter…)。

与没有使用工厂模式的原版本相比,也即是原先需要进行 new 操作的类型中内含一个工厂,在该类型创建后其内含工厂被实例化并可产出所需的特定类型对象, 以缓解 new 行为所造成的紧耦合


1.4 要点总结

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


★2. Abstract Factory 抽象工厂模式


2.1 Abstract Factory 抽象工厂模式动机

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

那么如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“多系列具体对象创建工作” 的紧耦合


2.2 模式定义

Abstract Factory 的本质是 Factory Method 中的单一对象的需求变化,变为了一组对象的需求变化。

Abstract Factory 和 Factory Method 的比较 见:Factory Method模式的误区:Factory Method模式是简化版的Abstract Factory吗?

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

在这里插入图片描述


2.3 示例代码

示例假设,业务 EmployeeDAO 要与数据库进行连接以获取雇员相关信息。

每次与数据库连接都要涉及到 connection, command, dataReader 三个操作,且该业务要支持多个类型数据库(sql, oracle, mysql…)的查询业务。

若是使用工厂模式,针对三种操作需要在业务类中 针对每种操作的 工厂模式抽象基类 内含其类型的一个指针,即三个指针(IDBCommand, IDBConnection, IDBDataReader);若是使用了抽象工厂模式,将三种相关操作(一种数据库类型的支持,其相关操作应当时相互关联和依赖的)进一步抽象为一个 系列对象接口(IDBFactory),则 业务量只须仅含一个 抽象工厂模式抽象基类 即可。

示例代码如下(主要看中右两代码,进行比较):

单独对sql数据库进行适配
使用组合工厂模式,client类(EmployeeDAO类)中含有三个抽象工厂类指针.
使用抽象工厂模式,将三种抽象工厂类组合为一个抽象接口,进而client类中仅含一个系列抽象工厂类指针

2.4 要点总结

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


3. Prototype 原型模式


3.1 Prototype 原型模式动机

在软件系统中,经常面临着"某些结构复杂的对象"(若是创建工厂抽象基类,很麻烦?)的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口

那么如何应对这种变化?如何向"客户程序"(使用这些对象的程序)隔离出这些"易变对象",从而使得"依赖这些对象的客户程序"不随着需求改变而改变。


3.2 模式定义

使用原型实例指定创建对象的种类,然后通过拷贝这些原型来创建新的对象。
在这里插入图片描述


3.3 示例代码

对于 Factory Method 中的 ISplitter,将 ISplitter 和 splitterFactory 的实现进行合并,保留一个虚析构函数,并将其中的 createSplitter() 函数更改为 clone() 函数。

也即是将 工厂抽象基类 和 产品抽象基类 合并定义,对于具体类,也不再分为 ConcreteCreator 和 ConcreteProduct。

Prototype.cpp
Client.cpp

3.4 要点总结

  • Prototype 模式 和以上模式一样 用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些易变类拥有稳定的接口;
  • Prototype 模式对于如何创建易变类的实体对象采用原型克隆的方法来做,它使得我们可以非常灵活地动态创建拥有某些稳定接口的新对象——所需工作仅仅是注册一个新类的对象(为内含的 prototype 重新赋值),然后在任何需要的地方 Clone;
  • 使用场景较少,看到代码只须看懂是该场景的应用即可;
  • Java 中 Prototype 模式中的 Clone 方法可以利用某些框架中的序列化来实现深拷贝;


4. Builder 构建器模式


4.1 Builder 构建器模式动机

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

那么如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变?


4.2 模式定义

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

有点类似 Template Method 模式,还是晚装配,运行时装配的思想,同时应用上了工厂模式。
在这里插入图片描述


4.3 实例代码

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(){
        
    }   
};

//Builder和Director可以进行合并
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();
    }
};

4.4 要点总结

  • Builder 模式主要用于“分步骤构建一个复杂的对象”。在这其中 “分步骤”是一个稳定的算法,而复杂对象的各个部分则经常变化;
  • 变化点在哪里,封装哪里—— Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动;
  • 使用场景较少,看到代码只须看懂是该场景的应用即可;
  • 在Builder模式中,要注意不同语言中构造器内调用虚函数的差别(C++ vs. C#);
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值