设计模式4:“对象创建”模式——Factory Method, Abstract Factory, Prototype, Builder

“对象创建”模式

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

典型模式: 

  • Factory Method
  • Abstract Factory
  • Prototype
  • Builder

Factory Method工厂方法

动机(Motivation)

  • 在软件系统中,经常面临这创建对象的工作;由于需求的变化,需要创建的对象的具体类型经常变化。
  • 如何应对这种变化?如何绕过常规的对象创建方法(new),提供一种“封装机制”来避免客户程序和这种“具体对象创建工作”的紧耦合。

在先前的课程中,我们分析过一个文件分割的例子,如果文件分割的算法有很多种的话,如何实现灵活的切换呢?

先看一下代码:

 1 class ISplitter{
 2 public:
 3     virtual void split()=0;
 4     virtual ~ISplitter(){}
 5 };
 6 
 7 class BinarySplitter : public ISplitter{
 8 
 9 };
10 
11 class TxtSplitter: public ISplitter{
12 
13 };
14 
15 class PictureSplitter: public ISplitter{
16 
17 };
18 
19 class VideoSplitter: public ISplitter{
20 
21 };
22 
23 
24 class MainForm : public Form
25 {
26     TextBox* txtFilePath;
27     TextBox* txtFileNumber;
28     ProgressBar* progressBar;
29 
30 public:
31     void Button1_Click(){
32 
33         ISplitter * splitter=
34             new BinarySplitter();//依赖具体类
35 
36         splitter->split();
37 
38     }
39 };
View Code

  从面向对象设计的思想出发,我们设计一个抽象基类(稳定),子类(变化)在创建对象过程中使用基类对象来创建,这样可以让我们将对象的类型判断放到运行时去决定。

  这种编程方法我们称为面向接口编程,不过我们会发现一个问题:抽象类不能直接用于对象创建,因此在创建对象的时候,需要在指出具体的实现细节,例如:
ISplitter * splitter=new BinarySplitter();

  这样的创建方法显然违背了之前提过的依赖倒置原则,那是否存在一种方法来绕开这种细节依赖呢?我们是否可以参考上面的分割算法实现方法,通过一个抽象基类来实现对象创建?

首先,来看一下创建对象的三种方法:
  • 直接创建:BinarySplitter bs();
  • 直接调用new函数;
  • 用“绕”的方法,让函数返回一个对象;
  第一种和第二种方法都依赖于具体实现细节,因此我们通过第三种方法来创建对象。这里我们使用抽象基类来实现创建对象细节依赖的延迟,将细节依赖排除在我们的程序之外,让它在其他地方实现细节依赖。具体代码如下:
 1 //抽象类
 2 class ISplitter{
 3 public:
 4     virtual void split()=0;
 5     virtual ~ISplitter(){}
 6 };
 7 
 8 //工厂基类
 9 class SplitterFactory{
10 public:
11     virtual ISplitter* CreateSplitter()=0;
12     virtual ~SplitterFactory(){}
13 };
14 
15 //具体类
16 class BinarySplitter : public ISplitter{
17 
18 };
19 
20 class TxtSplitter: public ISplitter{
21 
22 };
23 
24 class PictureSplitter: public ISplitter{
25 
26 };
27 
28 class VideoSplitter: public ISplitter{
29 
30 };
31 
32 //具体工厂
33 class BinarySplitterFactory: public SplitterFactory{
34 public:
35     virtual ISplitter* CreateSplitter(){
36         return new BinarySplitter();
37     }
38 };
39 
40 class TxtSplitterFactory: public SplitterFactory{
41 public:
42     virtual ISplitter* CreateSplitter(){
43         return new TxtSplitter();
44     }
45 };
46 
47 class PictureSplitterFactory: public SplitterFactory{
48 public:
49     virtual ISplitter* CreateSplitter(){
50         return new PictureSplitter();
51     }
52 };
53 
54 class VideoSplitterFactory: public SplitterFactory{
55 public:
56     virtual ISplitter* CreateSplitter(){
57         return new VideoSplitter();
58     }
59 };
60 
61 class MainForm : public Form
62 {
63     SplitterFactory*  factory;//工厂
64 
65 public:
66 
67     MainForm(SplitterFactory*  factory){
68         this->factory=factory;
69     }
70 
71     void Button1_Click(){
72 
73         ISplitter * splitter=
74             factory->CreateSplitter(); //多态new
75 
76         splitter->split();
77 
78     }
79 };
ISplitter2
1 SplitterFactory*  factory;//工厂
2 ISplitter * splitter=factory->CreateSplitter(); //多态new

  我们将创建对象这种方法直接抽象到了一个抽象基类(工厂基类)中,同时子类通过各自的具体方法(细节依赖)来实现对象创建。通过这种方法,我们在创建对象的时候,不再依赖具体的类,只有在运行时才进行具体类型的判断,从而实现依赖倒置原则。

  上述的每个具体工厂中使用new函数来实现对象创建,这里并不违背依赖倒置原则,因为具体对象的创建最后还是要依赖细节实现,我们不可能做到将这些变化消灭掉,而是将变化约束到一个更加可控的区域。
 
