12.C++ 设计模式

本节分为六部分:

  1. 单例模式
  2. 工厂模式
  3. 代理模式
  4. 装饰器模式
  5. 适配器模式
  6. 观察者模式

1. 单例模式

数学与逻辑学 中,singleton 定义为:有且仅有一个元素的集合

单例模式最初的定义出现在《设计模式》(艾迪生维斯理,1994):保证一个类仅有一个实例,并提供一个访问它的全局访问点

动机

对于系统中的某些类来说,只有一个实例很重要:

例如:一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或 ID (序号)生成器。

如在 Windows 中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。

因此有时==确保系统中某个对象的唯一性==即一个类只能有一个实例非常重要。

注意点

根据定义,显然单例模式的要点有三个:

  • 某个类只能有一个实例
  • 它必须自行创建这个实例
  • 它必须自行向整个系统提供这个实例、

具体实现条件:

  1. 构造函数私有化,这样用户就不能够随意定义该类的对象。
  2. 类定义中含有一个该类的静态私有对象。
  3. 该类 提供了一个静态的公有函数 用于创建或者获取它本身的静态四有对象。

1. 饿汉式单例模式

饿汉式单例模式,顾名思义,就是程序启动时就实例化了该对象,并没有推迟到第一次使用该对象时再进行实例化;如果运行过程中没有使用到,该实例对象就被浪费掉了。

实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Singleton
{
public:
static Singleton* getInstance()
{
return &instance;
}

private:
// 静态实例化类
static Singleton instance;
// 私有化构造
Singleton() { cout << "私有化构造调用!!" << endl; }
// 删除赋值重载和拷贝构造
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};

// 外部提供 instance 实例
Singleton Singleton::instance;

使用结果:

1
2
3
4
5
6
7
8
9
10
int main()
{
Singleton* a1 = Singleton::getInstance();
Singleton* a2 = Singleton::getInstance();
Singleton* a3 = Singleton::getInstance();

cout << a1 << endl;
cout << a2 << endl;
cout << a3 << endl;
}

根据定义我们知道,a1a2a3打印出来应该是同一个地址

image.png

2. 懒汉式单例模式

通过创建静态对象,c++11是线程安全的

对象的实例化,延迟到第一次使用它的时候。在开发过程中,用的是懒汉模式,这样尽可能延迟对象的创建。

在 C++11 中,考虑线程安全的懒汉式单例:static 是线程安全的,不用考虑加锁。

只有有人调用的时候,才会进行构造。

实现:

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
// 懒汉式单例模式
class Singleton
{
public:
static Singleton* getInstance()
{
// 判断实例是否为空
if (instance == nullptr)
{
// 创建实例
instance = new Singleton();
}
// 有实例了则直接返回
return instance;
}

private:
static Singleton* instance;
// 私有化构造
Singleton() { cout << "私有化构造调用!!" << endl; }
// 删除赋值重载和拷贝构造
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
// 初始化实例为 nullptr
Singleton* Singleton::instance = nullptr;

使用:

1
2
3
4
5
6
7
8
9
10
int main()
{
Singleton* a1 = Singleton::getInstance();
Singleton* a2 = Singleton::getInstance();
Singleton* a3 = Singleton::getInstance();

cout << a1 << endl;
cout << a2 << endl;
cout << a3 << endl;
}

image.png

虽然运行结果没有问题,
但是,这个代码是==有问题==的:
线程不安全

对于if (instance == nullptr) 、instance = new Singleton()这两句来说,如果处在==多线程==环境下:

image.png
发现这种情况下,会执行多次对象的构造

线程安全版本实现

为了避免上述问题,改进代码,加入线程互斥锁(mutex)

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
#include <mutex>
std::mutex mtx;

// 懒汉式单例模式(线程安全版本)
class Singleton
{
public:
static Singleton* getInstance()
{
// 判断实例是否为空
if (instance == nullptr)
{
// 加锁
lock_guard<std::mutex> lck(mtx);
// 再进行判断
if (instance == nullptr)
{
// 创建实例
instance = new Singleton();
}
}
// 有实例了则直接返回
return instance;
}

private:
static Singleton* instance;
// 私有化构造
Singleton() { cout << "私有化构造调用!!" << endl; }
// 删除赋值重载和拷贝构造
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
// 初始化实例为 nullptr
Singleton* Singleton::instance = nullptr;

注意:

为了避免线程安全问题:需要进行锁+两次判断;避免因为一个线程后续操作未完成,而使得后来的线程进入if语句

image.png

精简实现(优化线程安全版本)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Singleton
{
public:
static Singleton* getInstance()
{
static Singleton instance;
// 有实例了则直接返回
return &instance;
}

private:
// 私有化构造
Singleton() { cout << "私有化构造调用!!" << endl; }
// 删除赋值重载和拷贝构造
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};

image.png

注意:

这个精简版之所以也是==线程安全==的,是因为,在底层对于static静态局部变量的初始化,编译器会自动==加锁和解锁==。

将上述代码在Linux通过g++编译,命令如下:

1
g++ -o 单例模式 单例模式.cpp -g

接下来启动gdb调试:

1
gdb 单例模式 -q

接下来输入l:(注意是==英文L的小写==)

image.png

接着下断点:b 10

image.png

接着运行到断点处:run

image.png

接下来执行:disassemble getinstance

image.png

可以看到,底层的汇编是自动加锁解锁的!

2. 工厂模式

Factory Method 用于定义一个用于创建产品的接口,由子类决定生产什么产品。

主要是对 对象的创建 进行了一个封装。

意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个工厂类,工厂模式使其创建过程延迟到子类进行。

主要解决:主要解决接口选择的问题。

优点:

