0%

设计模式之工厂模式(Factory)

一、动机

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

二、解决方案

工厂方法模式是一种创建型设计模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。用于隔离类对象的使用者和具体类型之间的耦合关系。面对一个经常变化的具体类型,紧耦合关系(new)会导致软件的脆弱。

FactoryMethod模式通过面向对象的方式,将所要创建的具体对象工作延迟到子类,从而实现一种扩展(而非更改)的策略,较好的解决了这种紧耦合关系。

三、工厂模式的结构

structure of Factory

  1. 产品Product)将会对接口进行声明。对于所有由创建者及其子类构建的对象,这些接口都是通用的。
  2. 具体产品Concrete Products)是产品接口的不同实现。
  3. 创建者Creator)类声明返回产品对象的工厂方法。该方法的返回对象类型必须与产品接口相匹配。

你可以将工厂方法声明为抽象方法,强制要求每个子类以不同方式实现该方法。或者,你也可以在基础工厂方法中返回默认产品类型。

注意,尽管它的名字是创建者,但它最主要的职责并不是创建产品。一般来说,创建者类包含一些与产品相关的核心业务逻辑。工厂方法将这些逻辑处理从具体产品类中分离出来。打个比方,大型软件开发公司拥有程序员培训部门。但是,这些公司的主要工作还是编写代码,而非生产程序员。

  1. 具体创建者Concrete Creators)将会重写基础工厂方法,使其返回不同类型的产品。

注意,并不一定每次调用工厂方法都会创建新的实例。工厂方法也可以返回缓存、 对象池或其他来源的已有对象。

四、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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include <iostream>

// 运输基类
// 提供抽象运输接口的声明,具体的运输类都需要给出具体实现
class Transportation {
public:
Transportation() {}
virtual ~Transportation() {}

// 抽象物流类声明的抽象运输接口
virtual void deliver() = 0;
};

// 陆路卡车运输
class TruckTransportation : public Transportation {
public:
TruckTransportation() {}
~TruckTransportation() {}

void deliver() override {
std::cout << "I'm TruckTransportation." << std::endl;
}
};

// 陆路火车运输
class TrainTransportation : public Transportation {
public:
TrainTransportation() {}
~TrainTransportation() {}

void deliver() override {
std::cout << "I'm TrainTransportation." << std::endl;
}
};

// 海上轮船运输
class ShipTransportation : public Transportation {
public:
ShipTransportation() {}
~ShipTransportation() {}

void deliver() override {
std::cout << "I'm ShipTransportation." << std::endl;
}
};

// 物流工厂基类
// 必须声明一个返回具体运输工具的抽象方法
class LogisticsFactory {
public:
LogisticsFactory() {}
virtual ~LogisticsFactory() {}

// 返回具体运输工具的抽象方法
virtual Transportation *createTranportation() = 0;

// 这里需要注意,工厂的职责除了是创建具体的运输工具
// 同时还要负责一些其它啊的核心业务
// 这些业务依赖与抽象方法返回的运输工具对象
void startTransportation() {
// 调用抽象方法创建的一个运输工具对象
Transportation *tranportation = createTranportation();
// 现在使用该运输工具
tranportation->deliver();
}
};

// 陆路运输工厂
// 需要重写工厂的抽象方法以创建具体的运输工具
class RoadFactory : public LogisticsFactory {
bool m_trainEnabled;

public:
RoadFactory(bool trainEnabled) : m_trainEnabled(trainEnabled) {}
~RoadFactory() {}

Transportation *createTranportation() override {
// 默认采用卡车运输
Transportation *ts = new TruckTransportation;

// 如果火车运输可以用,则改用火车运输
if(m_trainEnabled) {
delete ts;
ts = new TrainTransportation;
}
return ts;
}
};

// 海上运输工厂
class MarineFactory : public LogisticsFactory {
public:
MarineFactory() {}
~MarineFactory() {}

Transportation *createTranportation() override {
return new ShipTransportation;
}
};

