文章目录
通过“对象创建” 模式绕开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 语句,因为所依赖的是编译时装配。
|
|
若是应用 Factory Method 模式,将 new 这一具化步骤转化为 调用工厂抽象基类向下转化后所动态绑定的派生类FactoryMethod() 方法(实现中对应的是 createSplitter() 方法),可以避免编译时装配,改用为运行时装配,即让派生类决定 实例化/new 哪一种类型的对象(多态化new)。
|
|
注意到,采用了 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),则 业务量只须仅含一个 抽象工厂模式抽象基类 即可。
示例代码如下(主要看中右两代码,进行比较):
|
|
|
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。
|
|
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#);