  1. 用户只需要知道具体工厂的名称就可得到所要的产品,无须知道产品的具体创建过程。
  2. 扩展性高,增加一个产品,著需要扩展一个工厂类即可,无需修改原工厂内容,满足 开闭原则
  3. 屏蔽产品的具体实现,调用者只关心产品的接口。

缺点:

每次增加一个产品时,都需要增加一个具体类和对象实现工厂,使得系统中==类的个数成倍增加==,在一定程度上==增加了系统的复杂度==,同时也==增加了系统具体类的依赖==。

1. 简单工厂(Simple Factory)

实现:

首先是具体产品的实现继承关系

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
// 系列产品
// 基类
class Car
{
public:
Car(string name): _name(name) { }
// 虚函数
virtual void show() = 0;

protected:
string _name;
};
// 派生类
class Bmw :public Car
{
public:
Bmw(string name) :Car(name) {}
void show()
{
cout << "获取了一辆宝马汽车:" << _name << endl;
}
};
class Audi :public Car
{
public:
Audi(string name) :Car(name) {}
void show()
{
cout << "获取了一辆奥迪汽车:" << _name << endl;
}
};

简单工厂实现:

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
// 产品枚举
enum CarType
{
BMW,
AUDI
};
// 简单工厂类
class SimpleFactory
{
public:
// 用户想要创建一个对象,只需要知道名称就可以了
Car* createCar(CarType ct)
{
switch (ct)
{
case BMW:
return new Bmw("x86");
case AUDI:
return new Audi("a8");
default:
cerr << "传入参数错误:" << ct << endl;
break;
}
}
};

使用:

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
// 智能指针
unique_ptr<SimpleFactory> fac(new SimpleFactory());
unique_ptr<Car> p1(fac->createCar(BMW));
unique_ptr<Car> p2(fac->createCar(AUDI));

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

return 0;
}

image.png

需要注意的是:

为了更好地释放资源,这里使用了==智能指针==(需要加入头文件#include <memory>);

也可以直接使用new的方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
SimpleFactory* fac = new SimpleFactory();
Car* p1 = fac->createCar(BMW);
Car* p2 = fac->createCar(AUDI);

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

delete fac;
delete p1;
delete p2;

return 0;
}

使用普通指针(new)的时候就需要注意一定记得delete

不足之处!

可以看到,简单工厂可以做到,让用户创建对象的时候==只需要知道对象的名称==(BMWAUDI)就好,而不需要关心创建对象的细节(BMW是如何建造的、型号是什么等等细节)。

当然缺点也很明显:

每当想要==扩展对象==的时候(增加BENZ的对象)就需要在SimpleFactory类中添加代码,增加switch后面的case选项。这样一来,就需要修改源代码。灵活性非常的差!!!

那么,能不能做到==添加对象==的时候,不对现有代码进行修改呢?(也就是开发软件时候需要遵守的==开-闭原则==)

tips:

什么是==开-闭原则==呢?
扩展开放、对修改关闭;
也就是说,可以添加代码,但是添加代码的时候不能够对现有的代码进行修改。

2. 工厂方式(Factory Method)

==工厂方法==的思想就是定义一个Factory基类,基类中定义了一个纯虚函数(创建产品);之后定义派生类(具体产品的工厂)负责创建对应的产品。

可以做到不同的产品在不同的工厂里面创建,能够对现有工厂以及产品的修改关闭

工厂方法实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 工厂方式
// 基类(包含纯虚函数,不能实例化对象)
class Factory
{
public:
// 纯虚函数
virtual Car* createCar(string name) = 0;
};
// 宝马汽车工厂,负责生产宝马汽车
class BmwFac : public Factory
{
Car* createCar(string name)
{
return new Bmw(name);
}
};
// 奥迪汽车工厂,负责生产奥迪汽车
class AudiFac : public Factory
{
Car* createCar(string name)
{
return new Audi(name);
}
};

使用:

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
unique_ptr<Factory> bmwfty(new BmwFac());
unique_ptr<Factory> audifty(new AudiFac());
unique_ptr<Car> p1(bmwfty->createCar("X6"));
unique_ptr<Car> p2(audifty->createCar("A8"));

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

return 0;
}

image.png

注意:

可以看到这一次的代码和之前简单工厂的不同:

  • 简单工厂:unique_ptr<SimpleFactory> fac(new SimpleFactory());
  • 工厂方法:unique_ptr<Factory> bmwfty(new BmwFac());

也就是说,工厂方法是用基类指针实例化派生类对象,这也是动态多态发生的条件。这样的话,就能够实现多态了!

不足之处!

工厂方法解决了简单工厂的问题(无法对修改关闭),但是它也有自己的局限:
试想一下,宝马工厂里面只是售卖成品汽车吗?
应该不是吧,作为一家成熟的工厂,除了汽车之外,还有一系列配套的零件、产品:比如说:轮胎、车灯、真皮豪华座椅等等。也就是说,跟汽车相关联的有一整个产品簇。

但是我们的宝马工厂 BmwFac 里面只有一个 createCar 方法,如果想要添加产品的话,就需要增加新的类。但是这些产品其实都应该在一个 BmwFac 工厂里面。这才是现实的逻辑,另外,工厂类太多,会不好维护。

于是乎,抽象工厂 应运而生

3. 抽象工厂(Abstract Factory)

思想:

把有关联关系的,属于一个产品簇的所有产品创建的接口函数,放在一个抽象工厂里面AbstractFactory,派生类(具体产品的工厂)应该负责创建该产品簇里面所有的产品。

产品类:

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
// 系列产品1:汽车
class Car
{
public:
Car(string name) :_name(name) {}
virtual void show() = 0;
protected:
string _name;
};
class Bmw :public Car
{
public:
Bmw(string name) :Car(name) {}
void show()
{
cout << "获取了一辆宝马汽车:" << _name << endl;
}
};
class Audi :public Car
{
public:
Audi(string name) :Car(name) {}
void show()
{
cout << "获取了一辆奥迪汽车:" << _name << endl;
}
};
// 系列产品2:车灯
class Light
{
public:
virtual void show() = 0;
};
class BmwLight : public Light
{
public:
void show() { cout << "BMW light!" << endl; }
};
class AudiLight : public Light
{
public:
void show() { cout << "Audi light!" << endl; }
};

抽象工厂实现:

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
// 工厂方法 => 抽象工厂(对有一组关联关系的产品簇提供产品对象的统一创建)
class AbstractFactory
{
public:
// 工厂方法 创建汽车
virtual Car* createCar(string name) = 0;
// 工厂方法 创建汽车关联的产品,车灯
virtual Light* createCarLight() = 0;
};
// 宝马工厂
class BMWFactory : public AbstractFactory
{
Car* createCar(string name)
{
return new Bmw(name);
}
Light* createCarLight()
{
return new BmwLight();
}
};
// 高低工厂
class AudiFactory : public AbstractFactory
{
Car* createCar(string name)
{
return new Audi(name);
}
Light* createCarLight()
{
return new AudiLight();
}
};

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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;
}

image.png

3. 代理模式

为其他对象提供一种代理以控制这个对象的访问。

在某些情况下,一个对象不适合或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。

代理模式 属于 结构型模式

组成:

  • 抽象角色:通过接口或抽象类声明实现真实角色实现的业务方法。
  • 真实角色:实现抽象角色,定义真实角色所需实现的业务逻辑,供代理角色调用。
  • 代理角色:实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作(访问权限)。

优点:

  1. 职责清晰:真实的角色就是实现实际的业务逻辑,不用关心其他非本职的事务,通过后期的代理来完成事务,附带的结果就是编程简洁清晰。
  2. 代理对象可以在客户端和目标对象之间起到中介作用,保护了目标对象
  3. 高扩展性。

实现:

了解了代理模式的定义和优点以后,我们来模拟这样一个场景:

我们在网上观看电影的时候,一般都会借助一些视频网站(腾讯视频、优酷、爱奇艺等);在这些网站观看电影的时候,都会遇到这样的情况:

一些电影可以观看,一些电影(评分高的、刚上映的等)就观看不了,提示要充值VIP才能观看;更有甚者,充了VIP也看不了,还需要买券观看。

抛开其他不谈,这个观看视频的权限是如何实现的呢?其实就和我们今天要讲的代理模式息息相关。

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 VideoSite
{
public:
virtual void freemovie() = 0;
virtual void vipmovie() = 0;
virtual void ticketmovie() = 0;
};
// 委托类
class YangVideoSite : public VideoSite
{
public:
void freemovie()
{
cout << "观看免费电影" << endl;
}
void vipmovie()
{
cout << "观看vip电影" << endl;
}
void ticketmovie()
{
cout << "用券观看电影" << endl;
}
};
// 免费游客对应代理类
class FreeVideoSitepProxy : public VideoSite
{
public:
void freemovie()
{
video.freemovie();
}
void vipmovie()
{
cout << "您只是普通游客,需要升级成VIP,才能观看VIP电影" << endl;
}
void ticketmovie()
{
cout << "您目前没有电影券,需要购买券才能观看电影" << endl;
}
private:
YangVideoSite video;
};
// Vip 会员对应代理
class VipVideoSiteProxy
{
public:
// 构造函数
VipVideoSiteProxy() { pvideo = new YangVideoSite(); }
// 析构
~VipVideoSiteProxy() { delete pvideo; }
// 权限
void freemovie()
{
pvideo->freemovie();
}
void vipmovie()
{
pvideo->vipmovie();
}
void ticketmovie()
{
cout << "您目前没有电影券,需要购买券才能观看电影" << endl;
}

private:
YangVideoSite* pvideo;
};

理解:

可以看到,代码中的委托类 YangVideoSite 就是上面提到的真实角色。它拥有了该网站所有视频的观看权限。

每一个代理类都有一个委托类的数据成员。代理类通过委托类来访问视频网站。并且,可以根据当前代理的用户的身份(普通游客、VIP会员),来限制其访问的内容。

为了模拟在视频网站上浏览视频的全部情景,可以再设计一个函数,专门用来观看电影(免费电影、VIP电影、用券观看的电影):

1
2
3
4
5
6
void watchMovie(unique_ptr<VideoSite>& ptr)
{
ptr->freemovie();
ptr->vipmovie();
ptr->ticketmovie();
}

使用:

1
2
3
4
5
6
7
8
9
10
11
int main()
{
unique_ptr<VideoSite> p1(new FreeVideoSiteProxy());
unique_ptr<VideoSite> p2(new VipVideoSiteProxy());

watchMovie(p1);
cout << "----------------------------" << endl;
watchMovie(p2);

return 0;
}

image.png

