【设计模式】工厂方法和原型模式

本文最后更新于:2023年12月17日 下午

Factory Method 工厂方法 —— 对象创建型模式

1. 意图

定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method是一个类的实例化延迟到其子类。

2. 别名

虚构造器(virtual constructor)

3. 动机

考虑这样一个应用框架,它可以向用户显示多个文档。在这个框架中,两个主要的抽象是Application和Document。这两个类都是抽象的,客户必须通过他们的子类来做以应用相关的实现。例如,为创建一个绘图应用,我们定义类DrawingApplication和DrawingDocument。Application负责管理Document并根据需要创建他们——例如,用户从菜单中选择open或者new的时候。

因为被实例化的特定Document子类是与特定应用相关的,所以Application类不可能预测到哪一个Document子类将被实例化——Application仅仅知道一个新的文档被创建,而不知道哪一种Document被创建。这就产生了一个尴尬的局面:框架必须被实例化,但是他只知道不能被实例化的抽象类。

Factory Method 模式提供了一个解决方案。它封装了哪个Document子类将创建的信息并将这些信息从该框架中分离出来。如下图所示。

Factory Method 解决方案

4. 适用性

在下列情况下可以使用Factory Method模式:

  • 当一个类不知道它所必须创建的对象的类的时候。
  • 当一个类希望由它的子类来指定它所创建的对象的时候。
  • 当类创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。

5. 结构

Factory Method 结构图

6. 参与者

  • Product(Document)
    • 定义工厂方法所创建的对象的接口
  • ConcreteProduct(MyDocument)
    • 实现Product接口
  • Creator(Application)
    • 声明工厂方法,该方法返回一个Product类型的对象。Creator也可以定义一个工厂方法的缺省实现,它返回一个缺省的ConcreteProduct对象
    • 可以调用工厂方法以创建一个Product对象
  • ConcreteCreator(MyApplication)
    • 重定义工厂方法以返回一个ConcreteProduct实例。

7. 协作

Creator依赖于它的子类来定义工厂方法,所以它返回一个适当的ConcreteProduct实例。

8. 效果

工厂方法不再将与特定应用有关的类绑定到你的代码中。代码仅处理Product接口,因此他可以与用户定义的任何ConcreteProduct类一起使用。

工厂方法的一个潜在缺点在于,客户可能仅仅为了创建一个特定的ConcreteProduct对象,就不得不创建Creator的子类。当Creator子类不是必需的时候,客户现在必然要处理类演化的其他方面。

9. 实现

当应用Factory Method模式时要考虑下面一些问题:

1)主要有两种不同的情况。① Creator类是一个抽象类并且不提供它所声明的工厂方法的实现。② Creator是一个具体的类而且为工厂方法提供一个缺省的实现。

2)参数化工厂方法。该模式的另一种情况使得工厂方法可以创建多种产品。工厂方法采用一个参数来标识要被创建的对象种类。

3)特定语言的变化和问题。

4)使用模板以避免创建子类。工厂方法另一个潜在的问题是它们可能仅为了创建适当的Product对象而迫使你创建Creator子类。在C++中另一个解决办法是提供Creator的一个模板子类,他使用Product类作为模板参数。

5)命名约定。使用命名约定是一个好习惯,他可以清楚地说明你正在使用工厂方法。

10. 示例代码

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
// FactoryMethod.h
// Created by 张宇轩 on 2022/4/9.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>
#include <string>
#include <stack>
using namespace std;
#ifndef CREATIONPATTERNDESIGN_FACTORYMETHOD_H
#define CREATIONPATTERNDESIGN_FACTORYMETHOD_H

void test_factory_method();

// 文档,抽象的一个产品类 Product ,提供默认实现
class Document{
public:
virtual void Open() {
if (is_open()) {
cout << "文档已经打开了!" << endl;
} else {
status = 1;
cout << "打开该文档,现在可以编辑" << endl;
}
}
virtual void Close() {
if (is_open()) {
status = 0;
cout << "文档已关闭" << endl;
} else {
cout << "该文档没有被打开!" << endl;
}
}
virtual void Save(string text) {
if (!is_open()) {
cout << "没有打开该文档,无法保存!" << endl;
return ;
}
prev_context.push(cur_context);
cur_context = text;
}
virtual void Revert() {
if (!is_open()) {
cout << "没有打开该文档,无法回退!" << endl;
return ;
}
if (!prev_context.empty()) {
cur_context = prev_context.top();
prev_context.pop();
}
}

virtual void Show() {
if (!is_open()) {
cout << "没有打开该文档,无法显示内容!" << endl;
return ;
}
cout << "当前文档内容为:" << cur_context << endl;
}
private:
// 文档当前的状态 0表示关闭,1表示打开,2表示有修改
int status;
// 文档的当前内容
string cur_context;
// 文档修改之前的内容
stack<string> prev_context;

protected:
bool is_open(){
return status != 0;
}

};

// 具体的产品类,对文档 Document的一个子类
class MyDocument : public Document {
public:
virtual void Show() {
if (!is_open()) {
cout << "没有打开该文档,无法显示内容!" << endl;
return ;
}
cout << "====文档字体是彩色的====" << endl;
Document::Show();
cout << "==== EOF ====" << endl;
}
};

class Application {
public:
// 工厂方法
virtual Document* CreateDocument() = 0;
virtual void OpenDocument(Document* document) {
document->Open();
}
virtual void CloseDocument(Document* document) {
document->Close();
}
};

// 子类实现工厂方法,返回具体的Document
class MyApplication : public Application {
public:
virtual Document* CreateDocument() {
return new MyDocument;
}
};


#endif //CREATIONPATTERNDESIGN_FACTORYMETHOD_H

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
// FactoryMethod.cpp
// Created by 张宇轩 on 2022/4/9.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>

#include "FactoryMethod.h"

void test_factory_method() {
cout << "——————————开始测试工厂方法模式——————————" << endl;

Application* application = new MyApplication;
Document* doc = application->CreateDocument();
doc->Show();
application->OpenDocument(doc);
doc->Save("hello world");
doc->Show();
doc->Save("say bye");
doc->Save("hello");
doc->Revert();
doc->Show();
application->CloseDocument(doc);
delete doc;
delete application;

cout << "——————————结束测试工厂方法模式——————————" << endl;
}

11. 相关模式

Abstract Factory经常用工厂方法来实现。

工厂方法通常在 Template Method中被调用。

Prototype不需要创建Creator的子类。但是它们通常要求一个针对Product类的Initialize操作。Creator使用Initialize来初始化对象,而Factory Method不需要这样的操作。

Prototype 原型 —— 对象创建型模式

1. 意图

用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

2. 适用性

在以下情况下可以使用Prototype模式:

  • 当一个系统应该独立于它的产品创建、构成和表示时。
  • 当要实例化的类是在运行时指定时,例如,通过动态装载。
  • 为了避免创建一个与产品类层次平行的工厂类层次时
  • 当一个类的实例只能有几个不同状态组合中的一种时。建立相应数目的原型并克隆他们可能比每次都用合适的状态手工实例化该类更方便一些。

3. 结构

Prototype结构图

4. 参与者

  • Prototype
    • 声明一个克隆自身的接口
  • ConcretePrototye
    • 实现一个克隆自身的操作
  • Client
    • 让一个原型克隆自身而创建一个新的对象

5. 协作

客户请求一个原型克隆自身

6. 效果

Prototype 有许多与 Abstract Factory和Builder 一样的效果:它对客户隐藏了具体的产品类,因此减少了客户知道的名字和数目。此外,这些模式使客户无需改变即可使用与特定应用相关的类。

下面列出Prototype模式的另外一些优点:

1)运行时增加和删除产品。Prototype允许只通过客户注册原型实例就将一个新的具体产品类并入系统。它比其他创建型模式更为灵活,因为客户可以在运行时建立和删除原型。

2)改变值以指定新对象。高度动态的系统允许你通过对象组合定义新的行为——例如,通过为一个对象变量指定值——并且不定义新的类。你通过实例化已有类并且将这些实例注册为客户对象的原型,就可以有效定义新类别的对象。

3)改变结构以指定新对象

4)减少子类的构造

5)用类动态配置应用。一个希望动态载入类的实例的应用不能静态引用类的构造器,而应该由运行环境在载入时自动创建每个类的实例,并用原型管理器来注册这个实例。

Prototype的主要缺陷是每一个Prototype的子类都必须实现Clone操作,这可能很困难。

7. 实现

当实现原型时,要考虑下面一些问题:

1)使用一个原型管理器。当一个系统中原型数目不固定时(也就说,他们可以动态创建和销毁),要保持一个可用原型的注册表。客户不会自己管理原型,但会在注册表中存储和检索原型。客户在克隆一个原型前会向注册表请求该原型。

2)实现克隆操作。Prototype模式最困难的部分在于正确实现Clone操作。当对象结构包含循环引用时,这尤为棘手。

3)初始化克隆对象。

8. 示例代码

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
// Prototype.h
// Created by 张宇轩 on 2022/4/9.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>

using namespace std;
#ifndef CREATIONPATTERNDESIGN_PROTOTYPE_H
#define CREATIONPATTERNDESIGN_PROTOTYPE_H

void test_prototype();

// 抽象的文具类
class Stationery {
public:
virtual void use() {
cout << "这是一个文具" << endl;
}
virtual Stationery* clone() = 0;
};

class Pen : public Stationery{
public:
virtual void use() {
cout << "这是一只笔" << endl;
}
// 这里就简单实现为浅拷贝
virtual Stationery* clone() {
return new Pen();
}
};

class Eraser : public Stationery {
virtual void use() {
cout << "这是一块橡皮擦" << endl;
}
virtual Stationery* clone() {
return new Eraser();
}
};

class StationeryFactory {
public:
StationeryFactory(Stationery* stationery) : prototype(stationery) {

}
Stationery* create_stationery(){
return prototype->clone();
}
~StationeryFactory(){
if (prototype != nullptr) {
delete prototype;
}
}
private:
// 要生产文具的原型
Stationery* prototype;
};

#endif //CREATIONPATTERNDESIGN_PROTOTYPE_H

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
// Prototype.cpp
// Created by 张宇轩 on 2022/4/9.
// Copyright (c) 2022 zhangyuxuan. All rights reserved.
//
#include <iostream>

#include "Prototype.h"

void test_prototype(){
cout << "——————————开始测试原型模式——————————" << endl;
StationeryFactory pen_factory(new Pen);
Stationery* pen1 = pen_factory.create_stationery();
Stationery* pen2 = pen_factory.create_stationery();
pen1->use();
pen2->use();
StationeryFactory eraser_factory(new Eraser);
Stationery* eraser = eraser_factory.create_stationery();
eraser->use();

delete pen1;
delete pen2;
delete eraser;

cout << "——————————结束测试原型模式——————————" << endl;
}

9. 相关模式

Prototype和 Abstract Factory模式在某些方面是相互竞争的。但是他们也可以一起使用。Abstract Factory 可以存储一个被克隆的原型集合,并且返回产品对象。

大量使用 Composite 和 Decorator模式的设计也可以从Prototype模式获益。


【设计模式】工厂方法和原型模式
https://2017zhangyuxuan.github.io/2022/04/09/2022-04/2022-04-09 【设计模式】工厂方法和原型模式/
作者
Zhang Yuxuan
发布于
2022年4月9日
许可协议