目录
一、引言
举个例子:
你关注了一个 UP 主,UP 主更新视频,所有关注他的粉丝都会收到推送通知。
怎么写代码?
二、优化前的代码
cpp
#include <iostream>
#include <vector>
#include <string>
class Fan {
public:
Fan(const std::string& name):_name(name){}
void update(const std::string& msg) {
std::cout << _name << ":" << msg << std::endl;
}
private:
std::string _name;
};
class UPOwner {
public:
void addFan(Fan* f) { _fans.push_back(f); }
void releaseVideo(const std::string& v) {
std::cout << "\nUP更新:" << v << std::endl;
for (auto f : _fans) f->update("看新视频:" + v);
}
private:
std::vector<Fan*> _fans;
};
int main() {
UPOwner up;
Fan f1("张三");
Fan f2("李四");
up.addFan(&f1);
up.addFan(&f2);
up.releaseVideo("C++教程");
return 0;
}
UPOwner类内部维护一个粉丝列表,通过addFan()方法将粉丝添加到列表中,当发布新的视频时,遍历粉丝列表通知所有的粉丝。
毫无疑问,它完成了我们提出的需求。
但是,它只能通知Fan类型,如果要新增一个系统推送类型,那么就需要在UPOwner内部添加一个新的列表来维护新的类型。
这就违反了开闭原则,代码的灵活性和扩展性不高。
三、观察者模式
观察者模式的定义:
观察者模式定义对象间的一对多依赖关系,使得每当一个对象状态发生改变时,其所有依赖者都会收到通知并自动更新。
观察者模式,也叫做发布-订阅模式。多个客户端订阅同一个"体育消息"主题时,当这个主题中有新消息到来后,就会自动广播发送给所有的订阅客户端。
在观察者模式中,有四个角色。
- Observer:观察者的抽象类,定义一个统一的更新接口,比如上面代码Fan类中的update。
- ConcreteObserver:具体观察者,继承了抽象观察者类,实现父类中的update方法。
- Subject:抽象主题类,提供注册、移除、通知观察者的接口,内部维护一个观察者列表,消息到来时,遍历列表统一发送通知。
- ConcreteSubject:具体主题类,也就是继承了抽象主题的类。
看到这里,估计还是一知半解,没关系,我们先看代码,结合优化后的代码来理解。
四、优化后的代码
cpp
#include <iostream>
#include <vector>
#include <string>
// 抽象观察者类
class Observer {
public:
virtual void update(const std::string& msg) = 0;
virtual ~Observer() = default;
};
// 抽象主题类
class Subject {
public:
// 注册接口,向列表中添加观察者
void attach(Observer* observer) {
_observers.push_back(observer);
}
// 移除接口,从列表中移除观察者,消息推送将不再通知它
void detach(Observer* observer) {
for (auto it = _observers.begin(); it != _observers.end(); it++) {
if (*it == observer) {
_observers.erase(it);
break;
}
}
}
// 广播接口,主题收到新的消息后,通过广播发送给所有订阅的观察者
void notify(const std::string& msg) {
for (Observer* obs : _observers) {
obs->update(msg);
}
}
virtual ~Subject() = default;
private:
std::vector<Observer*> _observers;
};
// 具体主题类,主题类型为UP主
class UPOwner : public Subject {
public:
// 发布视频
void releaseVideo(const std::string& video_name) {
_latestVideo = video_name;
std::cout << "\n[UP主] 发布了新视频:" << video_name << std::endl;
// 主动通知所有粉丝
notify("您关注的UP主更新了:" + _latestVideo);
}
private:
std::string _latestVideo;
};
// 具体观察者类,粉丝类
class Fan : public Observer {
public:
Fan(const std::string& name):_fan_name(name){}
// 实现父类中的抽象方法,描述收到推送来的消息后的处理逻辑
virtual void update(const std::string& msg) {
std::cout << "[粉丝 " << _fan_name << "] 收到通知:" << msg << std::endl;
}
private:
std::string _fan_name;
};
int main() {
// 1. 创建主题(UP主)
UPOwner up;
// 2. 创建观察者(粉丝)
Fan fan1("张三");
Fan fan2("李四");
Fan fan3("王五");
// 3. 粉丝关注UP主(注册观察者)
up.attach(&fan1);
up.attach(&fan2);
up.attach(&fan3);
// 4. UP主发布视频,自动通知所有粉丝
up.releaseVideo("C++ 观察者模式精讲");
// 5. 粉丝取消关注
up.detach(&fan2);
std::cout << "\n--- 粉丝李四取消关注了 ---\n" << std::endl;
// 6. UP主再次发布视频
up.releaseVideo("C++ 设计模式实战");
return 0;
}

引入了抽象的观察者类和抽象的主题类。有什么用?
这时,观察者的类型可以不再只是Fan类型,任何类型都可以,只要它继承抽象观察者类,主题类的列表中,就可以把它添加进去。
而且,主题类也可以不只是UP主,任何类型都可以,继承抽象主题类就行。
这样做毫无疑问提高了代码的扩展性。
我们看看它都符合哪些设计原则。
- 开闭原则:新增其他主题类型时,只要这个类型继承抽象主题类,实现发布接口就可以了,Subject类内部不需要修改任何代码。
- 单一职责:观察者只需要实现一个update方法,只负责接收并处理更新。
- 接口隔离:接口隔离说的是,子类不应该依赖它不需要的接口。上面代码中,子类只有一个方法update,而且不是多余的。简单来说,就是谁要用什么,就给它什么,别给多余的。
这里举一个违反接口隔离原则的例子:
cpp
class Animal {
public:
virtual void run() = 0;
virtual void fly() = 0;
};
class Dog : public Animal {
public:
virtual void run() {
//跑......
}
virtual void fly() {
// 飞......
}
};
Dog类继承Animal类,Dog类除了要实现run(跑)方法,还要实现fly(飞)方法,可是,狗是不会飞的!这就违背了接口隔离原则------fly方法不是Dog类想要的。
五、适用场景
主题消息的发布-订阅场景下,就很适合使用观察者模式。
六、结语
欢迎批评指正!
结束!