设计模式:观察者模式(Observer)
- 设计模式:观察者模式(Observer)
- 模式动机
- 模式定义
- 模式结构
- 时序图
- 模式实现
- 观察者模式在单线程环境下的测试
- 观察者模式在多线程环境下的测试
- 多线程下的观察者模式
- 模式分析
- 优缺点
- 适用场景
- 应用场景
- 模式拓展:MVC 模式
- 参考
设计模式:观察者模式(Observer)
观察者模式(Observer Pattern)属于行为型模式(Behavioral Pattern)的一种。
行为型模式(Behavioral Pattern)是对在不同的对象之间划分责任和算法的抽象化。
行为型模式不仅仅关注类和对象的结构,而且重点关注它们之间的相互作用。
通过行为型模式,可以更加清晰地划分类与对象的职责,并研究系统在运行时实例对象之间的交互。在系统运行时,对象并不是孤立的,它们可以通过相互通信与协作完成某些复杂功能,一个对象在运行时也将影响到其他对象的运行。
行为型模式分为类行为型模式和对象行为型模式两种:
- 类行为型模式:类的行为型模式使用继承关系在几个类之间分配行为,类行为型模式主要通过多态等方式来分配父类与子类的职责。
- 对象行为型模式:对象的行为型模式则使用对象的聚合关联关系来分配行为,对象行为型模式主要是通过对象关联等方式来分配两个或多个类的职责。根据“合成复用原则”,系统中要尽量使用关联关系来取代继承关系,因此大部分行为型设计模式都属于对象行为型设计模式。
模式动机
建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机。
模式定义
观察者模式(Observer Pattern):定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。
观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
模式结构
- 主题(Subject):也称为被观察者或可观察者,它是具有状态的对象,并维护着一个观察者列表。主题提供了添加、删除和通知观察者的方法。
- 观察者(Observer):观察者是接收主题通知的对象。观察者需要实现一个更新方法,当收到主题的通知时,调用该方法进行更新操作。
- 具体主题(Concrete Subject):具体主题是主题的具体实现类。它维护着观察者列表,并在状态发生改变时通知观察者。
- 具体观察者(Concrete Observer):具体观察者是观察者的具体实现类。它实现了更新方法,定义了在收到主题通知时需要执行的具体操作。
观察者模式通过将主题和观察者解耦,实现了对象之间的松耦合。当主题的状态发生改变时,所有依赖于它的观察者都会收到通知并进行相应的更新。
时序图
模式实现
被观察者基类 Subject.h:
#ifndef _SUBJECT_H_
#define _SUBJECT_H_
#include "Observer.h"
#include <list>
/**
* 被观察者基类
* 1. 观察者需要在这里注册、注销
* 2. 当被观察者发生变化时,需要通知观察者
*/
class Subject
{
protected:
std::list<Observer*> m_ObserverList;
public:
Subject() {}
virtual ~Subject() = default;
virtual void attach(Observer*) = 0; // 向该目标绑定一个观察者
virtual void detach(Observer*) = 0; // 将观察者从该目标上注销
virtual void notify() = 0; // 目标自身发生变化时,通知所有的观察者
virtual int getState() = 0; // 取得目标的值
virtual void setState(int) = 0; // 修改目标的值
};
#endif // !_SUBJECT_H_
被观察者派生类 ConcreteSubject.h:
#ifndef _CONSRETE_SUBJECT_H_
#define _CONSRETE_SUBJECT_H_
#include "Observer.h"
#include "Subject.h"
class ConcreteSubject : public Subject
{
private:
int state;
public:
ConcreteSubject() {}
virtual ~ConcreteSubject() {}
void attach(Observer* pObserver)
{
m_ObserverList.push_back(pObserver);
}
void detach(Observer* pObserver)
{
m_ObserverList.remove(pObserver);
}
void notify()
{
std::list<Observer*>::iterator iter = m_ObserverList.begin();
while (iter != m_ObserverList.end())
{
(*iter)->update(this);
iter++;
}
}
int getState()
{
return state;
}
void setState(int st)
{
state = st;
}
};
#endif // !_CONSRETE_SUBJECT_H_
观察者基类 Observer.h:
#ifndef _OBSERVER_H_
#define _OBSERVER_H_
class Subject;
// 观察者基类
class Observer
{
public:
Observer() {}
virtual ~Observer() = default;
virtual void update(Subject*) = 0; // 接收从目标传递过来的内容,并进行处理
};
#endif // !_OBSERVER_H_
观察者派生类 ConcreteObserver.h:
#ifndef _CONSRETE_OBSERVER_H_
#define _CONSRETE_OBSERVER_H_
#include "Observer.h"
#include "Subject.h"
#include <iostream>
class ConcreteObserver : public Observer
{
private:
int observerState;
public:
ConcreteObserver() {}
virtual ~ConcreteObserver() {}
void update(Subject* pSubject)
{
observerState = pSubject->getState();
std::cout << "ConcreteObserver get the update New State: " << observerState << std::endl;
}
};
#endif // !_CONSRETE_OBSERVER_H_
观察者模式在单线程环境下的测试
测试代码 main.cpp:
#include "ConcreteSubject.h"
#include "ConcreteObserver.h"
using namespace std;
int main()
{
ConcreteSubject* pSubject = new ConcreteSubject();
Observer* pObserver1 = new ConcreteObserver();
Observer* pObserver2 = new ConcreteObserver();
pSubject->attach(pObserver1);
pSubject->attach(pObserver2);
pSubject->setState(2);
pSubject->notify();
cout << "-------------------------" << endl;
pSubject->detach(pObserver1);
pSubject->setState(3);
pSubject->notify();
delete pObserver1;
delete pObserver2;
delete pSubject;
system("pause");
return 0;
}
运行结果:
本程序创建了一个目标 pSubject,两个观察者 pObserver1 和 pObserver2,将这两个观察者注册在目标上。
设置目标的 state 为 2,然后使用 notify() 函数进行通知。可以看到,两个观察者都对目标的变化做出来响应;
目标注销 pObserver1,再次设置目标的 state 为 3,然后使用 notify() 函数进行通知。可以看到,现在只有一个观察者对目标的变化做出来响应。
观察者模式在多线程环境下的测试
测试代码 main.cpp:
#include "ConcreteSubject.h"
#include "ConcreteObserver.h"
#include <thread>
using namespace std;
ConcreteSubject* pSubject = new ConcreteSubject();
Observer* pObserver = new ConcreteObserver();
static void func1()
{
pSubject->setState(1);
pSubject->notify();
}
static void func2()
{
pSubject->setState(2);
pSubject->notify();
}
int main()
{
pSubject->attach(pObserver);
thread t1(func1);
thread t2(func1);
t1.join();
t2.join();
pSubject->detach(pObserver);
delete pObserver;
delete pSubject;
system("pause");
return 0;
}
运行结果:
之前的代码显然不能用于多线程,不能让多线程操作一个没有同步的数据结构。
多线程下的观察者模式
只需要修改被观察者的派生类的 attach、detach、notify 三个函数,加上互斥锁。
mutex类提供了能用于保护共享数据免受从多个线程同时访问的同步原语。
创建lock_guard对象时,它试图接收给定互斥的所有权。控制离开创建 lock_guard 对象的作用域时,销毁 lock_guard 并释放互斥。
修改后的 ConcreteSubject.h:
#ifndef _CONSRETE_SUBJECT_H_
#define _CONSRETE_SUBJECT_H_
#include "Observer.h"
#include "Subject.h"
#include <mutex>
class ConcreteSubject : public Subject
{
private:
int state;
std::mutex _mutex;
public:
ConcreteSubject() {}
virtual ~ConcreteSubject() {}
void attach(Observer* pObserver)
{
std::lock_guard<std::mutex> guard(_mutex);
m_ObserverList.push_back(pObserver);
}
void detach(Observer* pObserver)
{
std::lock_guard<std::mutex> guard(_mutex);
m_ObserverList.remove(pObserver);
}
void notify()
{
std::lock_guard<std::mutex> guard(_mutex);
std::list<Observer*>::iterator iter = m_ObserverList.begin();
while (iter != m_ObserverList.end())
{
(*iter)->update(this);
iter++;
}
}
int getState()
{
return state;
}
void setState(int st)
{
state = st;
}
};
#endif // !_CONSRETE_SUBJECT_H_
多线程下的测试代码不变,以下是测试结果:
模式分析
- 观察者模式描述了如何建立对象与对象之间的依赖关系,如何构造满足这种需求的系统。
- 这一模式中的关键对象是观察目标和观察者,一个目标可以有任意数目的与之相依赖的观察者,一旦目标的状态发生改变,所有的观察者都将得到通知。
- 作为对这个通知的响应,每个观察者都将即时更新自己的状态,以与目标状态同步,这种交互也称为发布-订阅(publish-subscribe)。目标是通知的发布者,它发出通知时并不需要知道谁是它的观察者,可以有任意数目的观察者订阅它并接收通
优缺点
优点:
- 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
- 观察者模式在观察目标和观察者之间建立一个抽象的耦合。
- 观察者模式支持广播通信。
- 观察者模式符合“开闭原则”的要求。
缺点:
- 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
适用场景
在以下情况下可以使用观察者模式:
- 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
- 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
- 一个对象必须通知其他对象,而并不知道这些对象是谁。
- 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
应用场景
观察者模式在软件开发中应用非常广泛,如某电子商务网站可以在执行发送操作后给用户多个发送商品打折信息,某团队战斗游戏中某队友牺牲将给所有成员提示等等,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。
模式拓展:MVC 模式
MVC模式是一种架构模式,它包含三个角色:模型(Model),视图(View)和控制器(Controller)。观察者模式可以用来实现MVC模式,观察者模式中的观察目标就是MVC模式中的模型(Model),而观察者就是MVC中的视图(View),控制器(Controller)充当两者之间的中介者(Mediator)。当模型层的数据发生改变时,视图层将自动改变其显示内容。
参考
- https://design-patterns.readthedocs.io/zh-cn/latest/behavioral_patterns/observer.html
- https://blog.csdn.net/sinat_38816924/article/details/122760490
- https://www.jianshu.com/p/a14ccd51b97d
- https://www.runoob.com/design-pattern/observer-pattern.html