// 客户端代码
// 无需了解不同子类返回实际运输对象之间的差别,不关心其具体实现方式。
void clientCode() {
// 客户端程序根据当前配置或环境选择具体的运输类型
LogisticsFactory *log = new MarineFactory;
// log = new RoadFactory(false);
// log = new RoadFactory(true);

log->startTransportation();

delete log;
}

int main() {
clientCode();
}

Execution result:
I'm ShipTransportation.

五、使用场景

  • 当你在编写代码的过程中,如果无法预知对象确切类别及其依赖关系时,可使用工厂方法。

    工厂方法将创建产品的代码与实际使用产品的代码分离,从而能在不影响其他代码的情况下扩展产品创建部分代码。

    例如,如果需要向应用中添加一种新产品,你只需要开发新的创建者子类,然后重写其工厂方法即可。

  • 如果你希望用户能扩展你软件库或框架的内部组件,可使用工厂方法。

    继承可能是扩展软件库或框架默认行为的最简单方法。但是当你使用子类替代标准组件时,框架如何辨识出该子类?

    解决方案是将各框架中构造组件的代码集中到单个工厂方法中,并在继承该组件之外允许任何人对该方法进行重写。

    让我们看看具体是如何实现的。假设你使用开源 UI 框架编写自己的应用。你希望在应用中使用圆形按钮,但是原框架仅支持矩形按钮。你可以使用 圆形按钮Round­Button子类来继承标准的 按钮Button类。但是,你需要告诉 UI框架UIFramework类使用新的子类按钮代替默认按钮。

    为了实现这个功能,你可以根据基础框架类开发子类 圆形按钮 UI UIWith­Round­Buttons ,并且重写其 create­Button创建按钮方法。基类中的该方法返回 按钮对象,而你开发的子类返回 圆形按钮对象。现在,你就可以使用 圆形按钮 UI类代替 UI框架类。就是这么简单!

  • 如果你希望复用现有对象来节省系统资源, 而不是每次都重新创建对象,可使用工厂方法。

    在处理大型资源密集型对象(比如数据库连接、文件系统和网络资源)时,你会经常碰到这种资源需求。

    让我们思考复用现有对象的方法:

    1. 首先,你需要创建存储空间来存放所有已经创建的对象。

    2. 当他人请求一个对象时,程序将在对象池中搜索可用对象。

    3. …然后将其返回给客户端代码。

    4. 如果没有可用对象,程序则创建一个新对象(并将其添加到对象池中)。

    这些代码可不少!而且它们必须位于同一处,这样才能确保重复代码不会污染程序。

    可能最显而易见,也是最方便的方式,就是将这些代码放置在我们试图重用的对象类的构造函数中。但是从定义上来讲,构造函数始终返回的是新对象,其无法返回现有实例。

    因此,你需要有一个既能够创建新对象,又可以重用现有对象的普通方法。这听上去和工厂方法非常相像。

六、优缺点

  • 你可以避免创建者和具体产品之间的紧密耦合。
  • 单一职责原则。你可以将产品创建代码放在程序的单一位置,从而使得代码更容易维护。
  • 开闭原则。无需更改现有客户端代码,你就可以在程序中引入新的产品类型。
  • 应用工厂方法模式需要引入许多新的子类,代码可能会因此变得更复杂。最好的情况是将该模式引入创建者类的现有层次结构中。

七、与其它模式的关系

  • 在许多设计工作的初期都会使用工厂方法模式(较为简单,而且可以更方便地通过子类进行定制),随后演化为使用抽象工厂模式原型模式生成器模式(更灵活但更加复杂)。
  • 抽象工厂模式通常基于一组工厂方法,但你也可以使用原型模式来生成这些类的方法。
  • 你可以同时使用工厂方法迭代器模式来让子类集合返回不同类型的迭代器,并使得迭代器与集合相匹配。
  • 原型并不基于继承,因此没有继承的缺点。另一方面,原型需要对被复制对象进行复杂的初始化。工厂方法基于继承,但是它不需要初始化步骤。
  • 工厂方法是模板方法模式的一种特殊形式。同时,工厂方法可以作为一个大型模板方法中的一个步骤。