4. 装饰器模式

装饰器模式(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
// 基类
class Car
{
public:
virtual void show() = 0;
};
// 派生类
// 三个实体的汽车类
class Bmw :public Car
{
public:
void show()
{
cout << "这是一辆宝马汽车,配置有:基本配置";
}
};
class Audi :public Car
{
public:
void show()
{
cout << "这是一辆奥迪汽车,配置有:基本配置";
}
};
class Benz :public Car
{
public:
void show()
{
cout << "这是一辆奔驰汽车,配置有:基本配置";
}
};

// 装饰器1 定速巡航
class Decorator01 : public Car
{
public:
// 初始化构造
Decorator01(Car* p) :pCar(p) { }
// 功能展示
void show()
{
pCar->show();
cout << ", 定速巡航";
}

private:
Car* pCar;
};
// 装饰器2 自动刹车
class Decorator02 :public Car
{
public:
Decorator02(Car* p) :pCar(p) {}
void show()
{
pCar->show();
cout << ",自动刹车";
}
private:
Car* pCar;
};
// 装饰器3 定速巡航
class Decorator03 :public Car
{
public:
Decorator03(Car* p) :pCar(p) {}
void show()
{
pCar->show();
cout << ",车道偏离";
}
private:
Car* pCar;
};

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int main()
{
// 给 宝马添加了新功能1
Car* p1 = new Decorator01(new Bmw());
// 继续添加
p1 = new Decorator02(p1);
p1 = new Decorator03(p1);
p1->show();
cout << endl;

// 奥迪
Car* p2 = new Decorator02(new Audi());
p2->show();
cout << endl;

// 奔驰
Car* p3 = new Decorator03(new Benz());
p3->show();
cout << endl;

return 0;
}

image.png

5. 适配器模式

适配器模式(Adapter Pattern):是所谓两个不兼容的接口之间的桥梁。

这种类型的设计模式属于==结构型模式==。

意图:

将一个类的==接口==转换成为客户希望的另一个==接口==。
适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

主要解决:

软件系统中,常常要将一些 “现存的对象” 放到新的环境中,而新环境中的接口实现有对象无法满足的。

优点:

  1. 可以让任何两个没有关联的类一起工作;
  2. 提高了类的复用;
  3. 增加了类的透明度;
  4. 灵活性好。

缺点:

过多地使用适配器,会让系统非常==零乱==,不易整体进行把握。比如:明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行==重构==。

代码实现:

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
class VGA
{
public:
virtual void play() = 0;
};
class TV01 :public VGA
{
public:
void play()
{
cout << "通过VGA接口连接投影仪,进行视频播放" << endl;
}
};
class Computer
{
public:
void playVideo(VGA* pVGA)
{
pVGA->play();
}
};

class HDMI
{
public:
virtual void play() = 0;
};
class TV02 : public HDMI
{
public:
void play()
{
cout << "通过HDMI接口连接投影仪,进行视频播放" << endl;
}
};
class VGAtoHDMIAdapter : VGA
{
public:
VGAtoHDMIAdapter(HDMI* p) :pHdmi(p) {}
void play()
{
pHdmi->play();
}

private:
HDMI* pHdmi;
};

使用:

1
2
3
4
5
6
7
int main()
{
Computer com;
com.playVideo(new TV01());

return 0;
}

image.png

接下来是HDMI接口的投影仪和VGA接口的电脑:

1
2
3
4
5
6
7
int main()
{
Computer com;
com.playVideo(new TV02());

return 0;
}

可以看到,编译器给我们报错了,提示说:

无法将参数 1 从“TV02 *”转换为“VGA *”; 这就说明了==接口不匹配==的时候,直接使用,是会出错的。

接下来为了使用新的设备,使用适配器:

1
2
3
4
5
6
7
int main()
{
Computer com;
com.playVideo(new VGAtoHDMIAdapter(new TV02()));

return 0;
}

image.png

6. 观察者模式

==观察者模式==(Observer Pattern),也叫我们熟知的==发布-订阅模式==。

它是一种 行为型模式

观察者模式主要关注的是对象的==一对多==的关系, 也就是多个对象依赖于一个对象,当该对象的状态发生改变时,其他对象都能够收到相应的通知

意图:

定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决:

一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

如何解决:

使用面向对象技术,可以将这种关系弱化。

优点:

  1. 观察者和被观察者是抽象耦合的;
  2. 建立了一套触发机制。

缺点:

  1. 如果一个被观察者对象有很多的直接和间接的观察者的话,通知所有的观察者需要花费很长的时间;
  2. 如果在观察者和被观察者目标之间有循环依赖的话,观察目标会触发他们之间的循环调用,可能会导致系统崩溃;
  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
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
#include <iostream>
#include <string>
#include <unordered_map>
#include <list>
using namespace std;

class Observer
{
public:
// 处理消息的接口
virtual void handle(int msgid) = 0;
};
// 第一个观察者实例
class Observer1 :public Observer
{
public:
void handle(int msgid)
{
switch (msgid)
{
case 1:
cout << "Observer1 recv 1 msg!" << endl;
break;
case 2:
cout << "Observer1 recv 2 msg!" << endl;
break;
default:
cout << "Observer1 recv unkown msg!" << endl;
break;
}
}
};
// 第二个观察者实例
class Observer2 :public Observer
{
public:
void handle(int msgid)
{
switch (msgid)
{
case 2:
cout << "Observer2 recv 2 msg!" << endl;
break;
default:
cout << "Observer2 recv unkown msg!" << endl;
break;
}
}
};
// 第三个观察者实例
class Observer3 :public Observer
{
public:
void handle(int msgid)
{
switch (msgid)
{
case 1:
cout << "Observer3 recv 1 msg!" << endl;
break;
case 3:
cout << "Observer3 recv 3 msg!" << endl;
break;
default:
cout << "Observer3 recv unkown msg!" << endl;
break;
}
}
};

// 主题类
class Subject
{
public:
// 给主题对象增加观察者对象
void addObserver(Observer* obser, int msgid)
{
_subMap[msgid].push_back(obser);
}
// 主题发生改变,通知相应的观察者处理事件
void dispatch(int msgid)
{
auto it = _subMap.find(msgid);
if (it != _subMap.end())
{
for (Observer* obs : it->second)
{
obs->handle(msgid);
}
}
}
private:
unordered_map<int, list<Observer*>> _subMap;
};

解读:

我们可以看到==主题类==(Subject)的数据成员是一个==unordered_map==。使用这个是因为我们不需要数据是有序的,为了提高增删查的速率,使用了无序map。

使用map的好处是,它作为一个键值对,可以存储我们想要的数据类型:(消息类型,订阅此消息类型的观察者们)。

因为同样的消息类型,可能有多个观察者,所以,unordered_map的第二个参数我们使用了==list==,来存储订阅此消息类型的所有观察者。

并且,在==主题类==(Subject)的成员方法addObserver中,我们使用了一个==中括号运算符==([])重载的特性:

如果当前容器中存有相应的msgid键的话,就直接添加对应的值(Obser);
如果当前容器中没有相应的msgid键的话,就直接添加该键,并且添加一个默认的值。

这一行代码也可以用下面的代码代替:

1
2
3
4
5
6
7
8
9
10
11
auto it = _subMap.find(msgid);
if (it != _subMap.end())
{
it->second.push_back(obser);
}
else
{
list<Observer*> lis;
lis.push_back(obser);
_subMap.insert({ msgid, lis });
}

使用:

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
int main()
{
Subject sub;
Observer* p1 = new Observer1();
Observer* p2 = new Observer2();
Observer* p3 = new Observer3();

sub.addObserver(p1, 1);
sub.addObserver(p1, 2);
sub.addObserver(p2, 2);
sub.addObserver(p3, 1);
sub.addObserver(p3, 3);

int msgid = 0;
for (;;)
{
cout << "请输入消息id:";
cin >> msgid;
if (msgid == -1)
break;
sub.dispatch(msgid);
}

return 0;
}

12.C++ 设计模式
http://example.com/2023/09/21/03.C++进阶部分/12.C++ 设计模式/
Author
Yakumo
Posted on
September 21, 2023
Licensed under