模式定义

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

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

                                                                                             --《设计模式》Gof

结构:
 

要点总结:

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

  在软件中,可以通过将new封装到工厂类的create函数中,并且将工厂类抽象出一个基类,实现new的多态性。这样可以保证对象构建函数的复用,实现将变化集中的目的。 

抽象工厂 (Abstract Factory)

动机(Motivation)

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

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

  下面以数据连接系统为例进行设计模式的说明,要实现数据连接系统需要connecttion对象,command对象等等,同时不同的数据库所需的对象也会有变化。如果我们不采用任何设计模式,那么代码应该如下所示:
 1 class EmployeeDAO{
 2     
 3 public:
 4     vector<EmployeeDO> GetEmployees(){
 5         SqlConnection* connection =
 6             new SqlConnection();
 7         connection->ConnectionString = "...";
 8 
 9         SqlCommand* command =
10             new SqlCommand();
11         command->CommandText="...";
12         command->SetConnection(connection);
13 
14         SqlDataReader* reader = command->ExecuteReader();
15         while (reader->Read()){
16 
17         }
18     }
19 };
View Code

  很明显上面的代码不满足依赖倒置原则,难以满足变化的要求。

  参考工厂模式,我们很快就能找到解决方法,将数据库的具体实现向上抽象,创建抽象基类,具体代码如下:
 1 //数据库访问有关的基类
 2 class IDBConnection{
 3     
 4 };
 5 class IDBConnectionFactory{
 6 public:
 7     virtual IDBConnection* CreateDBConnection()=0;
 8 };
 9 
10 
11 class IDBCommand{
12     
13 };
14 class IDBCommandFactory{
15 public:
16     virtual IDBCommand* CreateDBCommand()=0;
17 };
18 
19 
20 class IDataReader{
21     
22 };
23 class IDataReaderFactory{
24 public:
25     virtual IDataReader* CreateDataReader()=0;
26 };
27 
28 
29 //支持SQL Server
30 class SqlConnection: public IDBConnection{
31     
32 };
33 class SqlConnectionFactory:public IDBConnectionFactory{
34     
35 };
36 
37 
38 class SqlCommand: public IDBCommand{
39     
40 };
41 class SqlCommandFactory:public IDBCommandFactory{
42     
43 };
44 
45 
46 class SqlDataReader: public IDataReader{
47     
48 };
49 class SqlDataReaderFactory:public IDataReaderFactory{
50     
51 };
52 
53 //支持Oracle
54 class OracleConnection: public IDBConnection{
55     
56 };
57 
58 class OracleCommand: public IDBCommand{
59     
60 };
61 
62 class OracleDataReader: public IDataReader{
63     
64 };
65 
66 class EmployeeDAO{
67     IDBConnectionFactory* dbConnectionFactory;
68     IDBCommandFactory* dbCommandFactory;
69     IDataReaderFactory* dataReaderFactory;
70     
71     
72 public:
73     vector<EmployeeDO> GetEmployees(){
74         IDBConnection* connection =
75             dbConnectionFactory->CreateDBConnection();
76         connection->ConnectionString("...");
77 
78         IDBCommand* command =
79             dbCommandFactory->CreateDBCommand();
80         command->CommandText("...");
81         command->SetConnection(connection); //关联性
82 
83         IDBDataReader* reader = command->ExecuteReader(); //关联性
84         while (reader->Read()){
85 
86         }
87     }
88 };
View Code

  上面的代码将数据连接系统的具体实现用工厂模式实现,为所有与数据库访问相关的对象创建了抽象基类,从而使得这些对象的创建实现多态性,但是这时候出现了一个新问题:

  这些对象需要一起配套使用,也就是说SQL的connection对象必须和SQL的command对象一起使用,不然就会在运行时报错。

  既然它们必须在一起使用,那我们就把它们合并就可以了,具体代码如下:
 1 //数据库访问有关的基类
 2 class IDBConnection{
 3     
 4 };
 5 
 6 class IDBCommand{
 7     
 8 };
 9 
