C++设计模式-观察者模式
Observer(观察者):它是一个抽象类或接口,为所有的具体观察者定义一个更新接口,使得在得到主题的通知时更新自己。Subject(主题):它维护了一系列依赖于它的Observer对象,并提供一个接口来允许Observer对象注册自己、注销自己以及通知它们。ConcreteObserver(具体观察者):它实现了Observer接口,存储与Subject的状态自洽的状态。具体观察者根据需要实现S
C++设计模式-观察者模式
一、概念
观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,当一个对象的状态发生改变时,其所有依赖者都会收到通知并自动更新。
观察者模式在现代编程中的重要性不仅仅在于它的实用性,更在于它所体现的思想——松耦合(Loose Coupling)。在这种设计中,对象间的相互作用不是通过紧密绑定的接口实现的,而是通过层次化、解耦的方式,增强了代码的灵活性和可维护性。
松耦合设计允许被观察者(Subject)维护一个观察者(Observer)列表,同时不需要了解这些观察者的具体实现。这种设计使得添加、移除或替换观察者变得简单,而不会影响被观察者或其他观察者的功能。这不仅提高了代码的可维护性,也增强了系统对未来变化的适应能力。
二、应用场景
- 当一个对象的状态变化需要同时更新其他对象时。
三、定义方式
- Observer(观察者):它是一个抽象类或接口,为所有的具体观察者定义一个更新接口,使得在得到主题的通知时更新自己。
- Subject(主题):它维护了一系列依赖于它的Observer对象,并提供一个接口来允许Observer对象注册自己、注销自己以及通知它们。
- ConcreteObserver(具体观察者):它实现了Observer接口,存储与Subject的状态自洽的状态。具体观察者根据需要实现Subject的更新接口,以使得自身状态与主题的状态保持一致。
- ConcreteSubject(具体主题):它实现了Subject接口,将有关状态存入具体观察者对象,并在状态发生改变时向Observer发出通知。
四、实现方式
以下从按定义实现最基础的观察者模式功能,到实现实际应用场景的各种变形
4.1 基础方式
按定义实现最基础的观察者模式功能:
/**
* @brief 定义一个Observer抽象基类
*/
class IObserver {
public:
IObserver() {}
virtual ~IObserver() {}
public:
virtual void Update(int data) = 0;
};
/**
* @brief 定义一个Subject抽象基类
*/
class ISubject {
public:
ISubject() {}
virtual ~ISubject() {}
public:
virtual void Subscribe(std::shared_ptr<IObserver> observer) = 0; // 观察者订阅事件
virtual void Unsubscribe(std::shared_ptr<IObserver> observer) = 0; // 观察者取消事件的订阅
virtual void Notify(int data) = 0; // 通知已订阅指定事件的观察者
public:
std::list<std::weak_ptr<IObserver>> observers_; // 存放所有已订阅的observer
};
/**
* @brief 实现一个具体的观察者
*/
class ConcreteObserver : public IObserver {
public:
ConcreteObserver(const std::string& name) : name_(name) {}
virtual ~ConcreteObserver() override {}
public:
void Update(int data) override {
std::cout << "observer [" << name_ << "] updated -> " << data << std::endl;
}
private:
std::string name_;
};
/**
* @brief 实现一个具体的Subject
*/
class ConcreteSubject : public ISubject {
public:
ConcreteSubject() {}
virtual ~ConcreteSubject() override {}
public:
void Subscribe(std::shared_ptr<IObserver> observer) override {
observers_.push_back(observer);
};
void Unsubscribe(std::shared_ptr<IObserver> observer) override {
observers_.erase(std::remove_if(observers_.begin(), observers_.end(),
[&observer](std::weak_ptr<IObserver> obj) {
std::shared_ptr<IObserver> tmp =
obj.lock();
if (tmp != nullptr) {
return tmp == observer;
} else {
return false;
}
}),
observers_.end());
}
void Notify(int data) override {
for (auto it = observers_.begin(); it != observers_.end(); ++it) {
std::shared_ptr<IObserver> ps = it->lock();
// weak_ptr提升为shared_ptr
// 判断对象是否还存活
if (ps != nullptr) {
ps->Update(data);
} else {
it = observers_.erase(it);
}
}
}
};
// 测试
int main() {
// 构造3个观察者对象
std::shared_ptr<IObserver> observer1(new ConcreteObserver("observer1"));
std::shared_ptr<IObserver> observer2(new ConcreteObserver("observer2"));
std::shared_ptr<IObserver> observer3(new ConcreteObserver("observer3"));
// 构造1个主题对象
ConcreteSubject subject;
// 为观察者订阅事件
subject.Subscribe(observer1);
subject.Subscribe(observer2);
subject.Subscribe(observer3);
// 通知订阅事件的观察者
subject.Notify(10);
// 模拟取消订阅
subject.Unsubscribe(observer1);
// 通知订阅事件的观察者
subject.Notify(20);
return 0;
}
控制台输出:
observer [observer1] updated -> 10
observer [observer2] updated -> 10
observer [observer3] updated -> 10
observer [observer2] updated -> 20
observer [observer3] updated -> 20
注意
:在ISubject 中维护了一个已订阅的observer的list,在list中存放了observer对象的指针,在很多例子中list中直接存放observer的裸指针,在notify通知所有observer时,需要遍历list,调用每个observer的update接口,在多线程环境中,肯定不明确此时observer对象是否还存活,或是已经在其它线程中被析构了。本例这里使用了weak_ptr和shared_ptr替代observer裸指针,解决上述问题,同时还能避免循环引用
从上面的例子中可以看出:Observer是不依赖于Subject的,想要增加一个新的Observer只需要继承IObserver即可,无需修改Subject,这符合开闭原则,也实现了Observer与Subject的解耦。
4.2 改进观察者模式
上面例子中的观察者模式是经典模式,但是存在缺陷:
- 需要继承,继承是强对象关系,只能对特定的观察者才有效,即必须是Observer抽象类的派生类才行;
- 观察者被通知的接口参数不支持变化,导致观察者不能应付接口的变化
为了解决上例观察者模式的缺陷,可使用C++11 做出改进
- 通过被通知接口参数化和std::function 来代替继承;
- 通过可变参数模板和完美转发来消除接口变化产生的影响。
下面以一个具体的场景举例:
- 有一个数据中心,数据中心负责从其他地方获取数据,并对数据进行加工处理
- 有一个图表组件,需要从数据中心拿数据进行可视化展示
- 有一个文本组件,需要从数据中心拿数据进行可视化展示
- 后期可能还有更多的组件,需要从数据中心拿数据…
/**
* @brief 实现一个具体的Subject,模拟一个数据中心
*/
template <typename Func>
class Subject {
public:
static Subject& GetInstance() {
static Subject instance;
return instance;
}
public:
// 注册观察者,右值引用
int Subscribe(Func&& f) { return Assign(f); }
// 注册观察者,左值
int Subscribe(const Func& f) { return Assign(f); }
// 移除观察者
void Unsubscribe(int id) { observers_map_.erase(id); }
// 通知所有观察者
template <typename... Args>
void Notify(Args&&... args) {
for (auto& it : observers_map_) {
auto& func = it.second;
func(std::forward<Args>(args)...);
}
}
private:
template <typename F>
int Assign(F&& f) {
int id = observer_id_++;
observers_map_.emplace(id, std::forward<F>(f));
return id;
}
private:
Subject() = default;
~Subject() = default;
Subject(const Subject&) = delete;
Subject& operator=(const Subject&) = delete;
Subject(Subject&&) = delete;
Subject& operator=(Subject&&) = delete;
int observer_id_ = 0; //观察者对应编号
std::map<int, Func> observers_map_; // 观察者列表
};
/**
* @brief 实现一个具体的观察者,模拟图表展示组件
*/
class ChartView {
public:
ChartView() {}
~ChartView() {}
public:
void Update(int data) {
std::cout << "the chart data has been updated to [" << data << "]" << std::endl;
}
};
/**
* @brief 实现一个具体的观察者,模拟文本展示组件
*/
class TextView {
public:
TextView() {}
~TextView() {}
public:
void Update(std::string key, std::string value) {
std::cout << "the text data has been updated to [" << key << ": " << value << "]" << std::endl;
}
};
// 测试程序
int main(void) {
ChartView cv; // 图表展示组件
// 从数据中心订阅
Subject<std::function<void(int)>>::GetInstance().Subscribe(
std::bind(&ChartView::Update, cv, std::placeholders::_1));
TextView tv; // 文本展示组件
// 从数据中心订阅
Subject<std::function<void(std::string, std::string)>>::GetInstance().Subscribe(std::bind(
&TextView::Update, tv, std::placeholders::_1, std::placeholders::_2));
// 这里模拟一个数据处理中心线程
std::thread([]() {
int cnt = 0;
while (1) {
// 模拟数据获取数据处理过程
std::this_thread::sleep_for(std::chrono::seconds(1));
auto time = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
// 数据处理完成后,通知组件
Subject<std::function<void(int)>>::GetInstance().Notify(cnt++);
Subject<std::function<void(std::string, std::string)>>::GetInstance().Notify("time", std::to_string(time));
}
}).detach();
// 主线程
while (1) {
std::cout << "main run ..." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
}
return 0;
}
控制台输出:
main run ...
the chart data has been updated to [0]
the text data has been updated to [time: 1723446799]
the chart data has been updated to [1]
the text data has been updated to [time: 1723446800]
the chart data has been updated to [2]
the text data has been updated to [time: 1723446801]
the chart data has been updated to [3]
the text data has been updated to [time: 1723446802]
main run ...
the chart data has been updated to [4]
the text data has been updated to [time: 1723446803]
在本例中,将Subject改为单例模式,这样在组件中调用注册接口,在数据中心调用通知接口,完全解耦分离;图表组件和文本组件所需的数据个数和数据类型是不同的,这在基础的观察者模式中是无法实现的,改进后的观察者模式脱离了需要继承的约束,可以实现更加通用的功能,后期扩展更多组件时,不需要修改Subject代码,只需要新增Observer即可。
总结
观察者模式的优点主要包括:
解耦:观察者和被观察的对象是抽象耦合的,即它们之间不直接调用,而是通过消息传递来通知。
灵活性:可以在运行时动态地添加或删除观察者。
复用性:观察者模式可以单独地重用主题和观察者。
缺点
开销:如果观察者非常多,那么更新的效率就会比较低,因为需要遍历所有的观察者,并调用它们的更新方法。
参考:
https://zhuanlan.zhihu.com/p/678950905
https://blog.csdn.net/QIANGWEIYUAN/article/details/88745835
https://download.csdn.net/blog/column/12397328/119132668
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)