👂 Honey Honey - 孙燕姿 - 单曲 - 网易云音乐
目录
[🈲现实场景 + 代码](#🈲现实场景 + 代码)
[场景1 -- 报纸发行](#场景1 -- 报纸发行)
[场景2 -- 气象资料发布](#场景2 -- 气象资料发布)
[场景3 -- 过红绿灯](#场景3 -- 过红绿灯)
[🏔观察者 -- 模式结构](#🏔观察者 -- 模式结构)
[🏦观察者 -- 适用情况](#🏦观察者 -- 适用情况)
🌼前言
《Linux多线程服务器端编程 使用muduo C++网络库》第 3 页提到,"本书默认大家已熟知 --++观察者模式++",特地来学习
🌼描述
- ++观察者模式++是一种行为设计模式,允许你定义一种订阅机制,可在对象事件发生时,通知多个 "观察" 该对象的其他对象
- 定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新
- 一种行为型的设计模式,QT 中的信号槽就是一种典型的观察者模式
🎂问题
比如你有两种类型的对象:顾客 和 商店。
顾客对某个特定品牌的产品非常感兴趣(比如最新型号的 iphone 手机),该产品很快会在商店里出售。
顾客可以每天到商店里看看 iphone 到货了没。如果最新款 iphone 没到货,大多数到达商店的顾客就会空手而归(浪费资源)
还有个问题是:为了避免 "顾客每天到商店查看到货情况",商店可以在每次新产品到货时,向 所有顾客 发送邮件,那么部分客户就不用反复前往商店。但是这会++打扰对新产品不感兴趣的顾客++。
如何解决呢?
💪解决方案
观察者模式 就此登场:
发布者(publisher):自身状态的改变,通知到其他对象。
订阅者(subscribers):希望关注发布者状态变化的对象。
观察者模式中,发布者类拥有自己的 添加订阅机制 ,每个对象都能订阅或取消订阅发布者事件流该机制包括:
1)一个用于存储订阅者对象的列表 2)几个用于添加或删除该列表中订阅者的方法
发布者发布通知后,都需要遍历订阅者列表,然后调用每个订阅者对象特定的通知方法
为了解耦订阅者和发布者,所有订阅者都要实现相同接口,发布者只通过接口和订阅者交互如果有多种不同类型的发布者,而且要求订阅者要兼容所有发布者,那么就让所有发布者拥有相同的接口。
这个接口只需描述几个订阅方法
如此一来,订阅者就可以在不与具体发布者耦合的情况下,通过接口观察发布者的状态
🈲现实场景 + 代码
++每个代码都是相似的,不同场景,增加熟练度++
场景1 -- 报纸发行
场景
发布者 -- 出版社
订阅者 -- 渣渣辉
比方说,你订阅了一份报纸,那么就不需要再去报摊查询新出版的报纸了。
出版社(发布者)会在报纸出版后,直接将最新一期报纸寄到你的邮箱。
出版社(发布者)负责维护订阅者列表( subscriber() ),了解订阅者对哪些报纸感兴趣。
当订阅者不感兴趣后,他们随时可以从该列表退出。
解释
++解释1 -- 代码中的接口++
ISubscriber和IPublisher是两个接口,定义了订阅者和出版者的基本行为。这两个接口是基类。
ISubscriber接口定义了一个Update方法,这是具体订阅者需要实现的方法,用于接收来自出版者的消息。
IPublisher接口定义了Attach、Detach和Notify方法,分别用于添加订阅者、移除订阅者和通知所有订阅者。
Publisher是IPublisher的子类,实现了IPublisher接口的所有方法。它维护了一个订阅者列表,并在有新消息时通知所有订阅者。
Subscriber是ISubscriber的子类,实现了ISubscriber接口的Update方法。它保存了一个出版者的引用,并可以从出版者的订阅者列表中添加或移除自己。在
ClientCode函数中,创建了一个Publisher对象和多个Subscriber对象,并通过Attach和Detach方法管理订阅者列表。当Publisher有新消息时,所有在订阅者列表中的Subscriber都会收到通知。总的来说,
ISubscriber和IPublisher是基类,定义了一组行为Subscriber 和 Publisher 是子类,实现了这些行为。这是典型的面向对象设计,++通过接口和实现分离++,使得代码更加灵活和可扩展
++解释2 -- 整体逻辑++
这段代码实现了观察者模式,
其中包含两个接口:ISubscriber(订阅者)和IPublisher(发布者)
具体的订阅者类Subscriber和发布者类Publisher分别实现了这两个接口
ISubscriber接口定义了一个Update方法,接收发布者的消息IPublisher接口定义了Attach、Detach和Notify方法(添加一个订阅者、删除一个订阅者,通知++所有++订阅者)
Publisher类中的订阅者列表 list_subscriber_,存储所有订阅者有新消息时,Publisher 通过 Notify 方法通知所有订阅者
Publisher 的 CreateMessage 方法,创建新消息 + 通知所有订阅者
Subscriber 类在构造函数中将自己添加到 Publisher 的订阅者列表,Update方法接收来自Publisher的消息Subscriber 的 RemoveMeFromTheList(),从Publisher的订阅者列表中删除自己
main函数中,创建了一个Publisher对象和三个Subscriber对象Publisher发布了几条消息,Subscriber接收并打印出这些消息
++解释3 -- Publisher 接口++
**1)**为什么 Subscriber 类需要通过通过统一接口 ISubscriber 实现呢?
是为了使 Publisher类 和 Subscriber类 解耦,具体的说👇
IPublisher接口 只依赖于 ISubscriber 接口,而不是具体的 Subscriber类,所以我们可以在不修改 Publisher类 的情况下,添加新的 Subsriber 类型,只要这个新的 Subscriber 类型实现了 ISubscriber 接口即可(这就是"解耦")
**2)**那么为什么 Publisher 类也需要接口 IPublisher 呢?比如说,你以后想要创建一个新的 Publisher 类,只需要实现 IPublisher 接口即可
任何依赖 IPublisher 接口的代码,都可以无缝衔接地使用这个新的 Publisher 类
++解释4 -- 类的关系++
参考博客👇
uml 类图依赖与关联的区别 - 掸尘 - 博客园 (cnblogs.com)
Publisher 和 ISubscriber 之间是聚合关系
这就像一个报社(Publisher)和它的订阅者(ISubscriber)之间的关系
报社是整体,订阅者是部分
报社可以有很多订阅者,但是即使没有订阅者,报社依然可以存在
这就是所谓的"has-a"关系。Subscriber 实现了 ISubscriber 接口,Publisher 实现了 IPublisher 接口
这就像一个具体的订阅者(Subscriber)是订阅者接口(ISubscriber)的具体实现,一个具体的报社(Publisher)是报社接口(IPublisher)的具体实现
这是一种"is-a"关系
main函数(也就是客户端代码)关联了 Publisher,依赖了 Subscriber
这就像客户端代码知道报社(Publisher)的存在,并且使用了订阅者(Subscriber)
这是一种"knows-a"关系和"uses-a"关系
客户端代码知道报社的存在,可以通过报社发布消息
同时,客户端代码也使用了订阅者,可以创建订阅者,让订阅者订阅报社的消息
++代码++
cpp
#include <iostream> // 输入输出流库
#include <list>
#include <string>
// 订阅者接口
class ISubscriber {
public:
virtual ~ISubscriber(){}; // 虚析构函数
// 具体订阅者的更新方法;= 0 纯虚函数,子类必须自己实现
virtual void Update(const std::string &message_from_publisher) = 0;
};
// 出版社接口
class IPublisher {
public:
// 纯虚函数:基类不能被实例化,只能在子类实现
// 虚析构 -- 保证子类析构时能够调用基类析构
virtual ~IPublisher(){};
// 添加订阅者;= 0 子类自己实现
virtual void Attach(ISubscriber *subscriber) = 0;
// 移除订阅者;= 0 子类自己实现
virtual void Detach(ISubscriber *subscriber) = 0;
// 通知所有订阅者;= 0 子类自己实现
virtual void Notify() = 0;
};
// 具体出版社类
class Publisher : public IPublisher {
public:
virtual ~Publisher() { // 析构
std::cout << std::endl;
std::cout << "报社要倒闭了!!!" << std::endl;
}
// override 表示子类覆盖父类的方法 -- 保证方法名和参数一致
// 添加 -- 子类实现
void Attach(ISubscriber *subscriber) override {
list_subscriber_.push_back(subscriber);
}
// 删除
void Detach(ISubscriber *subscriber) override {
list_subscriber_.remove(subscriber);
}
// 通知
void Notify() override {
std::cout << std::endl;
// 订阅者列表迭代器
std::list<ISubscriber *>::iterator iterator = list_subscriber_.begin();
HowManySubscriber(); // 输出订阅者数量
while (iterator != list_subscriber_.end()) {
// 更新所有订阅者
(*iterator)->Update(message_);
++iterator;
}
}
// 创建消息 && 通知订阅者
void CreateMessage(std::string message = "Empty") {
// Empty 默认值,会被输入值覆盖
this->message_ = message;
Notify();
}
// 输出订阅者数量
void HowManySubscriber() {
std::cout << "有 " << list_subscriber_.size()
<< " 个傻逼看我的报纸" << std::endl;
}
// 其他业务逻辑,大事件发生时通知订阅者
void SomeBusinessLogic() {
this->message_ = "大事件!大事件!"; // 更改消息
Notify(); // 通知
std::cout << "房价跌到 300 块一平,快点入手!" << std::endl;
}
private:
std::list<ISubscriber *> list_subscriber_; // 订阅者列表
std::string message_; // 发送给订阅者的消息
};
// 具体的订阅者类
class Subscriber : public ISubscriber {
public:
// 构造函数
Subscriber(Publisher &publisher) : publisher_(publisher) {
this->publisher_.Attach(this); // 添加订阅者
std::cout << std::endl;
std::cout << "O(∩_∩)O渣渣 "
<< ++Subscriber::static_number_
<< " 号订阅了报纸" << std::endl;
this->number_ = Subscriber::static_number_; // 订阅者编号
}
virtual ~Subscriber() {
std::cout << std::endl;
std::cout << "垃圾报纸,毁我青春,注销 " << this->number_
<< " 号" << std::endl;
}
// 用新消息更新订阅者
void Update(const std::string &message_from_publisher) override {
message_from_publisher_ = message_from_publisher;
PrintInfo();
}
// 从报社的订阅者列表,删除此订阅者
void RemoveMeFromTheList() {
publisher_.Detach(this);
std::cout << std::endl;
std::cout << "订阅者 " << number_ << " 不看报纸了" << std::endl;
}
// 打印新消息
void PrintInfo() {
std::cout << "订阅者 " << this->number_ << " 号: "
<< message_from_publisher_ << std::endl;
}
private:
std::string message_from_publisher_; // 来自报社的信息
Publisher &publisher_; // 报社的引用
static int static_number_; // 订阅者实例的数量
int number_; // 订阅者编号
/*
static
静态成员变量,只有一份拷贝
所有实例共享
通过类名::静态成员变量名访问
类外初始化
*/
};
// static成员变量,类外初始化
int Subscriber::static_number_ = 0;
// 客户端代码
int main() {
Publisher *publisher = new Publisher; // 创建报社
// 创建 3 个看报纸的人
Subscriber *subscriber1 = new Subscriber(*publisher);
Subscriber *subscriber2 = new Subscriber(*publisher);
Subscriber *subscriber3 = new Subscriber(*publisher);
// 报社发布消息 为空
publisher->CreateMessage();
// 报社发布新消息
publisher->CreateMessage("房价又涨了!");
// 第 2 个人退出
subscriber2->RemoveMeFromTheList();
// 第 4 个人加入
Subscriber *subscriber4 = new Subscriber(*publisher);
// 发布新消息
publisher->CreateMessage("我买了新车!!");
// 发布新消息
publisher->SomeBusinessLogic();
delete subscriber1;
delete subscriber2;
delete subscriber3;
delete subscriber4;
delete publisher; // 报社倒闭
return 0;
}
cpp
O(∩_∩)O渣渣 1 号订阅了报纸
O(∩_∩)O渣渣 2 号订阅了报纸
O(∩_∩)O渣渣 3 号订阅了报纸
有 3 个傻逼看我的报纸
订阅者 1 号: Empty
订阅者 2 号: Empty
订阅者 3 号: Empty
有 3 个傻逼看我的报纸
订阅者 1 号: 房价又涨了!
订阅者 2 号: 房价又涨了!
订阅者 3 号: 房价又涨了!
订阅者 2 不看报纸了
O(∩_∩)O渣渣 4 号订阅了报纸
有 3 个傻逼看我的报纸
订阅者 1 号: 我买了新车!!
订阅者 3 号: 我买了新车!!
订阅者 4 号: 我买了新车!!
有 3 个傻逼看我的报纸
订阅者 1 号: 大事件!大事件!
订阅者 3 号: 大事件!大事件!
订阅者 4 号: 大事件!大事件!
房价跌到 300 块一平,快点入手!
垃圾报纸,毁我青春,注销 1 号
垃圾报纸,毁我青春,注销 2 号
垃圾报纸,毁我青春,注销 3 号
垃圾报纸,毁我青春,注销 4 号
报社要倒闭了!!!
场景2 -- 气象资料发布
IDisplayA 和 IDisplayB 类是订阅者,它们实现了 IDisplay 接口并订阅了 DataCenter 发布的通知。DataCenter 类是发布者,它维护了一个订阅者列表,并在数据变化时通知所有的订阅者
气象站发布气象资料给数据中心,数据中心经过处理,将气象信息更新到两个不同的显示终端(A 和B)
上层:气象站
下层:显示中断
依赖:数据中心
++代码2++
cpp
#include <iostream>
#include <vector>
using namespace std;
// IDisplay是一个抽象基类,定义了观察者的接口
class IDisplay {
public:
virtual void show(float temperature) = 0; // 所有的观察者都需要实现这个方法
virtual ~IDisplay() = default; // 虚析构函数,用于删除派生类的对象
};
// IDisplayA是一个观察者,实现了IDisplay的接口
class IDisplayA : public IDisplay {
public:
void show(float temperature) override { // 打印温度信息
cout << "Display A: " << temperature << endl;
}
};
// IDisplayB也是一个观察者,实现了IDisplay的接口
class IDisplayB : public IDisplay {
public:
void show(float temperature) override { // 打印温度信息
cout << "Display B: " << temperature << endl;
}
};
// WeatherData是一个数据类,用于存储天气数据
class WeatherData {
// 这里可以添加一些属性和方法,例如温度、湿度等
};
// DataCenter是主题,它维护了一个观察者列表,并在状态改变时通知所有的观察者
class DataCenter {
public:
void Attach(IDisplay *ob) { // 添加一个观察者
obs.push_back(ob);
}
void Detach(IDisplay *ob) { // 删除一个观察者
obs.erase(remove(obs.begin(), obs.end(), ob), obs.end());
}
void Notify() { // 通知所有的观察者
float temper = CalcTemperature();
for (auto iter = obs.begin(); iter != obs.end(); iter++) {
(*iter)->show(temper);
}
}
private:
WeatherData* GetWeatherData() { // 获取天气数据
// 这里可以添加一些代码,例如从数据库或者API获取天气数据
return new WeatherData;
}
float CalcTemperature() { // 计算温度
WeatherData *data = GetWeatherData();
float temper = 25.0f; // 这里只是一个示例,实际的温度应该从WeatherData中获取
delete data;
return temper;
}
vector<IDisplay*> obs; // 观察者列表
};
int main() {
DataCenter *center = new DataCenter; // 创建一个DataCenter对象
IDisplay *da = new IDisplayA; // 创建一个IDisplayA对象
IDisplay *db = new IDisplayB; // 创建一个IDisplayB对象
center->Attach(da); // 将IDisplayA添加到观察者列表
center->Attach(db); // 将IDisplayB添加到观察者列表
center->Notify(); // 通知所有的观察者
delete da; // 删除IDisplayA对象
delete db; // 删除IDisplayB对象
delete center; // 删除DataCenter对象
return 0;
}
场景3 -- 过红绿灯
发布者 -- 红绿灯
订阅者 -- 汽车
十字路口汽车等待红绿灯变化
++代码3++
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// Observer class, which is an abstract class
// that other classes can inherit from to become observers
// 订阅者接口 Observer
class Observer {
public:
Observer(int num):m_number(num){}
// Virtual destructor to ensure correct deletion
// when deleting derived classes through a base class pointer
virtual ~Observer() {}
// Pure virtual function to be implemented by derived classes
virtual void update(bool flag) = 0;
protected:
int m_number;
};
// Subject class, which is an abstract class
// that other classes can inherit from to become subjects
// 发布者接口 Subject
class Subject {
public:
virtual ~Subject() {}
virtual void attach(Observer* observer) = 0;
virtual void detach(Observer* observer) = 0;
virtual void notify(bool flag) = 0;
protected:
vector<Observer*> observers;
};
// TrafficLight class, which is a concrete subject
// that observers can attach to
// 发布者具体实现
class TrafficLight : public Subject {
public:
void attach(Observer* observer) override {
observers.push_back(observer);
}
void detach(Observer* observer) override {
observers.erase(remove(observers.begin(), observers.end(), observer), observers.end());
}
// Notify all attached observers with the current state
void notify(bool flag) override {
if (flag) {
cout << "Green light, go." << endl;
} else {
cout << "Red light, stop." << endl;
}
for (auto observer : observers) {
observer->update(flag);
}
}
};
// Car class, which is a concrete observer that can attach to a subject
// 订阅者具体实现
class Car : public Observer {
public:
Car(int num) : Observer(num) {}
// Update the state of the car based on the state of the traffic light
void update(bool flag) override {
if (flag) {
cout << "Car " << m_number << ": Start." << endl;
} else {
cout << "Car " << m_number << ": Stop." << endl;
}
}
};
int main() {
Subject *subject = new TrafficLight;
Observer *car1 = new Car(1);
Observer *car2 = new Car(2);
// Attach the cars to the traffic light
subject->attach(car1);
subject->attach(car2);
// Notify the cars with the state of the traffic light
subject->notify(true);
subject->notify(false);
/*
一个类有虚函数,那么它应该有一个虚析构函数
这样,当删除一个指向派生类对象的基类指针时,派生类的析构函数也会被调用,防止资源泄露
*/
delete car1;
delete car2;
delete subject;
return 0;
}
cpp
Green light, go.
Car 1: Start.
Car 2: Start.
Red light, stop.
Car 1: Stop.
Car 2: Stop.
🏔观察者 -- 模式结构
结合场景1代码,看👇的模式结构图
++解释 -- 类图++
cpp
- subscriber: Subscriber[]
// 私有 成员变量名: 类型
// subscriber 是类中的一个私有成员变量
// 这个变量的类型是:存储多个 Subscriber 对象的数组
cpp
+ subscribe(s: Subscriber)
// 公有 函数名(参数名: 参数类型)
- 发布者(Publisher)向其他对象发送信息。Notify() 会在报社自身状态改变 或 执行特定函数后发生。Publisher 类中包含 Detach() 和 Attach(),允许订阅者离开或加入。
- 新事件发生时,报社会遍历订阅列表 list_subscriber_,并调用每个订阅者的 Notify(),该方法在 ISubscriber(订阅者接口)声明。
- 订阅者接口(ISubscriber):大多数情况,该接口只包含一个 Update() 方法,该方法拥有多个参数来传递信息。
- 具体订阅者(Subscriber):执行一些操作回应发布者的通知。所有具体订阅者都实现了同样的接口,因此发布者不需要与具体的类耦合。
- 订阅者需要一些上下文信息来正确的更新。所以,发布者要将一些上下文数据,作为 Notify() 的参数传递给订阅者。发布者也可将自身作为参数传递,以便订阅者直接获取数据。
- 客户端(Client):分别创建发布者和订阅者,然后为订阅者注册发布者的更新。
🏦观察者 -- 适用情况
1
- 当一个对象状态的改变,需要改变其他对象;或实际对象是事先未知的或动态变化时,可以用观察者模式
- 比如说,使用图形界面时,用户创建了自定义按钮类,并允许客户端在按钮中注入自定义代码,这样当用户按下按钮,就会触发这些代码
观察者模式允许任何实现了订阅者接口的对象,订阅发布者对象的事件通知
你可以在按钮中添加订阅机制,允许客户端通过自定义订阅类注入自定义代码
2
当应用中的一些对象必须观察其他对象时,可使用观察者模式
但只能在有限时间或特定情况下使用
订阅列表是动态的,因此订阅者可以随时加入或离开该列表
🐟实现方式
- 根据业务逻辑,将 观察者模式 拆分为两部分:
a. 独立于其他代码的核心功能(发布者)
b. 其他代码(一组订阅者类)- 声明订阅者接口:该接口至少应声明一个 update() 方法
- 声明发布者接口:并定义一些接口用于在列表中添加 / 删除订阅对象(注意!发布者必须且只能通过订阅者接口和订阅对象进行交互)
- 确定存放实际订阅列表的位置,并实现订阅方法:
a. 因为所有类型的发布者都一样,那么,列表应该放置在直接扩展自发布者接口(IPublisher)的具体发布者(Publisher)中。具体发布者会扩展 接口类,从而继承所有的订阅行为
b. 但是,如果你需要在现有的类层次结构中应用观察者模式,最好使用组合的方式:将订阅逻辑放入一个独立的对象,然后让所有实际订阅者使用该对象- 创建具体发布者类(Publisher):每次发生重要事件,发布者都要通过所有订阅者
- 具体订阅者类(Subscriber)中实现通知更新的方法:
a. 订阅者需要一些与事件相关的上下文数据,这些数据(message_from_publisher)作为通知方法的参数来传递
b. 另一种选择:订阅者从通知中获取 所有数据,此时发布者通过更新方法,将自身传递出去
c. 还有一种不太灵活的方法:通过构造函数,将发布者与订阅者永久联系起来- 客户端生成所需的全部订阅者,并到相应发布者完成注册工作
😖优缺点
++优点++
1,开闭原则
你不用修改任何发布者代码就能引用新的订阅者类(良好的扩展性)
(如果是发布者接口,就能轻松引入发布者类)
2,可以在运行时建立对象之间的联系(稳定的信息更新传递机制)
3,耦合双方依赖于抽象,不需要了解具体
++缺点++
1,订阅者(也称观察者)的通知顺序是随机 的(所以在设计观察者模式时,不要依赖于订阅者的通知顺序,这回让代码变得脆弱)
a. 订阅者运行时动态注册,那么注册顺序可能会变b. 部分订阅者的注销,会影响剩下订阅者的通知顺序
c. 发布者可以以任何顺序通知定于这
2,某个订阅者出现卡顿,可能会影响整个进程,一般采用异步机制处理,同时注意线程安全
3,订阅者过多时,挨个通知每个订阅者,耗时较长
🔚与其他模式的联系
责任链模式 ,命令模式 ,中介者模式 ,观察者模式---- 是用于处理请求发送者和接收者间的不同连接方式
- 责任链:按照顺序将请求动态传递给一系列的潜在接收者,直至其中一名接收者对请求进行处理
- 命令:在发送者和接收者之间建立单向连接
- 中介者:清除发送者和接收者之间的直接连接,强制它们通过一个中介对象进行间接沟通
- 观察者:允许接收者动态的订阅或取消接收要求
中介者 和 观察者的区别往往很难记住,具体的:
- 中介者的主要目标是,消除一系列系统组间之间的相互依赖。这些组间依赖于同一个中介者对象
- 观察者的目标是,在对象间建立动态的单向连接,使得部分对象也可作为其他对象的附属发挥作用
- 有一种流行的中介者模式的实现,依赖于观察者模式:
a. 中介者对象担当发布者的角色,其他组件作为订阅者,可以订阅中介者的事件或取消订阅
b. 当中介者用这种方式实现时,它看起来和观察者一样- 你可能会困惑,此时可以采用其他方式来实现中介者。比如,永久的将所有组件链接到同一个中介者对象。这种实现方式和观察者长的很像,但它仍然是中介者模式
- 假设有一个程序,它所有的组件都变成了发布者,它们之间可以相互建立动态连接。这样程序就没有了中心化的中介者对象,只有一些分布式的观察者
++参考文章👇++
C++ 观察者模式讲解和代码示例 (refactoringguru.cn)
设计模式之观察者模式(C++)-阿里云开发者社区 (aliyun.com)






