设计模式
2023-12-18 21:57:21 # 技术 # CS基础

一、设计模式概述

1、设计模式的定义

设计模式是在特定环境下人们解决某类重复出现问题的一套成功或有效的解决方案。
用大白话解释就是:「在一定的环境下,用固定套路解决问题。

2、设计模式的分类

GoF提出的设计模式有23个,包括

  • 创建型模式:如何创建对象
  • 结构型模式:如何实现类或对象的组合
  • 行为型模式:类或对象怎样交互以及怎样分配职责
    有一个 「简单工厂模式」,不属于 GoF 23 种设计模式,但大部分设计模式书籍都会对它进行介绍

    1. 创建型模式

模式名称 作用
单例模式 保证一个类仅有一个实例,并提供一个访问它的全局访问点
简单工厂模式 通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类
工厂方法模式 定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类中
抽象工厂模式 提供一个创建一系列相关或者相互依赖的接口,而无需指定它们具体的类
原型模式 用原型实例指定创建对象的种类,并且通过拷贝这样的原型创建新的对象
建造者模式 将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示

2. 结构型模式

模式名称 作用
适配器模式 将一个类的接口转换成希望的另一个接口。使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
桥接模式 将抽象部分与实际部分分离,使它们都可以独立的变化
组合模式 将对象组合成树形结构以表示“部分-整体”的层次结构。是的用户对单个对象和组合对象的使用具有一致性
装饰模式 动态地给一个对象添加一些额外的职责。就增加功能来说,此模式比生成子类更为灵活
外观模式 为子系统种的一组接口提供一个一致的界面,此模式定义了一个高层接口,这个接口使得这一子系统更加容易使用
享元模式 以共享的方式高效地支持大量地细粒度地对象
代理模式 为其他对象提供一种代理以控制对这个对象的访问

3. 行为型设计模式

模式名称 作用
职责链模式 在该模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配责任
命令模式 将一个请求封装为一个对象,从而使你可用不同的请求对客户端进行参数化;对请求排队或记录请求日志,以及支持可撤销的操作
解释器模式 如何为简单的语言定义一个语法,如何在该语言中表示一个句子,以及如何解释这些句子
迭代器模式 提供一种方法顺序来访问一个聚合对象中的各个元素,而又需要暴露该对象的内部表示
中介者模式 定义一个中介对象来封装系列对象之间的交互。中介者使各个对象不需要显式地相互调用,从而使其耦合性松散,而且可以独立的改变他们之间的交互
备忘录模式 是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态
观察者模式 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新
状态模式 对象的行为,依赖于他所处的状态
策略模式 准备一组算法,并将每一个算法封装起来,使得他们可以互换
模板方法模式 得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤
访问者模式 表示一个作用于某对象结构中的个元素的操作,它使你可以在不该百年各元素怒的类的前提下定义作用域这些元素的新操作

二、面向对象设计原则

面向对象设计原则为支持可维护性而诞生,这些原则的目的是高内聚、低耦合

1. 面向对象设计原则表

名称 定义
单一职责原则(SRP) 类的指责单一,对外只提供一种功能,而引起类变化的原因都应该只有一个
开闭原则(OCP) 类的改动是通过增加代码进行的,而不是修改源代码
里氏代换原则(LSP) 任何抽象类出现的地方都可以用它的实现类进行替换,实际就是虚拟机制,语言级别实现面向对象功能(多态)
依赖倒转原则(DIP) 依赖于抽象(接口),不要依赖具体的实现(类),也就是针对接口编程
接口隔离原则(ISP) 不应该强迫用户的程序依赖它们不需要的接口方法。一个接口应该只提供一种对外功能,不应该把所有的操作都封装到一个接口上去
合成复用原则(CRP) 如果使用继承,会导致父类的任何变换都可能影响到子类的行为。如果使用对象组合,就降低了这种依赖关系。对于继承和组合,优先使用组合
迪米特原则(LoD) 一个对象应当对其他对象尽可能少的了解,从而降低各个对象之间的耦合,提高系统的可维护性。例如在一个程序中,各个模块之间互相调用时,通常会提供一个统一的接口来实现。这样其他模块不需要了解另一个模块的内部实现细节,这样当一个模块内部的实现发生改变时,不会影响其他模块的使用。(黑盒原理)

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
/* 定义一个计算器类 */
class Calculator
{
private:
    int m_a;
    int m_b;
    string m_operator;
    int m_ret;
public:
    Calculator(int a, int b, string moperator) : m_a(a), m_b(b), m_operator(moperator)
    {
    }
    int getResult()
    {
        if (m_operator == "+")
        {
            return m_a + m_b;
        }
        else if (m_operator == "-")
        {
            return m_a - m_b;
        }
        else if (m_operator == "*")
        {
            return m_a * m_b;
        }
        else if (m_operator == "/")
        {
            return m_a / m_b;
        }
    }
};

