前言

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

(1)代码更易于维护,代码的可读性、复用性、可移植性、健壮性更好。

(2)当软件原有需求有变更或者增加新的需求时,合理的设计模式的应用,能够做到软件设计要求的"开-闭原则",即对修改关闭,对扩展开放,使软件原有功能修改,新功能扩充非常灵活。

(3)合理的设计模式的选择,会使软件设计更加模块化,积极的做到软件设计遵循的“高内聚,低耦合”这一根本原则。

单例模式

单例模式顾名思义,保证一个类仅可以有一个实例化对象,并且提供一个可以访问它的全局接口。这是一个创建性模式(主要是指对象的创建方式)。单例模式和多线程结合到是很紧密的。包括两种单例模式,以及线程安全的问题

应用

第一种:日志模块

假如 这里有一个类,提供了很多方法来封装了一个日志模块。一个软件本身可能有很多的功能模块,那么这么多的模块都要通过日志模块进行写入一些软件运行的日志信息到磁盘内。应该是把这些所有的日志信息都给到一个日志模块的对象上。

第二种:数据库模块

作为一个个的数据库客户端,是要通过数据库模块与数据库服务器进行交互通信的。那么这个数据库client就可以设计成一个单例:应用软件的其他功能模块如果要与数据库服务器进行交互通信,可以调用一个数据库模块对象的某一个方法即可,不需要去创建那么多的对象。毕竟数据库的请求那么多,不可能做到 每一次处理请求都生成一个数据库对象,这样会导致数据库模块对象特别特别多和数据库的连接也特别的多,占用大量内存且非常麻烦。

实现单例模式必须注意一下几点:

(1)单例类只能有一个实例化对象。

(2)单例类必须自己提供一个实例化对象。

(3)单例类必须提供一个可以访问唯一实例化对象的接口。

