文章

详解六大常用创建型设计模式

通过应用场景引入不同的创建型模式的作用,提供相应的设计模式例程,并解析这些模式的优缺点与简单优化等

详解六大常用创建型设计模式

一、关于创建型模式

  • 创建型模式(Creational Pattern)对类的实例化进行抽象封装,使得对象的使用和创建分离
    • 外界只需知道这些对象共同的接口,而无需知晓其具体的实现细节,符合单一职责原则

二、简单工厂模式

2.1 模式结构

  • 简单工厂(Simple Factory)模式又称为静态工厂方法(Static Factory Method)模式,其提供一个工厂类负责创建特定类(通常是派生自同一基类的子类)实例(根据不同参数返回不同类实例)
    • 假如有一个按钮基类,其具有若干派生类,各子类由于属性差异具有不同的外观
    • 我们用一组enum枚举指代具体子类,这样在实例化具体按钮时就无需知道具体子类的名字
    • 然后提供一个依据该参数创建并返回对应具体按钮对象的函数(一般是switch枚举)
  • 该模式一般由以下三个部分组成
    • 抽象产品(Product):抽象基类,描述所有具体产品的公共接口
    • 具体产品(Concrete Product):对抽象产品类的具体实现
    • 工厂(Factory):实现依据参数创建具体产品实例的逻辑

简单工厂模式UML图.png

2.2 代码实现

  • Product.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef _PRODUCT_HPP_
#define _PRODUCT_HPP_

//产品的抽象类
class Product
{
public:
    virtual ~Product() = 0;
    
    virtual void Use() = 0;
};

//纯虚析构需实现
Product::~Product() {}

#endif
  • ConcreteProduct.hpp
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
#ifndef _CONCRETE_PRODUCT_HPP_
#define _CONCRETE_PRODUCT_HPP_

#include <iostream>
#include "Product.hpp"

//对产品抽象类的其中一种A实现
class ConcreteProductA :public Product
{
public:
    //当基类的析构是虚函数时,派生类析构自动成为虚析构,不必用virtual或override修饰
    ~ConcreteProductA();
    
    void Use() override;
};

//对产品抽象类的其中一种B实现
class ConcreteProductB :public Product
{
public:
    ~ConcreteProductB();
    
    void Use() override;
};

ConcreteProductA::~ConcreteProductA() {}

void ConcreteProductA::Use()
{
    std::cout << "Use Product A\n";
}

ConcreteProductB::~ConcreteProductB() {}

void ConcreteProductB::Use()
{
    std::cout << "Use Product B\n";
}

#endif
  • SimpleFactory.hpp
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
#ifndef _SIMPLE_FACTORY_HPP_
#define _SIMPLE_FACTORY_HPP_

#include <vector>
#include "ConcreteProduct.hpp"

enum ProductType { A, B };

class SimpleFactory
{
private:
    //存储创建的对象,析构时释放
    std::vector<Product*> products;

public:
    //释放开辟在堆区的对象
    ~SimpleFactory();
    
    //通过传入参数创建某个产品
    Product* CreatProduct(ProductType);
};

SimpleFactory::~SimpleFactory()
{
    for (Product* _product : products)
        delete _product;
}

Product* SimpleFactory::CreatProduct(ProductType _idx)
{
    Product* _product;

    //枚举在被switch时会被强制转换为整型来判断
    switch (_idx)
    {
    case ProductType::A:
        _product = new ConcreteProductA();
        break;
    case ProductType::B:
        _product = new ConcreteProductB();
        break;
    default:
        _product = nullptr;
    }

    //将产品维护到列表内,然后再返回
    products.emplace_back(_product);
    return _product;
}

#endif
  • Test.cpp
1
2
3
4
5
6
7
8
9
10
#include "SimpleFactory.hpp"

int main()
{
    SimpleFactory factory;
    factory.CreatProduct(ProductType::A)->Use();
    //Use Product A
    factory.CreatProduct(ProductType::B)->Use();
    //Use Product B
}

2.3 模式分析

2.3.1 扩展性分析

  • 简单工厂模式具有以下缺点
    • 由于该模式是静态的,唯一工厂类的职责过重,增加新的产品类型时需侵入式修改工厂类的switch逻辑,这违背了开闭原则
    • 该模式的唯一工厂类集中了所有产品创建逻辑,一旦该工厂无法正常工作,整个系统都要受到影响,这违背了单一职责原则
  • 综上所述,简单工厂模式只适用于产品类型总数较少时

三、工厂方法模式

工厂方法模式中的每个具体工厂一般只负责生产唯一一个具体派生类的产品;当只有唯一一个具体工厂,且其可创建所有派生产品实例、创建方法为静态方法时,工厂方法模式退化为简单工厂模式