缺点
​ 如果增加取模运算需要修改getResult成员方法,如果增加新功能的情况下要修改调代码,那么就会有修改出错的可能性。我们应该在增加新的功能时候,不能影响其他已经完成的功能。这就是对修改关闭,对扩展开放,叫做开闭原则。

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
//写一个抽象类
class AbstractCaculator{
public:
virtual int getResult() = 0;//纯虚函数
virtual void setOperatorNumber(int a, int b)=0;
};

//加法计算器类
class PlusCaculator :public AbstractCaculator{
public:
int mA;
int mB;
public:
virtual void setOperatorNumber(int a, int b){
this->mA = a;
this->mB = b;
}
virtual int getResult(){
return mA + mB;
}
};

//减法计算器类
class MinuteCaculator :public AbstractCaculator{
public:
int mA;
int mB;
public:
virtual void setOperatorNumber(int a, int b){
this->mA = a;
this->mB = b;
}
virtual int getResult(){
return mA - mB;
}

};

//乘法计算器类
class MultiplyCaculator :public AbstractCaculator{
public:
int mA;
int mB;
public:
virtual void setOperatorNumber(int a, int b){
this->mA = a;
this->mB = b;
}
virtual int getResult(){
return mA * mB;
}

};

//取模计算器类,通过增加代码,而不是修改原来的
class QumoCaculator :public AbstractCaculator{
public:
int mA;
int mB;
public:
virtual void setOperatorNumber(int a, int b){
this->mA = a;
this->mB = b;
}
virtual int getResult(){
return mA % mB;
}

};

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
// 楼盘抽象类
class AbstractBuilding
{
public:
virtual void sale() = 0;
};

// 楼盘A
class BuildingA : public AbstractBuilding
{
public:
BuildingA()
{
m_qulity = "high";
}

virtual void sale()
{
cout << "building A " << m_qulity << " qulity saled!" << endl;
}

public:
string m_qulity;
};

// 楼盘B
class BuildingB : public AbstractBuilding
{
public:
BuildingB()
{
m_qulity = "low";
}

virtual void sale()
{
cout << "building B " << m_qulity << " qulity saled!" << endl;
}

public:
string m_qulity;
};

void test()
{
BuildingA *ba = new BuildingA;
if (ba->m_qulity == "low")
{
ba->sale();
}
BuildingB *bb = new BuildingB;
if (bb->m_qulity == "low")
{
bb->sale();
}
}

int main()
{
test();
return 0;
}

// 输出:building B low qulity saled!

这里面,需要跟具体的楼盘打交道,也就是说两类楼盘的细节都必须暴露给用户,不符合迪米特法则,此时可以构建一个中介类,用户只需要告诉中介,我要买一套高品质楼盘,不需要知道楼盘的具体操作就能实现,透露给用户尽可能少的知识。这个中介类是用来维护和管理这些类的,用户不需要跟具体类打交道。

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
// 楼盘抽象类
class AbstractBuilding
{
public:
virtual void sale() = 0;
virtual string getQulity() = 0;
};

// 楼盘A
class BuildingA : public AbstractBuilding
{
public:
BuildingA()
{
m_qulity = "high";
}

virtual void sale()
{
cout << "building A " << m_qulity << " qulity saled!" << endl;
}

virtual string getQulity()
{
return m_qulity;
}

public:
string m_qulity;
};

// 楼盘B
class BuildingB : public AbstractBuilding
{
public:
BuildingB()
{
m_qulity = "low";
}

virtual void sale()
{
cout << "building B " << m_qulity << " qulity saled!" << endl;
}

virtual string getQulity()
{
return m_qulity;
}

public:
string m_qulity;
};
// 中介类
class Mediator
{
public:
Mediator()
{
AbstractBuilding *building = new BuildingA;
Buildings.push_back(building);
building = new BuildingB;
Buildings.push_back(building);
}
~Mediator()
{
vector<AbstractBuilding *>::iterator it;
for (it = Buildings.begin(); it != Buildings.end(); it++)
{
if ((*it) != nullptr)
{
delete *it;
}
}
}
// 对外提供接口
AbstractBuilding *findMyBuilding(string qulity)
{
vector<AbstractBuilding *>::iterator it;
for (it = Buildings.begin(); it != Buildings.end(); it++)
{
if ((*it)->getQulity() == qulity)
{
(*it)->sale();
return *it;
}
}
return nullptr;
}

public:
vector<AbstractBuilding *> Buildings;
};

void test()
{
Mediator *mediator = new Mediator;
AbstractBuilding *building = mediator->findMyBuilding("high");
if (building == nullptr)
{
cout << "no building you want!" << endl;
}
}

int main()
{
test();
return 0;
}

这里用户只需要跟中介打交道就行了,不需要跟具体的类进行交互。

3. 合成复用原则

如果使用继承,会导致父类的任何变换都可能影响到子类的行为。如果使用对象组合,就降低了这种依赖关系。继承和组合优先使用组合。

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
#include <bits/stdc++.h>
using namespace std;

// 抽象基类 Car
class AbstractCar
{
public:
virtual void run() = 0;
};
// 宝马车
class BMW : public AbstractCar
{
public:
virtual void run()
{
cout << "BMW start..." << endl;
}
};
// 拖拉机
class tractor : public AbstractCar
{
public:
virtual void run()
{
cout << "tractor start..." << endl;
}
};
// 针对具体类
// 开拖拉机
class Person1 : public tractor
{
public:
void drive()
{
run();
}
};
// 开宝马车
class Person2 : public BMW
{
public:
void drive()
{
run();
}
};

int main()
{
return 0;
}

上述案例,会针对具体的类(宝马车还是拖拉机)来具体设计person,使用继承关系,可以使用组合来解决

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
// 使用组合,不针对具体类设计person
class Person
{
public:
~Person()
{
delete this->car;
}
void setCar(AbstractCar *car)
{
this->car = car;
}
void drive()
{
this->car->run();
}

public:
AbstractCar *car;
};

void test()
{
Person *p = new Person;
p->setCar(new BMW);
p->drive();

p->setCar(new tractor);
p->drive();
}

in main()
{
test();
return 0;
}

4. 依赖倒转原则

依赖于抽象(接口),不要依赖具体的实现(类),也就是针对接口编程

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
#include <bits/stdc++.h>
using namespace std;

class BankWorker
{
public:
void saveService()
{
cout << "办理存款业务。。。" << endl;
}
void payService()
{
cout << "办理支付业务。。。" << endl;
}
void transferService()
{
cout << "办理转账业务。。。" << endl;
}
};

// 中间模块
void doSaveBuiness(BankWorker *worker)
{
worker->saveService();
}
void doPayBuiness(BankWorker *worker)
{
worker->payService();
}
void doTransferBuiness(BankWorker *worker)
{
worker->transferService();
}

void test()
{
BankWorker *worker = new BankWorker;
doSaveBuiness(worker);
doPayBuiness(worker);
doTransferBuiness(worker);
}

int main()
{
test();
return 0;
}

上面的中间模块有三种,因为其依赖于具体的类实现,且一个类对外提供了很多功能,包括存款、付款和转账,不满足单一职责原则,可以让中间模块仅依赖于抽象类,使中间模块具有可扩展性。

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
#include <bits/stdc++.h>
using namespace std;

// 银行工作人员
class AbstractWorker
{
public:
virtual void doBusiness() = 0;
};

// 专门负责办理存款业务的工作人员
class SaveBankWorker : public AbstractWorker
{
public:
virtual void doBusiness()
{
cout << "办理存款业务。。。" << endl;
}
};

// 专门负责办理支付业务的工作人员
class PayBankWorker : public AbstractWorker
{
public:
virtual void doBusiness()
{
cout << "办理支付业务。。。" << endl;
}
};

// 专门负责办理转账业务的工作人员
class TransferBankWorker : public AbstractWorker
{
public:
virtual void doBusiness()
{
cout << "办理转账业务。。。" << endl;
}
};

// 中层业务,仅依赖于抽象层
void doNewBusiness(AbstractWorker *worker)
{
worker->doBusiness();
}

void test()
{
AbstractWorker *worker = new TransferBankWorker;
doNewBusiness(worker);
}

int main()
{
test();
return 0;
}

上面,中间模块仅依赖于抽象的银行工作人员类,并不依赖于具体的办理业务的银行工作人员类

三、创建型模型

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
#include <bits/stdc++.h>
using namespace std;

class Fruit
{
public:
Fruit(string name)
{
m_name = name;
}

void showFruitName()
{
if (m_name == "apple")
{
cout << "I'm apple!" << endl;
}
else if (m_name == "banana")
{
cout << "I'm banana!" << endl;
}
else if (m_name == "pear")
{
cout << "I'm pear!" << endl;
}
}

private:
string m_name;
};

int main()
{
Fruit *apple = new Fruit("apple");
apple->showFruitName();
Fruit *banana = new Fruit("banana");
banana->showFruitName();
Fruit *pear = new Fruit("pear");
pear->showFruitName();
return 0;
}

不难看出,Fruit 类是一个“巨大的”类,在该类的设计中存在如下几个问题:

  1. Fruit 类包含很多 if ... else ... 代码块,整个类的代码相当冗长,代码越长,阅读难度、维护难度和测试难度也越大;而且大量条件语句的存在还将影响系统的性能,程序在执行过程中需要做大量的条件判断。
  2. Fruit 类的职责过重,它负责初始化和显示所有的水果对象,将各种水果对象的初始化代码集中在一个类中实现,违反了 “ 单一职责原则 ” ,不利于类的重用和维护。
  3. 当需要增加新类型的水果时,必须修改Fruit 类的源代码,违反了 “ 开闭原则 ”
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
#include <bits/stdc++.h>
using namespace std;

// 抽象水果类
class AbstractFruit
{
public:
virtual void showName() = 0;
};
// 苹果
class Apple : public AbstractFruit
{
public:
virtual void showName()
{
cout << "I'm apple!" << endl;
}
};
// 香蕉
class Banana : public AbstractFruit
{
public:
virtual void showName()
{
cout << "I'm banana!" << endl;
}
};
// 梨
class Pear : public AbstractFruit
{
public:
virtual void showName()
{
cout << "I'm pear!" << endl;
}
};

// 水果工厂
class FruitFactory
{
public:
static AbstractFruit *createFruit(string fruit)
{
if (fruit == "apple")
{
return new Apple;
}
else if (fruit == "banana")
{
return new Banana;
}
else if (fruit == "pear")
{
return new Pear;
}
else
{
return NULL;
}
}
};

void test()
{
FruitFactory *factory = new FruitFactory;

AbstractFruit *fruit = factory->createFruit("apple");
fruit->showName();
delete fruit;

fruit = factory->createFruit("banana");
fruit->showName();
delete fruit;

fruit = factory->createFruit("pear");
fruit->showName();
delete fruit;
}

int main()
{
test();
return 0;
}

适用场景:

  • 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂
  • 客户端只知道传入工厂类的参数,对于如何创建对象并不关心

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
#include <bits/stdc++.h>
using namespace std;

// 抽象水果类
class AbstractFruit
{
public:
virtual void showName() = 0;
};
// 苹果
class Apple : public AbstractFruit
{
public:
virtual void showName()
{
cout << "I'm apple!" << endl;
}
};
// 香蕉
class Banana : public AbstractFruit
{
public:
virtual void showName()
{
cout << "I'm banana!" << endl;
}
};
// 梨
class Pear : public AbstractFruit
{
public:
virtual void showName()
{
cout << "I'm pear!" << endl;
}
};

// 抽象工厂
class AbstractFruitFactory
{
public:
virtual AbstractFruit *createFruit() = 0;
};

// 苹果工厂
class AppleFactory : public AbstractFruitFactory
{
public:
virtual AbstractFruit *createFruit()
{
return new Apple;
}
};

// 香蕉工厂
class BananaFactory : public AbstractFruitFactory
{
public:
virtual AbstractFruit *createFruit()
{
return new Banana;
}
};

// 梨工厂
class PearFactory : public AbstractFruitFactory
{
public:
virtual AbstractFruit *createFruit()
{
return new Pear;
}
};

void test()
{
AbstractFruitFactory *factory;
AbstractFruit *fruit;
// 创建苹果
factory = new AppleFactory;
fruit = factory->createFruit();
fruit->showName();
delete fruit;
delete factory;
// 创建香蕉
factory = new BananaFactory;
fruit = factory->createFruit();
fruit->showName();
delete fruit;
delete factory;
// 创建梨
factory = new PearFactory;
fruit = factory->createFruit();
fruit->showName();
delete fruit;
delete factory;
}

int main()
{
test();
return 0;
}

适用场景:

  • 客户端不知道它所需要的对象的类
  • 抽象工厂类通过其子类来指定哪个对象

3、抽象工厂模式

工厂方法模式通过引入工厂等级结构,解决了简单工厂模式中厂类职责太重的问题,但由于工厂方法模式中的每个工厂只生产一类产品,可能会导致系统中存在大量的工厂类,势必会增加系统的开销。此时,我们可以考虑将一些相关的产品组成一个 “ 产品族 ”,由同一个工厂来统一生产,这就是我们本文将要学习的抽象工厂模式的基本思想。