C++设计模式-工厂(2)

设计模式:在解决某一类问题场景时,有既定的,优秀的代码框架可以直接使用,其优势如下:

  • 代码更易于维护,代码的可读性,复用性,可移植性,健壮性会更好
  • 当软件原有需求有变更或者增加新的需求时,合理的设计模式的应用,能够做到软件设计要求的开-闭原则,即对修改关闭,对扩展开放,使软件原有功能修改,新功能扩充非常灵活
  • 合理的设计模式的选择,会使软件设计更加模块化,积极的做到软件设计遵循的根本原则高内聚,低耦合

软件设计开-闭原则

开闭原则是软件设计中的一个重要原则,指的是软件实体(如类、模块、函数等)应该对扩展开放,对修改关闭。这意味着在不修改现有代码的基础上,通过扩展新功能来满足新的需求,从而提高软件的灵活性和可维护性

对扩展开放:允许在现有系统中添加新功能

对修改关闭:不需要修改现有的代码来添加新功能,避免引入新错误

通过遵循开闭原则,可以提高代码的可复用性和稳定性。常用的方法包括使用抽象类、接口以及多态等技术

创建型设计模式

创建型设计模式关注对象的创建过程,旨在抽象实例化的过程。以下是几种常见的创建型设计模式

工厂方法模式(Factory Method Pattern)

抽象工厂模式(Abstract Factory Pattern)

单例模式(Singleton Pattern)

生成器模式(Builder Pattern)

原型模式(Prototype Pattern)



一、工厂模式

工厂模式,会从如下三个部分进行介绍:

  • 简单工厂
  • 工厂方法模式
  • 抽象工厂模式

1.简单工厂

简单工厂模式是一种创建型设计模式,用于根据传入的参数决定创建哪种类的实例。虽然它不属于设计模式的“正式”分类,但在实际应用中非常常见

(1)问题产生
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
// 产品基类
class Car{
public:
Car(string name):_name(name){}
// 纯虚方法
virtual void show() = 0;

protected:
string _name;
};

// 具体产品类Bwm
class Bwm:public Car{
public:
// 使用成员列表初始化的方式,初始化基类的成员变量,通过基类构造函数,创建基类对象
Bwm(string name): Car(name){}
// 重写继承自基类的纯虚函数
void show()
{
cout << "获取了一辆宝马汽车" << _name << endl;
}
};

// 具体产品类Audi
class Audi:public Car{
public:
Audi(string name): Car(name){}
void show()
{
cout << "获取了一辆奥迪汽车" <<_name << endl;
}
};

int main()
{
Car *p1 = new Bwm("X1");
Car *p2 = new Audi("A6");
p1->show();
p2->show();
delete p1;
delete p2;
return 0;
}

在上述代码存在问题,作为开发者,需要记住产品派生类的名字,进而创建出对应的对象(直接使用具体的产品类创建具体的类的对象),这样理解:

4S店买车,我们简单的做法,就是看好车之后,直接问4S店购买一辆对应的车(宝马、奥迪)。但是根据上述的代码,就相当于用户需要车,还需要自己去车间,亲自制造一辆奥迪、宝马车new一辆

因此引出后文的简单工厂,其中工厂类就相当于4S店,调用相关的接口就可以获得一辆需要的车(产品类实例对象)

(2)简单工厂代码
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
/**********************抽象产品基类************************/
class Car{
public:
Car(string name):_name(name){}
// 纯虚方法
virtual void show() = 0;

protected:
string _name;
};
/**********************抽象产品基类************************/


/************************具体产品*************************/
// 具体产品类Bwm
class Bmw:public Car{
public:
// 使用成员列表初始化的方式,初始化基类的成员变量,通过基类构造函数,创建基类对象
Bmw(string name): Car(name){}
// 重写继承自基类的纯虚函数
void show()
{
cout << "获取了一辆宝马汽车" << _name << endl;
}
};

// 具体产品类Audi
class Audi:public Car{
public:
Audi(string name): Car(name){}
void show()
{
cout << "获取了一辆奥迪汽车" <<_name << endl;
}
};
/************************具体产品*************************/