(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
#include <stdio.h>
#include <iostream>

using namespace std;

class Singleton
{
public:
static Singleton *getInstance()
{
return &instance;
}
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;

private:
static Singleton instance;
Singleton() {}
};
Singleton Singleton::instance;
int main()
{
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
cout << p1 << " " << p2 << endl;
// Singleton p = *p1;
// cout << &p << endl;
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
#include <stdio.h>
#include <iostream>

using namespace std;

class Singleton
{
public:
static Singleton *getInstance()
{
return &instance;
}
//Singleton(const Singleton &) = delete;
//Singleton &operator=(const Singleton &) = delete;

private:
static Singleton instance;
Singleton() {}
};
Singleton Singleton::instance;

int main()
{
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
cout << p1 << " " << p2 << endl;

// 允许拷贝构造,会产生一个新的 Singleton 实例
Singleton p = *p1;
cout << &p << endl;

return 0;
}

懒汉式: 对象的创建在第一次调用getInstance函数时创建【线程不安全】

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
#include <stdio.h>
#include <iostream>

using namespace std;

class Singleton
{
public:
static Singleton *getInstance()
{
if (instance == nullptr)
{
instance = new Singleton();
}
return instance;
}
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;

private:
static Singleton *instance;
Singleton() {}
};
Singleton *Singleton::instance = nullptr;
int main()
{
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
cout << p1 << " " << p2 << endl;
// Singleton p = *p1;
// cout << &p << endl;
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
#include <stdio.h>
#include <iostream>
#include <mutex>
using namespace std;

class Singleton
{
public:
static Singleton *getInstance()
{
if (instance == nullptr)
{
// 锁加双重判断
unique_lock<mutex>(mtx);
if (instance == nullptr)
{
instance = new Singleton();
}
}
return instance;
}
Singleton(const Singleton &) = delete;
Singleton &operator=(const Singleton &) = delete;

private:
static Singleton *instance;
mutex mtx;
Singleton() {}
};
Singleton *Singleton::instance = nullptr;
int main()
{
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
cout << p1 << " " << p2 << endl;
// Singleton p = *p1;
// cout << &p << endl;
return 0;
}

单例模式缺点

  1. 全局状态
    • 缺点:单例模式会在应用程序中引入全局状态,这可能导致代码的耦合度增加,使得代码更难以测试和维护。
    • 举例:假设你有一个 Logger 单例类,它在整个应用程序中记录日志。如果其他类直接依赖于这个单例类,那么在单元测试中模拟或隔离这个依赖就会变得困难。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Logger {
private:
Logger() {}
static Logger* instance;
public:
static Logger* getInstance() {
if (instance == nullptr) {
instance = new Logger();
}
return instance;
}
void log(const std::string& message) {
std::cout << message << std::endl;
}
};

Logger* Logger::instance = nullptr;

void someFunction() {
Logger::getInstance()->log("Logging from someFunction");
}

在这个例子中,Logger 类是一个单例类。someFunction 函数直接依赖于 Logger 单例类,这使得在单元测试中难以隔离 Logger 类。

  1. 难以扩展
    • 缺点:由于单例模式限制了类的实例数量,因此很难扩展或修改类的行为。
    • 举例:假设你有一个 Configuration 单例类,它存储应用程序的配置信息。如果将来需要支持多个配置实例,那么修改代码将会非常困难。
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
class Configuration {
private:
Configuration() {}
static Configuration* instance;
public:
static Configuration* getInstance() {
if (instance == nullptr) {
instance = new Configuration();
}
return instance;
}
void setConfig(const std::string& key, const std::string& value) {
// 设置配置
}
std::string getConfig(const std::string& key) {
// 获取配置
return "some_config_value";
}
};

Configuration* Configuration::instance = nullptr;

void someFunction() {
Configuration::getInstance()->setConfig("key", "value");
std::string value = Configuration::getInstance()->getConfig("key");
std::cout << value << std::endl;
}

在这个例子中,Configuration 类是一个单例类。如果将来需要支持多个配置实例,那么修改代码将会非常困难。

  1. 线程安全问题
    • 缺点:在多线程环境中,单例模式的实现可能会导致线程安全问题。
    • 举例:在多线程环境中,如果没有适当的同步机制,多个线程可能会同时创建单例类的实例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Singleton {
private:
Singleton() {}
static Singleton* instance;
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};

Singleton* Singleton::instance = nullptr;

在这个例子中,Singleton 类的 getInstance 方法不是线程安全的。在多线程环境中,多个线程可能会同时创建 Singleton 类的实例。

为了解决这个问题,可以使用双重检查锁定模式(Double-Checked Locking Pattern)或其他同步机制来确保线程安全。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <mutex>

class Singleton {
private:
Singleton() {}
static Singleton* instance;
static std::mutex mtx;
public:
static Singleton* getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
};

Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

在这个例子中,使用 std::mutex 来确保 getInstance 方法是线程安全的。

简单工厂和工厂方法

简单工厂:它不属于标准的OOP设计模式中,而后面两种是包含在标准的OOP的23种设计模式中的。

为什么要工厂模式:主要是封装了对象的创建过程。 创建性模式本身就是体现了:对象的创建过程的封装和隐藏。没有工厂模式的封装就是:对象的new 和 new等。当代码里面出现很多的类,每次创建在对象的时候,都需要通过new 类名称的方式来生成对象。

先来看个代码

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
#include <iostream>
#include <string>

class Car
{
public :
//在构造函数的初始化列表里面给name初始化
Car(std::string name):_name(name){}

//提供一个给派生类(具体的汽车)重写的接口
virtual void show() = 0;//纯虚函数
protected:
std::string _name;
};

class BMW:public Car
{
public:
BMW (std::string name):Car(name){}
//重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "宝马 " << _name << std::endl;
}
};
class Audi :public Car
{
public:
Audi(std::string name) :Car(name) {}
//重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "奥迪 " << _name << std::endl;
}
};

int main()
{
Car* p1 = new BMW("x1");
Car* p2 = new Audi("A6");
p1->show();
p2->show();
delete p1;
delete p2;
/*
上面的缺点在于:
1 需要记住派生类的名字,不然没法new对象
2 创建对象的时候,直接使用了对象

其实我们不需要去了解对象创建的具体内容
只要给我汽车就OK了,劳资不需要去知道怎么造车的
*/
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
58
59
60
61
62
63
64
65
66
67
68
69
#include <iostream>
#include <string>

class Car
{
public:
// 在构造函数的初始化列表里面给name初始化
Car(std::string name) : _name(name) {}

// 提供一个给派生类(具体的汽车)重写的接口
virtual void show() = 0; // 纯虚函数
protected:
std::string _name;
};

class Bmw : public Car
{
public:
Bmw(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "宝马 " << _name << std::endl;
}
};
class Audi : public Car
{
public:
Audi(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "奥迪 " << _name << std::endl;
}
};
enum CarType
{
BMW,
AUDI
};
class SimpleFactory
{
public:
Car *createCar(CarType type)
{
switch (type)
{
case BMW:
return new Bmw("x1");
case AUDI:
return new Audi("A6");
default:
break;
}
return nullptr;
}
};
int main()
{
SimpleFactory *factory = new SimpleFactory();
Car *car1 = factory->createCar(BMW);
Car *car2 = factory->createCar(AUDI);
car1->show();
car2->show();
delete factory;
delete car1;
delete car2;
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
#include <stdio.h>
#include <iostream>
#include <mutex>
#include <string>
using namespace std;

class F{
public:
F(string name):name_(name){}
virtual void show() =0;
string name_;
};

class A:public F{
public:
A(string name):F(name){}
void show(){
cout<<"AA:"<<name_;
}
};
class B:public F{
public:
B(string name):F(name){}
void show(){
cout<<"BB:"<<name_;
}
};

enum Type{
AA,BB
};
class SimpleFactory{
public:
F * create(Type type){
switch (type)
{
case AA:
return new A("aaa");
case BB:
return new B("bbb");
default:
break;
}
return nullptr;
}
};
signed main(){
SimpleFactory simpleFactory ;
simpleFactory.create(AA)->show();
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
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class Car
{
public:
// 在构造函数的初始化列表里面给name初始化
Car(std::string name) : _name(name) {}

// 提供一个给派生类(具体的汽车)重写的接口
virtual void show() = 0; // 纯虚函数
protected:
std::string _name;
};

class Bmw : public Car
{
public:
Bmw(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "宝马 " << _name << std::endl;
}
};
class Audi : public Car
{
public:
Audi(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "奥迪 " << _name << std::endl;
}
};
enum CarType
{
BMW,
AUDI
};
class SimpleFactory
{
public:
Car *createCar(CarType type)
{
switch (type)
{
case BMW:
return new Bmw("x1");
case AUDI:
return new Audi("A6");
default:
break;
}
return nullptr;
}
};
int main()
{
std::unique_ptr<SimpleFactory> factory(new SimpleFactory());

// SimpleFactory *factory = new SimpleFactory();
std::unique_ptr<Car> car1(factory->createCar(BMW));
std::unique_ptr<Car> car2(factory->createCar(AUDI));
car1->show();
car2->show();

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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class Car
{
public:
// 在构造函数的初始化列表里面给name初始化
Car(std::string name) : _name(name) {}

// 提供一个给派生类(具体的汽车)重写的接口
virtual void show() = 0; // 纯虚函数
protected:
std::string _name;
};

class Bmw : public Car
{
public:
Bmw(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "宝马 " << _name << std::endl;
}
};
class Audi : public Car
{
public:
Audi(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "奥迪 " << _name << std::endl;
}
};
enum CarType
{
BMW,
AUDI
};
class SimpleFactory
{
public:
virtual Car *createCar(string name) = 0;
};
class BmwFactoryMethod : public SimpleFactory
{

Car *createCar(string name)
{
return new Bmw(name);
}
};
class AUDIFactoryMethod : public SimpleFactory
{

Car *createCar(string name)
{
return new Audi(name);
}
};
int main()
{
std::unique_ptr<SimpleFactory> Afactory(new AUDIFactoryMethod());
std::unique_ptr<SimpleFactory> Bfactory(new BmwFactoryMethod());
// SimpleFactory *factory = new SimpleFactory();
std::unique_ptr<Car> car1(Afactory->createCar("x1"));
std::unique_ptr<Car> car2(Bfactory->createCar("A5"));
car1->show();
car2->show();

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
#include <stdio.h>
#include <iostream>
#include <mutex>
#include <string>
#include <memory>
using namespace std;
class F{
public:
F(string name):name_(name){}
virtual void show() =0;
string name_;
};
class A:public F{
public:
A(string name):F(name){}
void show(){
cout<<"AA:"<<name_;
}
};
class B:public F{
public:
B(string name):F(name){}
void show(){
cout<<"BB:"<<name_;
}
};
class SimpleFactory{
public:
virtual F* create(string name)=0;
};
class AFun:public SimpleFactory{
public:
F* create(string name){
return new A(name);
}
};
signed main(){
unique_ptr<SimpleFactory> nna(new AFun());
unique_ptr<F> aaaa(nna->create("aa"));
// unique_ptr<A> aaaa(new A("aa"));
aaaa->show();
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
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
#include <iostream>
#include <string>
#include <memory>

// 一系列产品1 汽车
class Car
{
public:
// 在构造函数的初始化列表里面给name初始化
Car(std::string name) : _name(name) {}

// 提供一个给派生类(具体的汽车)重写的接口
virtual void show() = 0; // 纯虚函数
protected:
std::string _name;
};
class Bmw : public Car
{
public:
Bmw(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "宝马 " << _name << std::endl;
}
};
class Audi : public Car
{
public:
Audi(std::string name) : Car(name) {}
// 重写从基类基类继承过来的纯虚函数
void show()
{
std::cout << "奥迪 " << _name << std::endl;
}
};
// 一系列产品2 车灯
class CarLight
{
public:
virtual void light() = 0;
};
class BmwLight : public CarLight
{
public:
// 重写从基类基类继承过来的纯虚函数
void light()
{
std::cout << "宝马车灯 " << std::endl;
}
};
class AudiLight : public CarLight
{
public:
// 重写从基类基类继承过来的纯虚函数
void light()
{
std::cout << "奥迪车灯 " << std::endl;
}
};
// 工厂方法 升级为 抽象工厂
// 抽象工厂:对有一组关联关系的产品簇提供产品对象的统一创建
class AbstractFactory
{
public:
// 下面是工厂方法,提供多个产品创建的抽象接口
// 车子产品方法 创建车子
virtual Car *createOneCar(std::string) = 0;
// 车灯产品方法 创建车灯
virtual CarLight *createCarLight() = 0;
};
// 下面是宝马工厂,负责创建宝马系列的产品
class BMWAbstractFactory : public AbstractFactory
{
Car *createOneCar(std::string name) // 创建车子
{
return new Bmw(name);
}
CarLight *createCarLight() // 创建车灯
{
return new BmwLight();
}
};
// 下面是奥迪工厂,负责创建奥迪系列的产品
class AUDIAbstractFactory : public AbstractFactory
{
Car *createOneCar(std::string name) // 创建车子
{
return new Audi(name);
}
CarLight *createCarLight() // 创建车灯
{
return new AudiLight();
}
};

/*
这里相较于简单工厂:使用一个工厂,通过传入不同的标识 来创建不同的对象
相当于给了一个工厂的基类,然后通过实现具体的产品的工厂。用这个具体产品的工厂
来创建具体的产品对象。

*/
int main()
{
std::unique_ptr<AbstractFactory> bmwAbstractFactory(new BMWAbstractFactory());
std::unique_ptr<AbstractFactory> audiAbstractFactory(new AUDIAbstractFactory());
std::unique_ptr<Car> p1(bmwAbstractFactory->createOneCar("x1"));
std::unique_ptr<Car> p2(audiAbstractFactory->createOneCar("A5"));

std::unique_ptr<CarLight> p3(bmwAbstractFactory->createCarLight());
std::unique_ptr<CarLight> p4(audiAbstractFactory->createCarLight());
p1->show();
p2->show();

p3->light();
p4->light();
return 0;
}

代理模式

结构型模式:(不是关注于对象的产生,关注于最后通过类与类的组合之后,功能上该怎么使用?以及对问题场景的符合与否?)

这些设计模式关注于类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。

代理模式:Proxy Pattern 。主要体现的是 对象访问权限的控制。这个Proxy代理可以把那些访问对象的权限不够的用户 都给挡回去。

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
#include <iostream>
#include <string>
#include <memory>

// 代理模式
class VedioWebSite // 抽象类
{
public:
virtual void freeMovie() = 0; // 免费电影
virtual void vipMovie() = 0; // VIP电影
virtual void ticketMovie() = 0; // VIP + 券电影
};

// 实际的 操作视频服务器后台的所有电影
class OperatorMovieBoss : public VedioWebSite // 这个是 委托类
{
public:
virtual void freeMovie() // 免费电影
{
std::cout << "你可以看免费电影" << std::endl;
}
virtual void vipMovie() // VIP电影
{
std::cout << "你可以看VIP电影" << std::endl;
}
virtual void ticketMovie() // VIP + 券电影
{
std::cout << "你可以看VIP + 券电影" << std::endl;
}
};
// 代理OperatorMovieBoss的代理类
class FreeMovieProxy : public VedioWebSite // 免费电影代理类
{
public:
FreeMovieProxy()
{
// 指针指向代理的对象
pvedio = new OperatorMovieBoss();
}
~FreeMovieProxy()
{
delete pvedio;
}
virtual void freeMovie()
{
// 通过代理对象的freeMovie来访问真正委托类对象的freeMovie方法
pvedio->freeMovie();
}
virtual void vipMovie() // VIP电影
{
std::cout << "你只是普通用户,不可以看VIP电影,请升级为会员"
<< std::endl;
}
virtual void ticketMovie() // VIP + 券电影
{
std::cout << "你没有电源券,不可以观看电影,请先升级为会员然后购买影券"
<< std::endl;
}

private:
// 这里需要一个组合的对象
/*
基类指针指向派生类对象,只是这里不直接指向委托类对象
而是直接访问代理对象,用代理对象来控制用户对委托类对象
访问权限的问题。既然也是从抽象类继承来的,3个纯虚函数都要去
重写一下。不然这个代理类也成了抽象类
*/
VedioWebSite *pvedio;
};
// 代理OperatorMovieBoss的代理类
class VIPMovieProxy : public VedioWebSite // VIP电影代理类
{
public:
VIPMovieProxy()
{
// 指针指向代理的对象
pvedio = new OperatorMovieBoss();
}
~VIPMovieProxy()
{
delete pvedio;
}
virtual void freeMovie()
{
// 通过代理对象的freeMovie来访问真正委托类对象的freeMovie方法
pvedio->freeMovie();
}
virtual void vipMovie() // VIP电影
{
pvedio->vipMovie();
}
virtual void ticketMovie() // VIP + 券电影
{
std::cout << "你没有电源券,不可以观看电影,请先购买影券" << std::endl;
}

private:
// 这里需要一个组合的对象
/*
基类指针指向派生类对象,只是这里不直接指向委托类对象
而是直接访问代理对象,用代理对象来控制用户对委托类对象
访问权限的问题。既然也是从抽象类继承来的,3个纯虚函数都要去
重写一下。不然这个代理类也成了抽象类
*/
VedioWebSite *pvedio;
};
void WhoWatchMovie(std::unique_ptr<VedioWebSite> &ptr)
{
ptr->freeMovie();
ptr->vipMovie();
ptr->ticketMovie();
}
int main()
{
// 基类指针 指向 普通用户级别的代理类
std::unique_ptr<VedioWebSite> p1(new FreeMovieProxy());
WhoWatchMovie(p1);
std::cout << "*****************************" << std::endl;
// 基类指针 指向 VIP用户级别的代理类
std::unique_ptr<VedioWebSite> p2(new VIPMovieProxy());
WhoWatchMovie(p2);

return 0;
}

总结:代理模式涉及到了 抽象类(公共类),委托类(需要从抽象类继承而来),代理类(需要从抽象类继承而来),以组合的方式 使用代理对象,在实际的使用中客户直接访问的是代理对象。

装饰器模式

结构型模式的一种:Decorator Pattern

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
#include <iostream>
#include <string>
#include <memory>

using namespace std;
// 装饰器模式
class Car // 抽象基类
{
public:
virtual void show() = 0;
};
// 下面是三个 汽车实体类
class Bmw : public Car
{
public:
void show()
{
cout << "这是宝马汽车的配置为:";
}
};
class Audi : public Car
{
public:
void show()
{
cout << "这是奥迪汽车的配置为:";
}
};
class Bnze : public Car
{
public:
void show()
{
cout << "这是奔驰汽车的配置为:";
}
};
/*进行功能增强了,如下:*/
class CarBaseDecorator : public Car // 装饰器的基类
{
public:
// 装饰器里面,先把基本的功能装饰写好,给谁添加 先不重要

// 装饰器的基类,具体的功能装饰还没有出来呢
virtual void show() = 0;
};

/*
其实对于装饰器基类,如果要有一些装饰器公共的方法的话,
可以在装饰器的基类里面进行实现。
这里各个装饰器类没有一些公有的方法需要在统一地在基类书写的
情况下。

例如这里 就没有公共的方法,这种情况下就可以把装饰器的
基类CarBaseDecorator给省略掉。直接让下面的CarChildDecorator们
直接继承于Car类。

但是我选择不省略
*/

// 功能装饰器1:定速巡航功能
class CarChildDecorator1 : public CarBaseDecorator
{
public:
CarChildDecorator1(Car *p)
{
_ptr = p;
}
void show()
{
_ptr->show();
cout << " 定速巡航功能";
}

private:
Car *_ptr;
};

// 功能装饰器2:自动驾驶功能
class CarChildDecorator2 : public CarBaseDecorator
{
public:
CarChildDecorator2(Car *p)
{
_ptr = p;
}
void show()
{
_ptr->show();
cout << " 自动驾驶功能";
}

private:
Car *_ptr;
};
// 功能装饰器3:车道偏离功能
class CarChildDecorator3 : public CarBaseDecorator
{
public:
CarChildDecorator3(Car *p)
{
_ptr = p;
}
void show()
{
_ptr->show();
cout << " 车道偏离功能";
}

private:
Car *_ptr;
};
int main()
{
Car *p1 = new CarChildDecorator1(new Bmw());
p1 = new CarChildDecorator2(p1);
p1 = new CarChildDecorator3(p1);
p1->show();
cout << endl;
cout << "************************************" << endl;
Car *p2 = new CarChildDecorator2(new Audi());
p2->show();
cout << endl;
cout << "************************************" << endl;
Car *p3 = new CarChildDecorator3(new Bnze());
p3->show();
cout << endl;
return 0;
}

从深入理解对象模型的角度看有点套娃的味了,应用了虚函数指针的运行时多态实现。

上述代码详细解释:

在这个代码中,我们使用了装饰器模式,也称为包装器模式。装饰器模式允许我们在运行时动态地添加行为或状态到现有的对象中,而不需要修改其原始类的源代码。

当我们执行 p1->show(); 时,输出的结果是 “这是宝马汽车的配置为: 定速巡航功能 自动驾驶功能 车道偏离功能”,这是因为我们在创建 p1 时,逐步地向它添加了这些功能:

  1. Car *p1 = new CarChildDecorator1(new Bmw()); 在这一步,我们创建了一个 Bmw 对象,并将其作为参数传递给 CarChildDecorator1 的构造函数。这个操作把 “定速巡航功能” 添加到了 Bmw 对象中。
  2. p1 = new CarChildDecorator2(p1); 在这一步,我们创建了一个 CarChildDecorator2 对象,并将已经包含了 “定速巡航功能” 的 p1 作为参数传递给 CarChildDecorator2 的构造函数。这个操作把 “自动驾驶功能” 添加到了 p1 中。
  3. p1 = new CarChildDecorator3(p1); 在这一步,我们创建了一个 CarChildDecorator3 对象,并将已经包含了 “定速巡航功能” 和 “自动驾驶功能” 的 p1 作为参数传递给 CarChildDecorator3 的构造函数。这个操作把 “车道偏离功能” 添加到了 p1 中。

因此,当我们调用 p1->show(); 时,p1 中的每个装饰器都会调用其内部存储的 Car 对象的 show 方法,并在此基础上添加自己的功能描述,从而得到了 “这是宝马汽车的配置为: 定速巡航功能 自动驾驶功能 车道偏离功能” 这个结果。

这就是装饰器模式的魅力,它允许我们动态地添加和组合功能,而不需要修改原始类的代码

适配器模式

这个适配器有点意思。

在项目中 使用到第三方的插件或者库,但是因为接口不兼容 就需要去添加很多的适配器类。

例如场景:电脑使用一个接口,把桌面演示投影到 投影仪上。

假如:现在有三种类型的接口:VGA HDMI TypeC 。但是目前电脑就用VGA,那么我们就需要用适配器将HDMI转成VGA的格式。把它想成显卡适配器就好理解了。

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
#include <iostream>
#include <string>
#include <memory>

using namespace std;
// 适配器模式:让不兼容的接口可以在一起工作
// 电脑 = 》 投影到 = 》 投影仪上 VGA HDMI TypeC

// VGA接口的电脑,(TV)投影仪也是VGA接口
class VGA // VGA接口 抽象类
{
public:
virtual void play() = 0;
};

// TV01表示支持VGA接口的投影仪
class TV01 : public VGA
{
public:
void play()
{
cout << "通过VGA接口连接投影仪,进行视频播放" << endl;
}
};

// 实现一个电脑类(只支持VGA接口)
class Computer
{
public:
// 由于电脑只支持VGA接口,所以该方法的参数也只能支持VGA接口的指针/引用
void playVideo(VGA *pVGA)
{
pVGA->play();
}
};
/*
方法1:换一个支持HDMI接口的电脑,这个就叫代码重构
方法2:买一个转换头(适配器),能够把VGA信号转成HDMI信号,这个叫添加适配器类
*/

// 进了一批新的投影仪,但是新的投影仪都是只支持HDMI接口
class HDMI
{
public:
virtual void play() = 0;
};
class TV02 : public HDMI
{
public:
void play()
{
cout << "通过HDMI接口连接投影仪,进行视频播放" << endl;
}
};

// 由于电脑(VGA接口)和投影仪(HDMI接口)无法直接相连,所以需要添加适配器类
class VGAToHDMIAdapter : public VGA
{
public:
VGAToHDMIAdapter(HDMI *p) : pHdmi(p) {}
void play() // 该方法相当于就是转换头,做不同接口的信号转换的
{
pHdmi->play();
}

private:
HDMI *pHdmi;
};
int main()
{
Computer computer;
// computer.playVideo(new TV01());
// 不可以直接在里面传入TV02对象的指针,所以需要加上一层转换适配
// 因为playVideo方法接收的还是VGA接口,而适配器也是从VGA 继承而来的
// 适配器类的构造函数接收的是HDMI接口的对象指针 所以这里传入的是newTV02()对象指针
computer.playVideo(new VGAToHDMIAdapter(new TV02()));
return 0;
}

观察者模式

观察者-监听者模式(发布订阅模式),隶属于行为型模式。

行为型模式:主要关注的是对象之间的通信,例如:对象A调用对象B的成员方法等。

观察者-监听者模式:主要处理 对象的一对多的关系,也就是多个对象都依赖于一个对象。当该对象的状态发生改变时,其他对象都可以接受到相应的通知。假如现在一个类表示了一组数据,生成一个对象。通过这同一个数据对象, 可以得到一组 圆饼图、柱状图、条形图等。

简单解释:

这段代码实现的是发布-订阅模式,也称为观察者模式。这是一种在对象之间定义一对多的依赖关系,当一个对象状态改变时,所有依赖于它的对象都会受到通知并被自动更新。

在这个代码中,Observer 是观察者的抽象基类,Observer1Observer2Observer3 都是具体的观察者,他们分别对1、2、3号的消息感兴趣。Subject 是主题类,用来存储每个观察者对哪个消息感兴趣。

当一个消息发生改变时,主题类 Subject 会通过 dispatch 方法找到对这个消息感兴趣的所有观察者,然后调用它们的 upData 方法,通知它们消息已经发生改变。这就是观察者模式的核心。

例如,如果1号消息发生改变,主题类就会找到对1号消息感兴趣的 Observer1Observer2,然后通知他们。如果2号消息发生改变,主题类就会找到对2号消息感兴趣的 Observer1Observer3,然后通知他们。如果3号消息发生改变,主题类就会找到对3号消息感兴趣的 Observer2Observer3,然后通知他们。

这种模式在很多实际应用中都非常有用,比如在GUI编程中,一个按钮的点击事件就可以看作是一个主题,而对这个点击事件感兴趣的所有监听器就是观察者。当用户点击按钮时,所有的监听器都会收到通知。

代码

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
135
136
137
138
139
140
141
142
143
#include <iostream>
#include <string>
#include <unordered_map>
#include <list>
using namespace std;
// 发布订阅模式
// 观察者接收到消息之后,就要去处理消息,更新图像
class Observer // 观察者抽象类
{
public:
// 统一的处理事件,messageid表示消息 回调函数
virtual void upData(int messageid) = 0;
};

// 以下全部是观察者实例
class Observer1 : public Observer
{
public:
void upData(int id)
{
switch (id)
{
case 1:
cout << "观察者1 对1 和 2号消息感兴趣.1号消息发生改变" << endl;
break;
case 2:
cout << "观察者1 对1 和 2号消息感兴趣.2号消息发生改变" << endl;
break;
}
}
};
class Observer2 : public Observer
{
public:
void upData(int id)
{
switch (id)
{
case 1:
cout << "观察者2 对1 和 3号消息感兴趣.1号消息发生改变" << endl;
break;
case 3:
cout << "观察者2 对1 和 3号消息感兴趣.3号消息发生改变" << endl;
break;
}
}
};
class Observer3 : public Observer
{
public:
void upData(int id)
{
switch (id)
{
case 2:
cout << "观察者3 对2 和 3号消息感兴趣.2号消息发生改变" << endl;
break;
case 3:
cout << "观察者3 对2 和 3号消息感兴趣.3号消息发生改变" << endl;
break;
}
}
};

// 主题类
// 需要存储一下 每个Observer对哪个id感兴趣
class Subject
{
public:
// 向list里面添加一个Observer 还有其感兴趣消息id
// 给主题添加观察者对象
void addObserver(Observer *obser, int messageid)
{
// mapping[messageid].push_back(obser);
/*
上面的这一句话
map的【】运算符重载函数
messageid存在 则返回键对应值的引用,插入到list。
键不存在的话,则默认插入一对。(新增加一个messageid,其值为
默认构造的list,把obser添加进去)
*/
unordered_map<int, list<Observer *>>::iterator it = mapping.find(messageid);
if (it != mapping.end())
{
it->second.push_back(obser);
}
else
{
list<Observer *> mylist;
mylist.push_back(obser);
mapping.insert({messageid, mylist});
}
}

// 主题发生改变,给相应的观察者进行广播
void dispatch(int messageid)
{
// 看看 谁对这个消息主题感兴趣
auto it = mapping.find(messageid);
if (it != mapping.end())
{
// 有人对这个消息感兴趣,进行查看list 分发
for (Observer *pObserver : it->second)
{
pObserver->upData(messageid); // 最核心的代码
}
}
}

private:
// 消息id 具体感兴趣的Observer
// 但是对于一个id的消息,可能有多个Observer
// 所以 很多个观察者串成一个列表
unordered_map<int, list<Observer *>> mapping;
};
int main()
{
Subject subject; // 消息主题对象

// 观察者1 对1 和 2号消息感兴趣
subject.addObserver(new Observer1(), 1);
subject.addObserver(new Observer1(), 2);

// 观察者2 对1 和 3号消息感兴趣
subject.addObserver(new Observer2(), 1);
subject.addObserver(new Observer2(), 3);

// 观察者3 对2 和 3号消息感兴趣
subject.addObserver(new Observer3(), 2);
subject.addObserver(new Observer3(), 3);
cout << "************************************" << endl;

int changeMessid;
cout << "请输入一个发生改变的消息 输入-1结束" << endl;
cout << "哪个消息改变了:";
while (cin >> changeMessid && changeMessid != -1)
{
subject.dispatch(changeMessid);
cout << "哪个消息改变了:";
}
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
58
59
60
61
62
63
64
65
66
67
68
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

class Observer
{
public:
virtual void update(const std::string& message) = 0;
virtual ~Observer() {}
};

class StockTicker
{
std::vector<Observer*> observers;
public:
void attach(Observer* obs)
{
observers.push_back(obs);
}

void detach(Observer* obs)
{
observers.erase(std::remove(observers.begin(), observers.end(), obs), observers.end());
}

void notify(const std::string& message)
{
for (Observer* obs : observers)
{
obs->update(message);
}
}

void newPriceUpdate(const std::string& stock, double price)
{
notify("Price updated - " + stock + ": $" + std::to_string(price));
}
};
class Investor : public Observer
{
std::string name;
public:
Investor(const std::string& name) : name(name) {}

void update(const std::string& message) override
{
std::cout << name << " received: " << message << std::endl;
}
};

int main() {
StockTicker ticker;

Investor alice("Alice");
Investor bob("Bob");

ticker.attach(&alice);
ticker.attach(&bob);

ticker.newPriceUpdate("AAPL", 150.50);
ticker.newPriceUpdate("GOOGL", 2729.34);

ticker.detach(&alice);
ticker.newPriceUpdate("AAPL", 151.45);

return 0;
}

学习自: