设计模式笔记——观察者模式(Observer)
阅读设计模式-可复用面向对象软件的基础一书做的笔记。
意图
建立一个一对多的对象关系,当一发生了变化,多就都得到通知并且进行一个更新的操作。
别名
依赖(dependent),发布-订阅(publish-subscrib)。
动机
当一个系统中有多个相互协作的,类需要维护相关对象之间的一致性时,就会出现许多紧密的耦合,导致了可复用性的降低。
在设计模式-可复用面向对象软件的基础一书中所提到的例子,书中表述了将数据与表格之间独立工作又相互关联的一种关系,当目标数据发生改变时着会通知其他的表格一同发生改变,保持数据与表格展示的状态一致,在这里 数据就是目标(subject),表格着是观察者(Observer)。
观察者模式(Observer)就是描述了如何建立这种关系,一个目标发生改变则可以通过发送通知去告诉该目标的观察者,每个观察者收到通知后都将自身状态于目标状态保持同步。
同时也可以称为发布-订阅(publish-subscrib),目标是发布者,它发出的通知并不需要知道谁是观察者,可以有多个观察者订阅并接受通知。
图片来源设计模式-可复用面向对象软件的基础书中第二百二十页
适用性
以下情况适用观察者模式:
- 一个抽象模型有两个方面,其中一个方面依赖于另一方面。将二者独立封装,使它们可以各自独立地改变和复用。
- 当一个对象发生改变时引发其他对象一起改变,而不知道有多少对象需要改变。
- 一个对象必须通知其他对象,但是它又不能假定其他对象是谁,换句话说就是不希望这些对象紧耦合。
结构
和书中的图一样,但删掉了一些其他的东西。
参与者
- 抽象类
- Subject(目标)
- 目标知道它的观察者,可以有任意多个观察者观察同一目标。
- 提供注册和删除的观察者对象的接口。
- Observer(观察者)
- 当目标发生改变时需要获取通知的对象定义一个更新的接口
- Subject(目标)
- 实现类
- ConcreteSubject(具体目标)
- 保存状态
- 当状态发生改变时发送通知给观察者
- ConcreteObserver(具体观察者)
- 维护一个指向ConcreteSubject对象的引用
- 存储目标有关的状态(数据),这个状态(数据)和观察目标一致
- 实现Observer的更新接口,保证自身和目标状态(数据)一致
- ConcreteSubject(具体目标)
代码实现
代码来源 https://refactoringguru.cn/design-patterns/observer/cpp/example 做了个精简,删掉了一部分打印,打印太多东西了看着花,顺带加上了注释看的明白点。
// 观察者
class Observer
{
public:
virtual ~Observer() {}
// 更新
virtual void update(const std::string& message) = 0;
};
// 目标
class Subject
{
public:
virtual ~Subject() {};
// 注册观察者
virtual void attach(Observer* observer) = 0;
// 删除观察者
virtual void detch(Observer* observer) = 0;
// 通知
virtual void notify() = 0;
};
// 具体目标
class ConcreteSubject : public Subject
{
public:
virtual ~ConcreteSubject()
{
std::cout << "具体目标析构\n";
}
// 增添观察目标的观察者,将其加入观察者列表中
void attach(Observer* observer) override {
m_obseverList.push_back(observer);
std::cout << "观察者+1\n";
}
// 删除观察者
void detch(Observer* observer)override {
m_obseverList.remove(observer);
std::cout << "观察者-1\n";
}
// 发送通知
void notify() override {
for (auto i : m_obseverList) {
// 将改变的数据更新至观察者中
i->update(m_message);
}
}
// 改变数据
void setMessage(std::string message = "无数据") {
this->m_message = message;
// 数据发生改变,发送通知
this->notify();
}
private:
std::list<Observer*> m_obseverList; // 观察者列表,维护多个观察者,以便发送通知
std::string m_message; // 数据,目标要处理的数据,可以是其他数据
};
// 具体观察者
class ConcreteObserver :public Observer {
public:
// 在构造函数中直接传入要观察的目标的引用,使用一个引用来维护具体目标
ConcreteObserver(ConcreteSubject& subject):m_subject(subject) {
// 同时吧当前观察者记录至目标中
this->m_subject.attach(this);
}
~ConcreteObserver()
{
// 删除观察者时也将其从列表中移除
this->m_subject.detch(this);
}
// 更新观察者中的数据,同步与目标一致
void update(const std::string& message)override {
m_message = message;
printMessage();
}
// 打印数据,理解为view层展示数据
void printMessage() {
std::cout << "展示目标当前的数据:" << m_message << "\n";
}
private:
// 要观察的目标引用
ConcreteSubject& m_subject;
// 数据,与目标数据保持一致
std::string m_message;
};
void client()
{
// 目标
std::shared_ptr<ConcreteSubject> subject = std::make_shared<ConcreteSubject>();
// 观察者,不要像我这样使用智能指针,我只是懒得写delete所以用智能指针
std::shared_ptr <ConcreteObserver> observer_1
= std::make_shared<ConcreteObserver>(*(subject.get()));
std::shared_ptr <ConcreteObserver> observer_2
= std::make_shared<ConcreteObserver>(*(subject.get()));
std::shared_ptr <ConcreteObserver> observer_3
= std::make_shared<ConcreteObserver>(*(subject.get()));
// 更新目标数据
subject->setMessage("典,孝");
std::cout << "------------------------------------\n";
// 添加区域,当智能指针离开这个区域时自动析构,测试删除观察者
{
std::shared_ptr <ConcreteObserver> observer_4
= std::make_shared<ConcreteObserver>(*(subject.get()));
subject->setMessage("麻,乐,绷");
}
std::cout << "------------------------------------\n";
subject->setMessage("批,赢");
}
int main() {
client();
return 0;
}