// 具体产品枚举
enum CarType{
BMW,
AUDI,
};


/************************简单工厂类*************************/
// 根据传入的参数决定创建哪种类的实例
class SimpleFactory{
public:
Car *createCar(CarType ct)
{
switch (ct)
{
case BMW:
return new Bmw("X1");
break;
case AUDI:
return new Audi("A6");
break;;
default:
cerr << "传入工厂的参数不正确: ";
break;
}
return nullptr;
}
};
/************************简单工厂类*************************/

int main()
{
// 获取工厂--使用智能指针--自动释放资源
unique_ptr<SimpleFactory> factory(new SimpleFactory());

// 在工厂中创建具体的产品
// factory->createCar 返回的是Car的派生类的地址
// 指向基类的引用/指针可引用派生类对象不需要显示转换
unique_ptr<Car> p1(factory->createCar(BMW));
unique_ptr<Car> p2(factory->createCar(AUDI));

p1->show();
p2->show();

return 0;
}

在上述的基础上,使用智能指针,对工厂new出的类实例对象,不需要用户人为的delete,可以自动释放资源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 只有main 函数不同
int main()
{
// 获取工厂--使用智能指针--自动释放资源
unique_ptr<SimpleFactory> factory(new SimpleFactory());

// 在工厂中创建具体的产品
// factory->createCar 返回的是Car的派生类的地址
// 指向基类的引用/指针可引用派生类对象不需要显示转换
unique_ptr<Car> p1(factory->createCar(BMW));
unique_ptr<Car> p2(factory->createCar(AUDI));

p1->show();
p2->show();

return 0;
}

在上述代码工厂类SimpleFactorycreateCar方法中,根据传入的参数决定创建哪种类的实例

结论:将对象的创建封装在一个接口函数里面,通过传入不同的标识,返回创建的对象

(3)优缺点

优点:

简化对象创建:客户端无需直接实例化对象,简化了对象创建过程

集中管理对象创建:对象的创建逻辑集中在工厂类中,便于维护和修改

缺点:

违反开闭原则每新增一种产品都需要修改工厂类,不符合开闭原则(对扩展开放,修改关闭

单一职责原则工厂类承担了过多的职责,容易变得复杂

适用场景

需要根据条件创建不同类型对象:根据传入的参数决定创建哪种类的实例。

客户端不需要了解产品的具体实现:客户端只需知道如何使用产品接口,无需关心具体实现


2.工厂方法模式

工厂方法模式(Factory Method Pattern)是一种创建型设计模式,定义了一个用于创建对象的接口,但由子类决定实例化哪个类。这样使得一个类的实例化延迟到其子类

工厂方法,相较于简单工厂,满足了软件设计的 开闭原则

工厂方法相较于简单方法的升级

划分了一个继承结构,工厂基类Factory,封装了一个纯虚方法createCar()工厂方法

一个工厂的派生类就代表一个具体工厂,一个具体工厂就产生一个具体的产品实例(重写纯虚方法)

符合开闭原则,若像新增别的具体工厂,不需要修改之前的内容,而是新增一个工厂的派生类对应一个具体的工厂

工厂方法主要角色

抽象工厂(Creator)声明一个工厂方法(重点:一个),返回一个产品对象。可以调用工厂方法以代替直接实例化产品对象

具体工厂(Concrete Creator):实现工厂方法,返回具体产品实例

抽象产品(Product):定义产品对象的接口

具体产品(Concrete Product):实现产品接口

(1)工厂方法代码示例
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
/**********************抽象产品基类************************/
class Car{
public:
Car(string name):_name(name){}
// 纯虚方法
virtual void show() = 0;

protected:
string _name;
};
/**********************抽象产品基类************************/


/************************具体产品*************************/
// 具体产品类Bwm
class Bmw:public Car{
public:
// 使用成员列表初始化的方式,初始化基类的成员变量,通过基类构造函数,创建基类对象
Bmw(string name): Car(name){}
// 重写继承自基类的纯虚函数
void show()
{
cout << "获取了一辆宝马汽车" << _name << endl;
}
};

// 具体产品类Audi
class Audi:public Car{
public:
Audi(string name): Car(name){}
void show()
{
cout << "获取了一辆奥迪汽车" <<_name << endl;
}
};
/************************具体产品*************************/


/**********************抽象工厂基类***********************/
class Factory{
public:
// 工厂方法
virtual Car* createCar(string name) = 0;
};
/**********************抽象工厂基类***********************/


/************************具体工厂*************************/
// 具体工厂--BMW工厂
class BmwFactory:public Factory{
public:
// 重写工厂类的工厂方法
Car *createCar(string name)
{
return new Bmw(name);
}
};

// 具体工厂--奥迪工厂
class AudiFactory:public Factory{
public:
Car *createCar(string name)
{
return new Audi(name);
}
};
/************************具体工厂*************************/

int main()
{
// 创建一个抽象工厂类对象
// 指向基类的引用/指针可引用派生类对象不需要显示转换
unique_ptr<Factory> bmwfty(new BmwFactory());
unique_ptr<Factory> audifty(new AudiFactory());
// 使用具体工厂创建具体产品实例对象
unique_ptr<Car> p1(bmwfty->createCar("X6"));
unique_ptr<Car> p2(audifty->createCar("A8"));
p1->show();
p2->show();
return 0;
}

一个抽象工厂派生类就代表一个具体工厂,一个具体工厂有一个工厂方法接口,调用会产生一种具体的产品实例

(2)优缺点

优点:

遵循开闭原则:可以通过引入新产品类和具体工厂类扩展产品,客户端无需修改代码

符合单一职责原则:每个具体工厂类负责创建具体产品的实例

减少耦合:客户端通过抽象工厂接口操作产品,避免了直接依赖具体产品

缺点:

增加代码复杂度:引入多个工厂类和产品类,增加了系统的复杂性

增加类数量:每增加一个产品,需要对应增加一个具体工厂类

使用场景

需要创建的对象具有复杂结构或过程:工厂方法隐藏了对象创建的复杂性

系统需要独立于对象创建和使用:使得系统的对象创建和使用分离

产品类经常变化:系统可以在不修改客户端代码的情况下引入新产品

重点实际上,很多产品是有关联的,属于一个产品族,不应该放在不同的具体工厂里面去创建,这样不符合实际产品对象创建逻辑,二是工厂类太多,不好维护,因此引出,抽象工厂


3.抽象工厂模式

将一系列有关联关系的,属于一个产品族的所有产品创建的接口函数,放在一个抽象工厂里面,AbstractFactory,该抽象工厂的派生类就是这一个产品族的具体工厂,负责创建该产品族里面的所有产品

抽象工厂模式是一种创建型设计模式提供一个接口,用于创建一系列相关或互相依赖的对象,而无需指定它们的具体类。它通过组合多个工厂方法,实现对产品族的创建

主要角色

抽象工厂(Abstract Factory):声明一组用于创建各种产品的方法(和工厂方法模式存在区别

具体工厂(Concrete Factory):实现创建产品的方法,返回具体产品实例

抽象产品(Abstract Product):为每种产品声明接口

具体产品(Concrete Product):实现产品接口

(1)抽象工厂代码示例
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
/**********************系列产品1************************/
class Car{
public:
Car(string name):_name(name){}
// 纯虚方法
virtual void show() = 0;

protected:
string _name;
};

// 系列产品1的--具体产品类Bwm
class Bmw:public Car{
public:
// 使用成员列表初始化的方式,初始化基类的成员变量,通过基类构造函数,创建基类对象
Bmw(string name): Car(name){}
// 重写继承自基类的纯虚函数
void show()
{
cout << "获取了一辆宝马汽车" << _name << endl;
}
};

// 系列产品1的--具体产品类Audi
class Audi:public Car{
public:
Audi(string name): Car(name){}
void show()
{
cout << "获取了一辆奥迪汽车" <<_name << endl;
}
};
/**********************系列产品1************************/


/**********************系列产品2************************/
class Light{
public:
Light(){}
virtual void show() = 0;
};

// 系列产品2--具体产品类BmwLight
class BmwLight:public Light{
public:
BmwLight(){}
// override 关键词 表示重写了基类中的同名虚函数
void show() override
{
cout << "BMW Light!" << endl;
}
};

// 系列产品2--具体产品类AudiLight
class AudiLight:public Light{
public:
AudiLight(){}
// override 关键词 表示重写了基类中的同名虚函数
void show() override
{
cout << "AUDI Light!" << endl;
}
};
/**********************系列产品2************************/


/**********************抽象工厂************************/
// 声明一组用于一系列有关联关系产品(产品族)的方法
class AbstractFactory{
public:
virtual Car* createCar(string name) = 0; // 工厂方法,创建汽车
virtual Light* createCarLight() = 0; // 工厂方法,创建与汽车有关的产品,车灯
};
/**********************抽象工厂************************/


/*******************具体(宝马)工厂*********************/
// 宝马的具体工厂,生产与宝马有关的一系列产品
class BMWFactory:public AbstractFactory{
public:
// 获取宝马车实例对象的接口
Car* createCar(string name) override
{
return new Bmw(name);
}
// 获取宝马车灯实例对象的接口
Light *createCarLight() override
{
return new BmwLight();
}
};
/*******************具体(宝马)工厂*********************/


/*******************具体(奥迪)工厂*********************/
// 宝马的具体工厂,生产与宝马有关的一系列产品
class AUDIFactory:public AbstractFactory{
public:
// 获取宝马车实例对象的接口
Car* createCar(string name) override
{
return new Audi(name);
}
// 获取宝马车灯实例对象的接口
Light *createCarLight() override
{
return new AudiLight();
}
};
/*******************具体(奥迪)工厂*********************/

int main()
{
// 宝马具体工厂对象
unique_ptr<AbstractFactory> bmwfty(new BMWFactory());
// 奥迪具体工厂对象
unique_ptr<AbstractFactory> audifty(new AUDIFactory());
// 从宝马工厂获取宝马车实例对象
unique_ptr<Car> p1(bmwfty->createCar("X6"));
// 从奥迪工厂获取奥迪车实例对象
unique_ptr<Car> p2(audifty->createCar("A8"));
// 从宝马工厂获取宝马车灯实例对象
unique_ptr<Light> L1(bmwfty->createCarLight());
// 从奥迪工厂获取奥迪车灯实例对象
unique_ptr<Light> L2(audifty->createCarLight());

p1->show();
L1->show();

p2->show();
L2->show();

return 0;
}
(2)区别

工厂方法模式与抽象工厂模式的区别:

  • 工厂方法模式的抽象工厂基类中仅仅声明了一种工厂方法

  • 抽象工厂模式的抽象工厂基类中声明了一组一系列由关联关系产品的工厂方法,对一组有关联关系的产品(产品族)提供了产品对象的统一创建。在一个具体工厂中创建一组有关联关系的产品,而非工厂方法模式中一个具体工厂仅创建一种产品

(3)优缺点

优点:

分离接口和实现:客户端通过抽象工厂和抽象产品接口操作,不依赖于具体产品类

产品族一致性:确保同一工厂生产的产品是相互兼容的

遵循开闭原则:添加新的产品族时,只需新增具体工厂和产品类,无需修改现有代码

缺点:

增加复杂性:系统中涉及多个接口和类,增加了代码复杂度

扩展不便:增加新的产品类型时,需要修改抽象工厂接口及所有具体工厂类

(4)适用场景

产品对象有复杂的依赖关系:需要确保一系列对象一起使用

需要创建一系列相关或依赖的对象:例如跨平台界面库,需在不同平台上创建不同的界面控件

系统不应依赖于产品类实例如何被创建、组合和表达:通过抽象工厂模式隔离具体类