C++设计模式-观察者(3)

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

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

软件设计开-闭原则

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

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

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

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

行为型设计模式

行为型设计模式是一类设计模式,关注对象之间的责任分配和相互协作(主要关注对象之间的通信)。它们主要用于定义类和对象之间的交互方式,帮助软件系统实现更加灵活和可扩展的结构。以下是一些常见的行为型设计模式

观察者模式(Observer Pattern)

策略模式(Strategy Pattern)

命令模式(Command Pattern)

状态模式(State Pattern)

责任链模式(Chain of Responsibility Pattern)

迭代器模式(Iterator Pattern)



一、观察者模式

观察者模式(Observer Pattern)是一种行为型设计模式定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会收到通知并自动更新。此模式通常用于事件处理系统。又称之为观察者-监听者或者发布-订阅模式

举例:

一组数据(数据对象)

有三个对象是依赖这一组数据:曲线图(对象1)、柱状图(对象2)、圆饼图(对象3)

当这一组数据(数据对象)发生改变时,对象1、对象2、对象3应该及时收到相应通知,并且自动进行更新

主要角色

主题(Subject):持有观察者的集合,定义添加、删除和通知观察者的方法

观察者(Observer):定义一个更新接口,用于接收主题的通知

具体主题(Concrete Subject):实现主题接口,维护状态并通知观察者

具体观察者(Concrete Observer):实现观察者接口,维护与主题的同步


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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
#include <iostream>
#include <unordered_map>
#include <memory>
#include <list>
using namespace std;

/**********************抽象观察者**************************/
// 观察者抽象类
class Observer{
public:
// 处理消息接口(更新接口,用于接收主题的通知) msgId 为消息id
virtual void handle(int msgId) = 0;
virtual ~Observer() {}
};
/**********************抽象观察者**************************/


/**********************具体观察者**************************/
// 观察者实例1--仅对消息1/2感兴趣
class Observer1:public Observer{
public:
Observer1(){}
// 重写基类纯虚函数--接收主题信息
void handle(int msgId) override
{
switch (msgId)
{
case 1:
cout << "Observer1 recv 1 msg" << endl;
break;
case 2:
cout << "Observer1 recv 2 msg" << endl;
break;
default:
cout << "Observer1 recv unknow msg" << endl;
break;
}
}
};

// 观察者实例2--仅对消息2感兴趣
class Observer2:public Observer{
public:
Observer2(){}
// 重写基类纯虚函数--接收主题信息
void handle(int msgId) override
{
switch (msgId)
{
case 2:
cout << "Observer2 recv 2 msg" << endl;
break;
default:
cout << "Observer2 recv unknow msg" << endl;
break;
}
}
};

// 观察者实例3--仅对消息1/3感兴趣
class Observer3:public Observer{
public:
Observer3(){}
// 重写基类纯虚函数--接收主题信息
void handle(int msgId) override
{
switch (msgId)
{
case 1:
cout << "Observer3 recv 1 msg" << endl;
break;
case 3:
cout << "Observer3 recv 3 msg" << endl;
break;
default:
cout << "Observer3 recv unknow msg" << endl;
break;
}
}
};
/**********************具体观察者**************************/


/**********************主题类**************************/
// 维护观察者列表
// 提供添加观察者、删除观察者、通知等方法,用于管理观察者和通知状态更新
class Subject{
public:
// 添加观察者
void addObserver(Observer *obSer,int msgId)
{
_subMap[msgId].push_back(obSer);
}

// 删除观察者
void deleteObserver(Observer *obSer) {
for(auto& [key,LIST]:_subMap)
{
LIST.remove(obSer);
}
}

// 通知(主题检测发生改变,通知相应的观察者对象处理事件)
void notify(int msgId)
{
auto it = _subMap.find(msgId);
// map中存在对应的键
if(it != _subMap.end())
{
for(Observer *observer:it->second)
{
// 通知对该id消息感兴趣的观察者
observer->handle(msgId);
}
}
}

private:
// list 中存储的 Observer*裸指针,而不是智能指针对象
unordered_map<int,list<Observer*>> _subMap;
};
/**********************主题类**************************/


int main()
{
// 创建主题对象
Subject subject;
// 观察观察者对象
Observer *p1 = new Observer1();
Observer *p2 = new Observer2();
Observer *p3 = new Observer3();

// 主题中添加观察者(类似于观者者订阅该主题)
subject.addObserver(p1,1);
subject.addObserver(p1,2);
subject.addObserver(p2,2);
subject.addObserver(p3,1);
subject.addObserver(p3,3);

// 发布主题
int msgId = 0;
for(;;)
{
cout << "输入消息id: ";
cin >> msgId;
if(msgId == -1)
break;
subject.notify(msgId);
}

delete p1;
delete p2;
delete p3;

return 0;
}

2.优缺点

优点:

解耦:观察者与主题之间是松散耦合,方便扩展和维护

动态更新:观察者可以在运行时动态添加或移除

广播通信:主题向所有注册观察者广播通知

缺点:

可能有性能开销:如果观察者较多,通知过程可能较耗时

可能引起循环依赖:不小心使用可能导致循环依赖

无序通知:观察者收到通知的顺序不一定

适用场景

事件驱动系统:如 GUI 事件处理

模型视图分离:如MVC架构中,视图观察模型变化

对象间的联动:一个对象的改变需要引起其他对象的变化

观察者模式通过定义对象间的一对多依赖关系,使得一个对象状态改变时,依赖的对象能够自动收到通知并更新,广泛应用于事件驱动系统和实时更新场景