3.1 模式结构

  • 工厂方法(Factory Method)模式又称工厂模式、虚拟构造器(Virtual Constructor)模式、多态工厂(Polymorphic Factory)模式,基类工厂负责声明创建产品对象的公共接口,而子类工厂则负责生成具体的产品对象(将产品类的实例化操作延迟到工厂子类中完成)
    • 在前文简单工厂模式中的按钮例子中,我们定义了唯一一个按钮工厂类,其统一负责所有按钮子类的创建,而工厂方法模式不同,其先定义抽象的按钮工厂类,再定义具体的子类按钮工厂,最后将具体子类按钮的创建过程交给专门的工厂完成
    • 当我们引进新的按钮子类时,无需修改任何原本的按钮工厂子类,只需为这种新类型按钮创建一个具体工厂类即可,而无需像简单工厂模式那样侵入式修改唯一的工厂类中的switch语句,更加符合开闭原则
  • 该模式一般由以下三个部分组成(抽象工厂和抽象产品的创建接口匹配)
    • 抽象产品(Abstract Product)
    • 具体产品(Concrete Product)
    • 抽象工厂(Abstract Factory)
    • 具体工厂(Concrete Factory)

工厂方法模式UML图.png

3.2 代码实现

  • Product.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef _PRODUCT_HPP_
#define _PRODUCT_HPP_

//产品的抽象类
class Product
{
public:
    virtual ~Product() = 0;
    
    virtual void Use() = 0;
};

//纯虚析构也需实现
Product::~Product() {}

#endif
  • ConcreteProduct.hpp
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
#ifndef _CONCRETE_PRODUCT_HPP_
#define _CONCRETE_PRODUCT_HPP_

#include <iostream>
#include "Product.hpp"

//对产品抽象类的其中一种A实现
class ConcreteProductA :public Product
{
public:
    ~ConcreteProductA();

    void Use() override;
};

//对产品抽象类的其中一种B实现
class ConcreteProductB :public Product
{
public:
    ~ConcreteProductB();

    void Use() override;
};

ConcreteProductA::~ConcreteProductA() {}

void ConcreteProductA::Use()
{
    std::cout << "Use Product A\n";
}

ConcreteProductB::~ConcreteProductB() {}

void ConcreteProductB::Use()
{
    std::cout << "Use Product B\n";
}

#endif
  • Factory.hpp
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
#ifndef _FACTORY_HPP_
#define _FACTORY_HPP_

#include <vector>
#include "Product.hpp"

//抽象的工厂类
class Factory
{
protected:
    std::vector<Product*> products;

public:
    //销毁开辟在堆区的产品的虚析构函数
    virtual ~Factory();
    
    virtual Product* CreateProduct() = 0;
};

Factory::~Factory()
{
    for (Product* _product : products)
        delete _product;
}

#endif
  • ConcreteFactory.hpp
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
#ifndef _CONCRETE_FACTORY_HPP_
#define _CONCRETE_FACTORY_HPP_

#include "Factory.hpp"
#include "ConcreteProduct.hpp"

//通过不同的具体工厂,来返回不同的产品,所以仅如果新增了产品的话,仅需新实现一个对应的具体工厂即可
class ConcreteFactoryA :public Factory
{
public:
    ~ConcreteFactoryA();
    
    Product* CreateProduct() override;
};

class ConcreteFactoryB :public Factory
{
public:
    ~ConcreteFactoryB();
    
    Product* CreateProduct() override;
};

ConcreteFactoryA::~ConcreteFactoryA() {}

Product* ConcreteFactoryA::CreateProduct()
{
    Product* _product = new ConcreteProductA();
    products.emplace_back(_product);
    return _product;
}

ConcreteFactoryB::~ConcreteFactoryB() {}

Product* ConcreteFactoryB::CreateProduct()
{
    Product* _product = new ConcreteProductB();
    products.emplace_back(_product);
    return _product;
}

#endif
  • Test.cpp
1
2
3
4
5
6
7
8
9
10
11
#include "ConcreteFactory.hpp"

int main()
{
    ConcreteFactoryA fa;
    fa.CreateProduct()->Use();
    //Use Product A
    ConcreteFactoryB fb;
    fb.CreateProduct()->Use();
    //Use Product B
}

3.3 模式分析

3.3.1 扩展性分析

  • 添加新的具体产品种类时
    • 只需添加具体产品类和具体工厂类,而无须修改抽象工厂与其它具体工厂、抽象产品提供的接口与其它具体产品等,可扩展性强,完全符合开闭原则
    • 但是会增加两倍的新类(具体产品类和具体工厂类),增加了系统的复杂度,造成了额外的编译和运行开销

3.3.2 对象池优化

  • 在塔防或即时战略游戏等的场景中,往往存在着巨量实例,其频繁地创建与销毁开销极大
    • 可以将工厂已经创建的产品保存(池化)到一个容器(对象池)中,根据客户端对产品的请求查询该容器,若满足则直接返回已有对象,否则实例化新对象添加到容器内后再返回
    • 这样就能够实现产品对象的重复使用,以降低内存空间占用与性能开销
  • 对象池优化与享元模式都旨在复用已有对象,以减少内存占用与性能消耗,但要注意区别
    • 对象池中的可复用对象是无状态的,或每次取出时会重置状态(例如小兵单位),这类对象一般创建成本高,且生命周期较短(对象池优化旨在减少对象的创建与销毁)
    • 享元模式中的可复用对象是不可变的(例如游戏纹理、音效、3D模型等共享资源),且需跨多个上下文共享(享元模式旨在减少共享资源的重复加载

四、抽象工厂模式

抽象工厂模式中的每个具体工厂一般只负责生产不同基类的各唯一一个派生类产品,单个工厂里的不同类产品具有一定的关联关系(例如同一品牌);当每个具体工厂类只存在一个产品等级结构,且只创建同一基类的唯一一类产品对象时,抽象工厂模式退化成工厂方法模式

4.1 模式结构

  • 引入两个概念方便理解
    • 产品等级结构:指的是产品的抽象基类与其若干派生类的继承结构(即同一产品种类)
    • 产品族:指的是由同一品牌(工厂)生产的不同产品等级结构中的一组产品(即工厂不止生产单一基类的派生产品,而是生产具有一定联系的多个基类的派生产品)
  • 抽象工厂(Abstract Factory)模式又称为Kit模式,其提供创建位于不同产品等级结构中不同基类的具体产品的接口
    • 抽象工厂模式的不同具体工厂分别负责一个产品族的生产,其内包含不同类型的产品(针对多个产品等级结构,即一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建),但其生产的都是属于同一个品牌的产品(而工厂方法模式的不同具体工厂负责的都是针对单个产品等级结构派生自同一基类的产品)
    • 当同一产品族中的多个不同基类派生种类的对象被设计成一起协同工作时,可通过仅开放单一具体工厂的生产权限,以保证客户端始终只使用同一产品族中的对象,这对一些需要根据当前环境来决定行为的软件系统来说非常实用
  • 抽象工厂模式包含的角色和抽象工厂模式的一样,区别在于单个具体工厂生产的产品登记结构数量前者多个,后者单个
    • 抽象产品(Abstract Product)
    • 具体产品(Concrete Product)
    • 抽象工厂(Abstract Factory)
    • 具体工厂(Concrete Factory)
  • 下图中$Ai$和$Bi$的$i=1,2$,字母$A$或$B$代表不同产品等级结构(不同基类),数字$i$代表不同品牌
    • 工厂$1$生产属于同一品牌的两种不同产品$A1$和$B1$
    • 工厂$2$生产属于同一品牌的两种不同产品$A2$和$B2$

抽象工厂模式UML.png

4.2 代码实现

  • Product.hpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#ifndef _PRODUCT_HPP_
#define _PRODUCT_HPP_

//产品的抽象类
class Product
{
public:
    virtual ~Product() = 0;

    virtual void Use() = 0;
};

//纯虚析构也需实现
Product::~Product() {}

#endif
  • ConcreteProductA
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
#ifndef _CONCRETE_PRODUCT_A_HPP_
#define _CONCRETE_PRODUCT_A_HPP_

#include <iostream>
#include "Product.hpp"

//所有的A产品都属于同一种类(产品等级结构),但其后面的数字代表其属于不同品牌(产品族)
class ConcreteProductA1 :public Product
{
public:
    ~ConcreteProductA1();

    void Use() override;
};

class ConcreteProductA2 :public Product
{
public:
    ~ConcreteProductA2();
    
    void Use() override;
};

ConcreteProductA1::~ConcreteProductA1() {}

void ConcreteProductA1::Use()
{
    std::cout << "Use Product A Of Brand 1\n";
}

ConcreteProductA2::~ConcreteProductA2() {}

void ConcreteProductA2::Use()
{
    std::cout << "Use Product A Of Brand 2\n";
}

#endif
  • ConcreteProductB
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
#ifndef _CONCRETE_PRODUCT_B_HPP_
#define _CONCRETE_PRODUCT_B_HPP_

#include <iostream>
#include "Product.hpp"

//所有的B产品都属于同一种类(产品等级结构),但其后面的数字代表其属于不同品牌(产品族)
class ConcreteProductB1 :public Product
{
public:
    ~ConcreteProductB1();

    void Use() override;
};

class ConcreteProductB2 :public Product
{
public:
    ~ConcreteProductB2();

    void Use() override;
};

ConcreteProductB1::~ConcreteProductB1() {}

void ConcreteProductB1::Use()
{
    std::cout << "Use Product B Of Brand 1\n";
}

ConcreteProductB2::~ConcreteProductB2() {}

void ConcreteProductB2::Use()
{
    std::cout << "Use Product B Of Brand 2\n";
}

#endif
  • Factory.hpp
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
#ifndef _FACTORY_HPP_
#define _FACTORY_HPP_

#include <iostream>
#include <vector>
#include "Product.hpp"

//工厂的抽象类,下面的A、B均是不同产品等级结构(不同种类)的产品,但属于同一产品族(同一品牌)
class Factory
{
protected:
    std::vector<Product*> products;

public:
    ~Factory();

    //生产该工厂所代表品牌的A产品
    virtual Product* CreateProductA() = 0;
    //生产该工厂所代表品牌的B产品
    virtual Product* CreateProductB() = 0;
};

Factory::~Factory()
{
    for (Product* _product : products)
        delete _product;
}

#endif
  • ConcreteFactory
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
#ifndef _CONCRETE_FACTORY_HPP_
#define _CONCRETE_FACTORY_HPP_

#include "Factory.hpp"
#include "ConcreteProductA.hpp"
#include "ConcreteProductB.hpp"

//生产品牌1(产品族1)的所有不同种类(不同产品等级结构)的产品
class ConcreteFactory1 :public Factory
{
public:
    ~ConcreteFactory1();
    
    //生产品牌1的A产品ConcreteProductA1
    Product* CreateProductA() override;
    //生产品牌1的B产品ConcreteProductB1
    Product* CreateProductB() override;
};

//生产品牌2(产品族2)的所有不同种类(不同产品等级结构)的产品
class ConcreteFactory2 :public Factory
{
public:
    ~ConcreteFactory2();
    
    //生产品牌2的A产品ConcreteProductA2
    Product* CreateProductA() override;
    //生产品牌2的B产品ConcreteProductB2
    Product* CreateProductB() override;
};

ConcreteFactory1::~ConcreteFactory1() {}

Product* ConcreteFactory1::CreateProductA()
{
    Product* _product = new ConcreteProductA1();
    products.emplace_back(_product);
    return _product;
}

Product* ConcreteFactory1::CreateProductB()
{
    Product* _product = new ConcreteProductB1();
    products.emplace_back(_product);
    return _product;
}

ConcreteFactory2::~ConcreteFactory2() {}

Product* ConcreteFactory2::CreateProductA()
{
    Product* _product = new ConcreteProductA2();
    products.emplace_back(_product);
    return _product;
}

Product* ConcreteFactory2::CreateProductB()
{
    Product* _product = new ConcreteProductB2();
    products.emplace_back(_product);
    return _product;
}

#endif
  • Test.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include "ConcreteFactory.hpp"

int main()
{
    //测试创建品牌1(产品族1)的不同产品并使用
    ConcreteFactory1 cf1;
    cf1.CreateProductA()->Use();
    //Use Product A Of Brand 1
    cf1.CreateProductB()->Use();
    //Use Product B Of Brand 1

    //测试创建品牌2(产品族2)的不同产品并使用
    ConcreteFactory2 cf2;
    cf2.CreateProductA()->Use();
    //Use Product A Of Brand 2
    cf2.CreateProductB()->Use();
    //Use Product B Of Brand 2
}

4.3 模式分析

4.3.1 扩展性分析

  • 抽象工厂模式可以很方便地添加一个新产品族
    • 只需选择性提供该产品族对应的若干不同产品等级结构的具体产品实现(无需提供新的抽象基类),以及对应的新的具体工厂即可
    • 该过程中的所有操作均是提供新的派生实现而无须侵入式修改原有系统,符合开闭原则
  • 虽然增加新产品族很方便,但增加新的产品等级结构很不方便
    • 这需要我们首先提供新的抽象基类,然后实现其派生类,然后侵入式地为抽象工厂增加针对该新基类产品的接口,然后为每个具体工厂实现这个新生产函数
    • 该过程中不仅包含新的派生实现,还存在侵入式修改,不符合开闭原则

五、建造者模式

5.1 模式结构

  • 思考以下场景
    • 对于拥有大量成员属性的复杂对象,其属性初始化相当于汽车的部件构造,其构建过程相当于把零件组合成一辆汽车的过程,只有对象的所有属性都被初始化后才能正常使用
    • 在构造这类复杂对象过程中,若成员属性的初始化必须符合一定的依赖顺序进行,则如果将该对象的构建过程开放给客户端的话,很容易由于顺序错误导致构建失败
  • 建造者(Builder)模式又称生成器模式,允许用户只通过指定类型和内容来构建复杂对象
    • 客户端传递对产品的需求给建造者,建造者内部严格按照顺序逐步创建复杂产品对象,最终返还给客户端建造完毕的完整产品对象
    • 客户端无须关心该对象所包含的属性,以及建造者内部的具体构建细节
  • 该模式一般包含以下组件(若对组件构造没有顺序要求,则可以省略掉指挥者这一层封装,直接使用具体建造者)
    • 产品(Product):包含特定组件(需初始化的成员属性)的复杂对象
    • 抽象建造者(Abstract Builder):声明产品不同组件的构建方法和返回方法,不指定顺序
    • 具体建造者(Concrete Builder):实现生产具体产品的组件构建方法与完整产品的返回方法
    • 指挥者(Director):控制产品生产过程的组件生产顺序细节,客户端只需提供具体建造者的类型,指挥者调用其相关方法建造并返回完整的产品对象

建造者模式UML.png

  • 以KFC各种套餐的生产为例(也可参考游戏应用,角色包括人体、服装、装备等组成部分,可使用建造者模式对其进行构建,通过不同的具体建造者创建不同类型的角色)
    • 制餐员就是建造者,每个制餐员负责单个套餐制作,实现制餐步骤以及返回完整套餐方法
    • 服务员就是指挥者,负责吩咐不同制餐员去制作套餐,然后将制作完成的套餐交予客户

建造者模式KFC示例.png

5.2 代码实现

  • Product.hpp
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
#ifndef _PRODUCT_HPP_
#define _PRODUCT_HPP_

#include <iostream>

//需要被制造的产品,依据制造细节的不同(差异由具体Builder实现),产出不同的产品
class Product
{
protected:
    std::string part1;
    std::string part2;
    std::string part3;

public:
    ~Product();
    
    void SetPart1(std::string);
    void SetPart2(std::string);
    void SetPart3(std::string);

    //用于测试
    void Use();
};

Product::~Product() {}

void Product::SetPart1(std::string _part)
{
    part1 = _part;
}

void Product::SetPart2(std::string _part)
{
    part2 = _part;
}

void Product::SetPart3(std::string _part)
{
    part3 = _part;
}

void Product::Use()
{
    std::cout << "Use Product: {[" << part1 << "], [" << part2 << "], [" << part3 << "]}\n";
}

#endif
  • Builder.hpp
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
#ifndef _BUILDER_HPP_
#define _BUILDER_HPP_

#include "Product.hpp"

//抽象建造者类,用于被Director调用(传入不同的ConcreteBuilder来获取不同的产品)
class Builder
{
protected:
    //需要被生产出的产品,此处建造者只负责创建对象,而不负责销毁
    Product product;
    
public:
    //纯虚析构函数
    virtual ~Builder() = 0;
    
    //生产完整产品所需要的中间步骤,其实生产的顺序可以依靠Director来调用,但是在Builder内合理排序有助于提升代码可读性
    virtual void ConstructPart1() = 0;
    virtual void ConstructPart2() = 0;
    virtual void ConstructPart3() = 0;
    
    //全部零件生产完成后返回完整产品
    virtual Product* GetBuilding() = 0;
};

//纯虚析构函数也需实现
Builder::~Builder() {}

#endif
  • ConcreteBuilderA.hpp
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
#ifndef _CONCRETE_BUILDER_A_HPP_
#define _CONCRETE_BUILDER_A_HPP_

#include "Builder.hpp"

//为了生产特定产品的具体Builder,是对抽象Builder的一种实现
class ConcreteBuilderA :public Builder
{
public:
    //需要实现父类中的所有纯虚函数,包括纯虚析构函数,否则此类仍为抽象类
    ~ConcreteBuilderA();

    //对各抽象制造步骤的具体实现
    void ConstructPart1() override;
    void ConstructPart2() override;
    void ConstructPart3() override;

    //返回最终产品
    Product* GetBuilding() override;
};

ConcreteBuilderA::~ConcreteBuilderA() {}

void ConcreteBuilderA::ConstructPart1()
{
    product.SetPart1("A Part1");
}

void ConcreteBuilderA::ConstructPart2()
{
    product.SetPart2("A Part2");
}

void ConcreteBuilderA::ConstructPart3()
{
    product.SetPart3("A Part3");
}

Product* ConcreteBuilderA::GetBuilding()
{
    //实际应用中要注意Product类的拷贝构造函数需要设计得安全(深拷贝)
    //因为返回的是一个开辟在堆区的产品,所以需要记得在使用完后清理内存
    return new Product(product);
}

#endif
  • ConcreteBuilderB.hpp
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
#ifndef _CONCRETE_BUILDER_B_HPP_
#define _CONCRETE_BUILDER_B_HPP_

#include "Builder.hpp"

//为了生产特定产品的具体Builder,是对抽象Builder的一种实现
class ConcreteBuilderB :public Builder
{
public:
    //需要实现父类中的所有纯虚函数,包括纯虚析构函数,否则此类仍为抽象类
    ~ConcreteBuilderB();

    //对各抽象制造步骤的具体实现
    void ConstructPart1() override;
    void ConstructPart2() override;
    void ConstructPart3() override;

    //返回最终产品
    Product* GetBuilding() override;
};

ConcreteBuilderB::~ConcreteBuilderB() {}

void ConcreteBuilderB::ConstructPart1()
{
    product.SetPart1("B Part1");
}

void ConcreteBuilderB::ConstructPart2()
{
    product.SetPart2("B Part2");
}

void ConcreteBuilderB::ConstructPart3()
{
    product.SetPart3("B Part3");
}

Product* ConcreteBuilderB::GetBuilding()
{
    //实际应用中要注意Product类的拷贝构造函数需要设计得安全(深拷贝)
    //因为返回的是一个开辟在堆区的产品,所以需要记得在使用完后清理内存
    return new Product(product);
}

#endif
  • Director.hpp
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
#ifndef _DIRECTOR_HPP_
#define _DIRECTOR_HPP_

#include <iostream>
#include "Builder.hpp"

class Director
{
private:
    //以指针的方式存储,Director根据设置的具体Builder的不同,会生产出不同的产品
    Builder* builder = nullptr;

public:
    //设置具体Builder
    void SetBuilder(Builder*);
    //获取产品
    Product* GetProduct();
};

void Director::SetBuilder(Builder* _builder)
{
    builder = _builder;
}

Product* Director::GetProduct()
{
    //在使用Director前,需要先设置具体Builder
    if (builder == nullptr)
        throw std::invalid_argument("ERROR: No Concrete Builder Available For Director");

    //按照顺序使用具体Builder进行零件的生产
    builder->ConstructPart1();
    builder->ConstructPart2();
    builder->ConstructPart3();

    //返回最终成品
    return builder->GetBuilding();
}

#endif
  • Test.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
28
29
30
31
32
#include "Director.hpp"
#include "ConcreteBuilderA.hpp"
#include "ConcreteBuilderB.hpp"

int main()
{
    //准备两个不同的具体Builder,由于Director接收的是指针类型Builder,故而使用指针
    ConcreteBuilderA* ba = new ConcreteBuilderA();
    ConcreteBuilderB* bb = new ConcreteBuilderB();

    //准备一个Director应对客户端的需求
    Director drt;

    //生产A加工型产品
    drt.SetBuilder(ba);
    Product* pa = drt.GetProduct();
    pa->Use();
    //Use Product: {[A Part1], [A Part2], [A Part3]}

    //生产B加工型产品
    drt.SetBuilder(bb);
    Product* pb = drt.GetProduct();
    pb->Use();
    //Use Product: {[B Part1], [B Part2], [B Part3]}

    //记得销毁new出来的内存防止泄露
    delete ba;
    delete bb;
    //具体Builder返回的产品指针是开辟在堆区的,所以要销毁
    delete pa;
    delete pb;
}

5.3 模式分析

5.3.1 扩展性分析

  • 建造者模式使得一个生产方式可以被复用,创建组成相似的不同类产品对象,将产品本身与产品的创建过程解耦
    • 新增产品时只需实现新的具体建造者即可,符合开闭原则
    • 适用于产品共性较强(成员属性组成相似)的场景,不适用于产品间差异较大时

六、原型模式

6.1 模式结构

  • 原型(Prototype)模式的目的是优化对产品对象的拷贝,我们让所有可被拷贝的类继承自一个提供语义明确的克隆虚函数Clone的接口类Prototype并在子类实现克隆函数
    • 假设在客户端中已存在某个子类对象,若想拷贝该对象,直接使用一个Prototype*类型的指针去接收该对象调用的的Clone函数返回值即可完成拷贝,而无需知晓这个对象的类名
    • 这种设计模式常用于游戏开发中的预制体实例化、场景实例化等,或用于模组开发中通过配置文件数据(而无需书写该对象代码)动态生成游戏对象等
  • 那么为什么不直接使用拷贝构造函数或拷贝复制运算符呢?
    • 拷贝构造函数与拷贝赋值运算符是静态的(编译时确定),与具体类相耦合,需知晓具体类名才能拷贝,而原型模式是动态多态的(运行时决定复制行为)
    • 拷贝构造函数与拷贝赋值运算符一般会拷贝对象的所有状态(成员变量值),若客户端希望在拷贝对象时忽略其某些状态而只拷贝部分状态
      • 由于拷贝构造函数等本意就是完整拷贝,且可能存在其它地方需要使用完整拷贝,所以我们不宜直接修改拷贝构造函数等
      • 所以才引入原型模式提供一种新的对象克隆方法,以满足客户端的具体克隆要求

6.2 代码实现

  • Prototype.hpp
1
2
3
4
5
6
7
8
9
10
11
12
#ifndef _PROTOTYPE_HPP_
#define _PROTOTYPE_HPP_

//原型接口,所有需实现复制功能的产品类都应继承该接口,核心是该接口的复制方法
class Prototype
{
public:
    virtual Prototype* Clone() const = 0;   //复制方法
    virtual void ShowInfo() const = 0;      //显示信息
};

#endif
  • Product.hpp
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
#ifndef _PRODUCT_HPP_
#define _PRODUCT_HPP_

#include <iostream>
#include "Prototype.hpp"

//可被复制的具体产品类
class Product :public Prototype
{
private:
    std::string name = "";                //只需浅拷贝的属性
    int* attr = nullptr;                  //需要深拷贝的属性

public:
    Product(std::string, int);
    Product(const Product&);              //实现深拷贝的拷贝构造函数
    ~Product();

    Prototype* Clone() const override;    //实现接口中的对象拷贝虚函数
    void ShowInfo() const override;       //实现接口中的信息显示虚函数
};

Product::Product(std::string _name, int _val) :name(_name)
{
    //开辟在堆区的成员,需深拷贝以及析构
    attr = new int(_val);
}

Product::Product(const Product& _other) :name(_other.name)
{
    //深拷贝
    attr = new int(*_other.attr);
}

Product::~Product()
{
    //销毁堆区成员
    delete attr;
    attr = nullptr;
}

Prototype* Product::Clone() const
{
    //此处仅使用拷贝构造函数克隆自身
    return new Product(*this);
    //若有不同的拷贝需求,例如希望只拷贝部分属性值(而其它值使用初始值),则可在此函数内实现,而不是直接修改拷贝构造
}

void Product::ShowInfo() const
{
    std::cout << "[name=" << name << "], [*attr=" << *attr << "]\n";
}

#endif
  • Test.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "Product.hpp"

int main()
{
    Product* p1 = new Product("p1", 99);
    //注意此处接收p1克隆体的p2的类型为Prototype*而非Product*
    //这使得在拷贝p1的时候无需知晓p1的具体类型,统一用Prototype*原型接收即可
    Prototype* p2 = p1->Clone();

    p1->ShowInfo();
    //[name=p1], [*attr=99]
    p2->ShowInfo();
    //[name=p1], [*attr=99]
}

七、单例模式

7.1 模式结构

  • 单例(Singleton)模式又称单件模式或单态模式,其确保某单例类具有唯一的全局共享实例,以保证被访问资源的统一性,适合需要统一管理使用的对象概念(其同时承担了实例化和功能使用两种职责,违背了单一职责原则)
    • 系统中的某些类在逻辑上必须最多只存在唯一一个实例,比如可以存在多个打印任务但只能有一个正在工作中的任务、一个运行的游戏只能有一个音频/玩家/场景管理器等

单例模式UML.png

7.2 懒汉饿汉

  • 单例模式分为懒汉和饿汉两种实现方法
  • 懒汉式(Lazy Initialization)
    • 实例在第一次使用时才被创建,优点是节省资源,若单例从未使用就不会被创建
    • 缺点是线程不安全,在首次调用GetInstance()时,多个线程可能同时检查实例存在性并尝试创建,导致竞态条件(Race Condition),最终可能创建多个实例从而破坏单例的唯一性
  • 饿汉式(Eager Initialization)
    • 实例在程序启动时就创建,优点是线程安全,因为实例在程序刚启动时就被初始化
    • 缺点是如果单例一直未被使用,则会造成资源浪费

7.3 代码实现

  • SingletonLazy.hpp
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
#ifndef _SINGLETON_LAZY_HPP_
#define _SINGLETON_LAZY_HPP_

// //可继承单例Manager模板类实现
// template <typename T>
// class Manager
// {
// protected:
// 	static T* manager;                              //静态的唯一Manager类对象的指针,在外部初始化
//
// public:
// 	static T* GetInstance();                        //静态的函数,用于获取Manager类的唯一实例指针
//
// protected:
// 	Manager() = default;                            //保护构造函数,单例模式的类不应当能被在外部创建对象
// 	~Manager() = default;                           //保护析构函数
// 	Manager(const Manager&) = delete;               //拷贝构造函数的调用无效
// 	Manager& operator=(const Manager&) = delete;    //运算符=的重载的调用无效
// };
//
// //初始化静态成员变量(我们也可以将manager实例作为函数GetInstance的局部静态变量,效果一样)
// template <typename T>
// T* Manager<T>::manager = nullptr;
//
// template <typename T>
// T* Manager<T>::GetInstance()
// {
// 	//若manager未被创建,则在堆区创建一个,这是懒汉式实现
// 	if (manager == nullptr)
// 		manager = new T();
//
// 	//这样我们就可以在外部通过Manager* xxxx = Manager::GetInstance();获取内部这个Manager对象的地址,而不是创建一个新的Manager
// 	return manager;
// }

//以下是保证了线程安全的懒汉式实现,因为C++11规定局部静态变量的初始化是线程安全的
template <typename T>
class Manager
{
public:
	static T* GetInstance();

protected:
    Manager() = default;
	~Manager() = default;
	Manager(const Manager&) = delete;
	Manager& operator=(const Manager&) = delete;
};

//当多个线程同时调用该函数时,C++运行时库会确保static T manager;只被初始化一次,且其他线程会等待初始化完成后再访问它
template <typename T>
T* Manager<T>::GetInstance()
{
    //创建局部静态变量,并返回其引用
    static T manager;
    return &manager;
}

#endif
  • SingletonEager.hpp
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
#ifndef _SINGLETON_EAGER_HPP_
#define _SINGLETON_EAGER_HPP_

template <typename T>
class Manager
{
protected:
	static T* manager;

public:
    static T* GetInstance();

protected:
    Manager() = default;
	~Manager() = default;
	Manager(const Manager&) = delete;
	Manager& operator=(const Manager&) = delete;
};

//在程序启动时就初始化,这是饿汉式实现
template <typename T>
T* Manager<T>::manager = new T();

template <typename T>
T* Manager<T>::GetInstance()
{
    //直接返回在程序刚开始时就初始化好的manager
    return manager;
}

#endif
  • GameManager.hpp
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
#ifndef _GAME_MANAGER_HPP_
#define _GAME_MANAGER_HPP_

#include <iostream>
//采用饿汉式实现
#include "SingletonEager.hpp"

//游戏主管理器
class GameManager : public Manager<GameManager>
{
	//授予Manager基类的GetInstance函数权限以调用GameManager类的构造函数
	friend class Manager<GameManager>;

public:
	int Run();

protected:
	GameManager() = default;
	~GameManager() = default;
};

int GameManager::Run()
{
	std::cout << "GamaManager Run Success\n";
	return 0;
}

#endif
  • Test.cpp
1
2
3
4
5
6
7
8
9
#include "GameManager.hpp"

int main()
{   
    //运行游戏主管理器内的主循环方法,并获取返回值
    //注意这里获取的实例是开辟在堆区的,但此处可以不用delete,系统会给你擦屁股(虽说最好处理一下,但是单例模式问题不大)
    return GameManager::GetInstance()->Run();
    //GamaManager Run Success
}

7.4 模式分析

7.4.1 生命周期与滥用

  • 单例类的生命周期无法在类外控制(单例一旦创建,则存在于整个应用程序生命周期)
    • 这会导致无法按需销毁单例(即使不再需要,也无法被垃圾回收)
    • 单例类似全局变量,长期存在容易过多状态,导致难以调试的未知错误
  • 不是所有需要全局访问的对象都适合单例模式,不应该滥用该模式
    • 滥用会由于单例无法被人为控制销毁,容易导致单例累积而占用内存
    • 滥用可能导致单例模式代码间耦合度高而难以测试

7.4.2 线程安全问题

  • 多线程环境下,普通的懒汉式实现可能导致线程不安全,有以下解决方法
    • 在C++11后在成员函数局部初始化静态实例
    • 使用饿汉式实现,初始化类的成员静态实例为new T()而不是nullptr

7.4.3 不可被继承

  • 除了单例模板类可以被继承一级外,其它单例类(包括单例模板类的第一级派生类)都不应该被继承,因为会破坏单例性

7.4.4 被回收可能

  • Java和C#等运行环境提供了自动垃圾回收GC,若实例化的对象长时间未被利用,就会被认为是垃圾然后被自动销毁回收,下次利用时又将重新实例化而导致对象状态的丢失
本文由作者按照 CC BY-NC-SA 4.0 进行授权