“对象创建”模式:通过“对象创建”模式绕开 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(); // 多态new

splitter->split();
}
};

(代码仅作展示,未考虑内存管理等内容。)

MainForm 不再没有具体类的依赖,至于 MainForm 以外的部分有没有具体的依赖就没关系了。

很多时候并不是把变化消灭掉,依赖具体类是消灭不掉的,只是把变化"赶到某个地方关起来"。

结构

  • Product 对应 ISplitter
  • ConcreteProduct 对应 BinarySplitter, TxtSplitter, PictureSplitter 这些
  • Creator 对应 SplitterFactory
  • ConcreteCreator 对应 BinarySplitterFactory, TxtSplitterFactory, PictureSplitterFactory

MainForm 只依赖红色(稳定的)部分。

总结

  1. Factory Method 模式用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的类型,紧耦合关系(new)会导致软件的脆弱。
  2. Factory Method 模式通过面向对象(多态)的手法,将要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好地解决了这种耦合关系。
  3. 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;
};

// 支持SQL Server
class SqlConnection : public IDBConnection {};
class SqlConnectionFactory : public IDBConnectionFactory {};

class SqlCommand : public IDBCommand {};
class SqlConnectionFactory : public IDBConnectionFactory {};

class SqlDataReader : public IDataReader {};
class SqlDataReaderFactory : public IDataReaderFactory {};

// 支持Oracle
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()) {
}
}
};

问题:IDBConnectionFactoryIDBCommandFactory 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;
};

// 支持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 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等

总结

  1. 如果没有应对“多系列对象构建”的需求变化,则没有必要使用 Abstract Factory 模式,这时候使用简单的工厂完全可以。

  2. “系列对象”指的是在某一特定系列下的对象之间有相互依赖、或作用的关系的关系。不同系列的对象不能相互依赖。

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

原型对象不是使用的,是放那供克隆的。

结构

image-20240328155308789

总结

  1. Prototype 模式同样用于隔离类对象的使用者和具体类型(易变类)之间的耦合关系,它同样要求这些“易变类”拥有“稳定的接口”。

  2. Prototype 模式对于”如何创建易变类的实体对象“采用”原型克隆“的方法,这使得我们可以非常灵活地动态创建”拥有某些稳定接口“的心对象——所需工作仅仅是注册一个新类的对象(即原型),然后再任何需要的地方Clone。

  3. 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() {
// 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() { // C++中不能放到构造函数中
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 合并也行。
类越来越复杂,就拆拆拆;类越来越简单,就合并。

总结

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