10 class IDataReader{
11     
12 };
13 
14 
15 class IDBFactory{
16 public:
17     virtual IDBConnection* CreateDBConnection()=0;
18     virtual IDBCommand* CreateDBCommand()=0;
19     virtual IDataReader* CreateDataReader()=0;
20     
21 };
22 
23 
24 //支持SQL Server
25 class SqlConnection: public IDBConnection{
26     
27 };
28 class SqlCommand: public IDBCommand{
29     
30 };
31 class SqlDataReader: public IDataReader{
32     
33 };
34 
35 
36 class SqlDBFactory:public IDBFactory{
37 public:
38     virtual IDBConnection* CreateDBConnection()=0;
39     virtual IDBCommand* CreateDBCommand()=0;
40     virtual IDataReader* CreateDataReader()=0;
41  
42 };
43 
44 //支持Oracle
45 class OracleConnection: public IDBConnection{
46     
47 };
48 
49 class OracleCommand: public IDBCommand{
50     
51 };
52 
53 class OracleDataReader: public IDataReader{
54     
55 };
56 
57 
58 class EmployeeDAO{
59     IDBFactory* dbFactory;
60     
61 public:
62     vector<EmployeeDO> GetEmployees(){
63         IDBConnection* connection =
64             dbFactory->CreateDBConnection();
65         connection->ConnectionString("...");
66 
67         IDBCommand* command =
68             dbFactory->CreateDBCommand();
69         command->CommandText("...");
70         command->SetConnection(connection); //关联性
71 
72         IDBDataReader* reader = command->ExecuteReader(); //关联性
73         while (reader->Read()){
74 
75         }
76 
77     }
78 };
Abstract Factory

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

模式定义 

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

                                                                                                      --《设计模式》Gof

结构

要点总结:

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

 

原型模式(Prototype)

动机(Motivation)
  • 在软件系统中,经常面临着“某些结构复杂的对象”的创建工作;由于需求的变化,这些对象经常面临着剧烈的变化,但是他们却拥有着比较稳定一致的接口。
  • 如何应对这种变化?如何向“客户程序(使用这些对象的程序)”隔离出“这些易变对象”,从而使得“依赖这些易变对象的客户程序”不随着需求改变而改变?

  原型模式也是属于对象创建模式,它用来处理某些结构复杂对象的创建工作,这也是与工厂模式最大的区别。

  如果我们要创建的对象比较简单的话,使用工厂模式new一下就可以了,但是当对象比较复杂,它的初始状态不是我们最想用的时候,我们可以调用拷贝构造函数来深拷贝某个比较符合我们要求的对象。当然所谓的复杂是相对而言的,应当根据实际情况确定。
具体代码如下:
 1 //抽象类
 2 class ISplitter{
 3 public:
 4     virtual void split()=0;
 5     virtual ISplitter* clone()=0; //通过克隆自己来创建对象
 6     
 7     virtual ~ISplitter(){}
 8 
 9 };
10 
11 //具体类
12 class BinarySplitter : public ISplitter{
13 public:
14     virtual ISplitter* clone(){
15         return new BinarySplitter(*this);
16     }
17 };
18 
19 class TxtSplitter: public ISplitter{
20 public:
21     virtual ISplitter* clone(){
22         return new TxtSplitter(*this);
23     }
24 };
25 
26 class PictureSplitter: public ISplitter{
27 public:
28     virtual ISplitter* clone(){
29         return new PictureSplitter(*this);
30     }
31 };
32 
33 class VideoSplitter: public ISplitter{
34 public:
35     virtual ISplitter* clone(){
36         return new VideoSplitter(*this);
37     }
38 };
39 
40 class MainForm : public Form
41 {
42     ISplitter*  prototype;//原型对象
43 
44 public:
45     
46     MainForm(ISplitter*  prototype){
47         this->prototype=prototype;
48     }
49     
50     void Button1_Click(){
51 
52         ISplitter * splitter=
53             prototype->clone(); //克隆原型
54         
55         splitter->split();
56         
57     }
58 };
Prototype

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

 

模式定义 

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

                                                                                                      --《设计模式》Gof

结构:

 

 

要点总结: 

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

构建器(Builder)

