“对象创建”模式 :通过“对象创建”模式绕开 new
来避免对象创建过程中所导致的紧耦合(依赖具体类),从而支持对象创建的稳定。它是接口抽象后的第一步工作。
Factory Method
Factory Method
也被称为 Virtual Constructor
。
动机
在软件系统中,经常面临着创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。
如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合?
定义
Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer(目的:解耦;手段:虚函数) instantiation to subclasses.
实现
Naive
FileSplitter1.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 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 {};
MainForm1.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 class MainForm : public Form { TextBox* txtFilePath; TextBox* txtFileNumber; ProgressBar* progressBar; public : void Button1_Click () { ISplitter* splitter = new BinarySplitter (); splitter->split (); } };
ISplitter* splitter
是抽象依赖,new BinarySplitter()
是细节依赖——只要出现一处细节依赖,也是违反依赖倒置原则 。
如果改成下面的方式:
1 2 3 4 class SplitterFactory { public : ISplitter* CreateSplitter () { return new BinarySplitter (); } }
仍然是编译时依赖具体类,无法绕开。
考虑提供运行时依赖的基础设施——virtual
!
Factory Method
ISplitterFactory.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 class ISplitter { public : virtual void split () = 0 ; virtual ~ISplitter () {} }; class SplitterFactory { public : virtual ISplitter* CreateSplitter () = 0 ; virtual ~SplitterFactory () {} };
FileSplitter2.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 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 (); } };
MainForm2.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 class MainForm : public Form { SplitterFactory* factory; public : MainForm (SplitterFactory* factory) { this ->factory = factory; } void Button1_Click () { ISplitter* splitter = factory->CreateSplitter (); splitter->split (); } };
(代码仅作展示,未考虑内存管理等内容。)
MainForm 不再没有具体类的依赖,至于 MainForm 以外的部分有没有具体的依赖就没关系了。
很多时候并不是把变化消灭掉,依赖具体类是消灭不掉的,只是把变化"赶到某个地方关起来"。
结构
Product 对应 ISplitter
ConcreteProduct 对应 BinarySplitter, TxtSplitter, PictureSplitter 这些
Creator 对应 SplitterFactory
ConcreteCreator 对应 BinarySplitterFactory, TxtSplitterFactory, PictureSplitterFactory
MainForm 只依赖红色(稳定的)部分。
总结
Factory Method 模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的类型,紧耦合关系(new)会导致软件的脆弱。
Factory Method 模式通过面向对象(多态)的手法,将要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种耦合关系。
Factory Method 模式解决“单个对象”的需求变化。缺点在于要求创建方法 / 参数下相同 。
Prototype 比 Factory Method 和 Builder 使用的要少的多。
Abstract Factory
Abstract Factory
也被称为 Kit
。
动机
在软件系统中,经常面临着“一系列相互依赖的对象 “的创建工作;同时,由于需求的变化,往往存在更多系列 对象的创建工作。
如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种”封装机制“来避免客户程序和这种”多系列具体对象创建工作“的紧耦合?
定义
Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
实现
Naive
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 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 ()) { } } };
Factory Method
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 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 ; }; class SqlConnection : public IDBConnection {};class SqlConnectionFactory : public IDBConnectionFactory {};class SqlCommand : public IDBCommand {};class SqlConnectionFactory : public IDBConnectionFactory {};class SqlDataReader : public IDataReader {};class SqlDataReaderFactory : public IDataReaderFactory {};class Connection : public IDBConnection {};class OracleConnectionFactory : public IDBConnectionFactory {};class OracleCommand : public IDBCommand {};class OracleConnectionFactory : public IDBConnectionFactory {};class OracleDataReader : public IDataReader {};class OracleDataReaderFactory : public IDataReaderFactory {};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 ()) { } } };
问题:IDBConnectionFactory
、IDBCommandFactory
和 IDataReaderFactory
必须是同系列,固定搭配的。
直觉:把这三个变量通过一个工厂产出,似乎这个问题就能有所改善。
Abstract Factory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 class IDBConnection {};class IDBCommand {};class IDataReader {};class IDBFactory { public : virtual IDBConnection* CreateDBConnection () = 0 ; virtual IDBCommand* CreateDBCommand () = 0 ; virtual IDataReader* CreateDataReader () = 0 ; }; 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 ; }; class OracleConnection : public IDBConnection {};class OracleCommand : public IDBCommand {};class OracleDataReader : public IDataReader {};class OracleDBFactory : public IDBFactory { public : virtual IDBConnection* CreateDBConnection () = 0 ; virtual IDBCommand* CreateDBCommand () = 0 ; virtual IDataReader* CreateDataReader () = 0 ; }; 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 ()) { } } };
结构
AbstractFactory 相当于 IDBFactory
AbstactProductA, AbstractProductB 相当于 IDBConnection, IDBCommand, IDataReader等
总结
如果没有应对“多系列对象构建”的需求变化,则没有必要使用 Abstract Factory 模式,这时候使用简单的工厂完全可以。
“系列对象”指的是在某一特定系列下的对象之间有相互依赖、或作用的关系的关系。不同系列的对象不能相互依赖。
Abstract Factory 模式主要在于应对“新系列”的需求变化。其缺点在于难以应对“新对象”的需求变化。
新系列:比如除了 sql
, Oracle
外,加一个系列比较容易。
新对象:比如在 IDBFactory
这个抽象基类(要求是稳定的)去加一个 Create*
比较难。
可以把Factory Method 视为 Abstract Factory 的一种特例。
Prototype
动机
在软件系统中,经常面临着”某些结构复杂的对象 “的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是它们却拥有比较稳定一致的接口。
如何应对这种变化?如何向”客户程序(使用这些对象的程序)”隔离出"这些易变对象",从而使得”依赖这些易变对象的客户程序“不随着需求改变而改变?
Q: 什么时候要用 Prototype
, 或者说,它和 Factory Method
的最大区别是什么?
A: 当对象比较复杂时,使用 Factory Method
需要写的很复杂**,需要考虑很复杂的中间状态;如果使用 Prototype
只用初始化一次,以后使用深拷贝就行了。
定义
Specify the kinds of objects to create using a prototypical instance , and create new objects by copying(深拷贝) this prototype.
实现
有点像把 Factory Method 中的 Factory 和 Product 合并。
1 2 3 4 5 6 7 8 9 10 11 12 13 class ISplitter { public : virtual void split () = 0 ; virtual ~ISplitter () {} }; class SplitterFactory { public : virtual ISplitter* CreateSplitter () = 0 ; virtual ~SplitterFactory () {} };
合并后去掉~SplitterFactory()
,并把 CreateSplitter()
改为 clone()
,如下:
Prototype.cpp
1 2 3 4 5 6 7 8 9 class ISplitter { public : virtual void split () = 0 ; virtual ISplitter* clone () = 0 ; virtual ~ISplitter () {} };
ConcretePrototype.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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 ); } };
Client.cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 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 方法可以利用某些框架中的序列化来实现深拷贝。
C++ 中写好拷贝构造函数就行。
Builder
动机
在软件系统中,有时候面临着“一个复杂对象 ”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化 ,但是将它们组合在一起的算法却相对稳定。
如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变?
定义
Separate the construction of a complex object from its representation so that same construction process(稳定) can create different representations(变化).
实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 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 () { } 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 (); } };
注意 HouseDirector::Construct()
中内容不能放到构造函数中(虽然放进构造函数是最自然的),构造函数去调用虚函数完成的是静态绑定。
只有C++中是这样的,在 Java, C# 中都不是这样的。
House 是表示, HouseBuild 是构建。
结构
很多情况下,Director 和 Builder 合并也行。
类越来越复杂,就拆拆拆;类越来越简单,就合并。
总结
Builder 模式主要用于“分步骤构建一个复杂的对象”。其中,“分步骤”是一个稳定 的算法,而复杂对象的各个部分则经常变化 。
变化点在哪里,封装哪里——Builder模式主要在于应对“复杂对象各个部分”的频繁需求变动。其缺点在于难以应对“分步骤构建算法”的需求变动。
在 Builder 模式中,要注意不同语言中构造器内调用虚函数的差别(C++ vs. C#)。