动机(Motivation)

  • 在软件系统中,有时候面临着“一个复杂对象”的创建工作,其通常由各个部分的子对象用一定的算法构成;由于需求的变化,这个复杂对象的各个部分经常面临着剧烈的变化,但是将他们组合在一起的算法却相对稳定。
  • 如何应对这种变化?如何提供一种“封装机制”来隔离出“复杂对象的各个部分”的变化,从而保持系统中的“稳定构建算法”不随着需求改变而改变。

  构建器解决的问题是一个复杂对象的创建工作,其通常由各个部分的子对象用一定算法构成。下面以游戏中房子的构建为例进行说明,这个房子的建造过程是相对固定的,比如大地节,墙,门,窗的构建等等,但是具体的墙,门,窗可能不同。

 1 class House{
 2     //....
 3 
 4      House(){ // 错误示例,不能这样写
 5 
 6         this->BuildPart1(); // 构造函数中是静态绑定,此时调用纯虚函数会报错,不会去调用子类
 7 
 8         for (int i = 0; i < 4; i++){
 9             this->BuildPart2();
10         }
11 
12         bool flag = pHouseBuilder->BuildPart3();
13 
14         if (flag){
15             this->BuildPart4();
16         }
17 
18         this->BuildPart5();
19     }
20 
21     void Init(){ // 流程相对固定
22 
23         this->BuildPart1(); // 构造第一部分
24 
25         for (int i = 0; i < 4; i++){
26             this->BuildPart2();
27         }
28 
29         bool flag = pHouseBuilder->BuildPart3();
30 
31         if (flag){
32             this->BuildPart4();
33         }
34 
35         this->BuildPart5();        
36     }
37     virtual ~HouseBuilder(){}
38 protected:    
39     virtual void BuildPart1() = 0;
40     virtual void BuildPart2() = 0;
41     virtual void BuildPart3() = 0;
42     virtual void BuildPart4() = 0;
43     virtual void BuildPart5() = 0;
44 };
45 
46 class StoneHouse: public House{
47 
48 protected:
49 
50     virtual void BuildPart1(){
51         //pHouse->Part1 = ...;
52     }
53     virtual void BuildPart2(){
54 
55     }
56     virtual void BuildPart3(){
57 
58     }
59     virtual void BuildPart4(){
60 
61     }
62     virtual void BuildPart5(){
63 
64     }
65 };
66 
67 int main()
68 {
69     House* pHouse = new StoneHouse();
70     pHouse->Init();
71 }
View Code

  因此将稳定的部分用抽象基类来实现,具体的房子即子类只需重写这些虚函数即可。

具体代码如下:
 1 class House{ // 表示
 2     //.... house 与 HouseBuilder 相分离
 3 };
 4 
 5 class HouseBuilder {  // 构建
 6 public:
 7     House* GetResult(){
 8         return pHouse;
 9     }
10     virtual ~HouseBuilder(){}
11 protected:
12     
13     House* pHouse;
14     virtual void BuildPart1()=0;
15     virtual void BuildPart2()=0;
16     virtual void BuildPart3()=0;
17     virtual void BuildPart4()=0;
18     virtual void BuildPart5()=0;
19     
20 };
21 
22 class StoneHouse: public House{
23     
24 };
25 
26 class StoneHouseBuilder: public HouseBuilder{
27 protected:
28     
29     virtual void BuildPart1(){
30         //pHouse->Part1 = ...;
31     }
32     virtual void BuildPart2(){
33         
34     }
35     virtual void BuildPart3(){
36         
37     }
38     virtual void BuildPart4(){
39         
40     }
41     virtual void BuildPart5(){
42         
43     }
44     
45 };
46 
47 // 稳定
48 class HouseDirector{  // 同样的构建过程
49     
50 public:
51     HouseBuilder* pHouseBuilder;
52     
53     HouseDirector(HouseBuilder* pHouseBuilder){
54         this->pHouseBuilder=pHouseBuilder;
55     }
56     
57     House* Construct(){
58         
59         pHouseBuilder->BuildPart1();
60         
61         for (int i = 0; i < 4; i++){
62             pHouseBuilder->BuildPart2();
63         }
64         
65         bool flag=pHouseBuilder->BuildPart3();
66         
67         if(flag){
68             pHouseBuilder->BuildPart4();
69         }
70         
71         pHouseBuilder->BuildPart5();
72         
73         return pHouseBuilder->GetResult();
74     }
75 };
HouseDirector

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

--《设计模式》Gof                                

 

要点总结:

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

解决方案 :与模板方法类似,将创建流程在基类中实现,子过程作为protected的虚函数,在子类中再实现。实现时需要注意不同语言在构建对象时调用虚函数的区别。Prototype与Builder实际应用中并不多。

posted on 2017-12-08 23:01  flysong  阅读(184)  评论(0编辑  收藏  举报

导航