【声明】本题目来源于卡码网(题目页面 (kamacoder.com))
【提示:如果不想看文字介绍,可以直接跳转到C++编码部分】
【设计模式大纲】
前面的文章介绍了创建型模式和结构型模式,今天开始介绍行为型模式。
【简介】什么是观察者模式(第13种模式)
观察者模式(发布-订阅模式)属于⾏为型模式,定义了⼀种⼀对多的依赖关系,让多个观察者对象同时监听⼀个主题对象,当主题对象的状态发⽣变化时,所有依赖于它的观察者都得到通知并被⾃动更新。
观察者模式依赖两个模块:
- Subject (主题):也就是被观察的对象,它可以维护⼀组观察者,当主题本身发⽣改变时就会通知观察者。
- Observer (观察者):观察主题的对象,当“被观察”的主题发⽣变化时,观察者就会得到通知并执⾏相应的处理。
使⽤观察者模式有很多好处,⽐如说观察者模式将主题和观察者之间的关系解耦,主题只需要关注⾃⼰的状态变化,⽽观察者只需要关注在主题状态变化时需要执⾏的操作,两者互不⼲扰,并且由于观察者和主题是相互独⽴的,可以轻松的增加和删除观察者,这样实现的系统更容易扩展和维护。
【基本结构】
观察者模式依赖主题和观察者,但是⼀般有4个组成部分:
- 主题Subject : ⼀般会定义成⼀个接⼝,提供⽅法⽤于注册、删除和通知观察者,通常也包含⼀个状态,当状态发⽣改变时,通知所有的观察者。
- 观察者Observer : 观察者也需要实现⼀个接⼝,包含⼀个更新⽅法,在接收主题通知时执⾏对应的操作。
- 具体主题ConcreteSubject : 主题的具体实现, 维护⼀个观察者列表,包含了观察者的注册、删除和通知⽅法。
- 具体观察者ConcreteObserver : 观察者接⼝的具体实现,每个具体观察者都注册到具体主题中,当主题状态变化并通知到具体观察者,具体观察者进⾏处理。
【基本实现】
根据上⾯的类图,我们可以写出观察者模式的基本实现(以Java代码作以说明):
1. 主题接口
// 主题接⼝ (主题)
interface Subject {// 注册观察者void registerObserver(Observer observer);// 移除观察者void removeObserver(Observer observer);// 通知观察者void notifyObservers();
}
2. 观察者接口
// 观察者接⼝ (观察者)
interface Observer {// 更新⽅法void update(String message);
}
3. 具体主题的实现
// 具体主题实现
class ConcreteSubject implements Subject {// 观察者列表private List<Observer> observers = new ArrayList<>();// 状态private String state;// 注册观察者@Overridepublic void registerObserver(Observer observer) {observers.add(observer);}// 移除观察者@Overridepublic void removeObserver(Observer observer) {observers.remove(observer);}// 通知观察者@Overridepublic void notifyObservers() {for (Observer observer : observers) {// 观察者根据传递的信息进⾏处理observer.update(state);}}// 更新状态public void setState(String state) {this.state = state;notifyObservers();}
}
4. 具体观察者的实现
// 具体观察者实现
class ConcreteObserver implements Observer {// 更新⽅法@Overridepublic void update(String message) {}
}
【使用场景】
观察者模式特别适⽤于⼀个对象的状态变化会影响到其他对象,并且希望这些对象在状态变化时能够⾃动更新的情况。 ⽐如说在图形⽤户界⾯中,按钮、滑动条等组件的状态变化可能需要通知其他组件更新,这使得观察者模式被⼴泛应⽤于GUI框架,⽐如Java的Swing框架。
此外,观察者模式在前端开发和分布式系统中也有应⽤,⽐较典型的例⼦是前端框架Vue , 当数据发⽣变化时,视图会⾃动更新。⽽在分布式系统中,观察者模式可以⽤于实现节点之间的消息通知机制,节点的状态变化将通知其他相关节点。
【编码部分】
1. 题目描述
小明所在的学校有一个时钟(主题),每到整点时,它就会通知所有的学生(观察者)当前的时间,请你使用观察者模式实现这个时钟通知系统。
注意点:时间从 0 开始,并每隔一个小时更新一次。
2. 输入描述
输入的第一行是一个整数 N(1 ≤ N ≤ 20),表示学生的数量。 接下来的 N 行,每行包含一个字符串,表示学生的姓名。 最后一行是一个整数,表示时钟更新的次数。
3. 输出描述
对于每一次时钟更新,输出每个学生的姓名和当前的时间。
4. C++编码实例
/**
* @version Copyright (c) 2024 NCDC, Servo。 Unpublished - All rights reserved
* @file ObserverMode.hpp
* @brief 观察者模式
* @autor 写代码的小恐龙er
* @date 2024/01/15
*/#include <iostream>
#include <string>
#include <vector>using namespace std;// 前置声明// 观察者接口类(再次声明 接口类都是属于基类 需要后续具体实现类来重载操作)
class Observer;
// 主题接口
class Subject;
// 具体主题的实现 -- 时钟
class Clock;
// 具体的观察者 -- 学生
class Student;// 观察者接口类
class Observer
{
// 接口函数 -- 更新主题的变化
public:Observer(){}virtual void UpdateTime(int hour) = 0;
};// 主题接口
class Subject
{
// 主题对观察者的接口函数
public:Subject(){}// 注册(添加)观察者virtual void AddObserver(Observer *observer) = 0;// 移除观察者virtual void RemoveObserver(Observer *observer) = 0;// 通知所有观察者virtual void NotifyObservers() = 0;
};// 具体主题的实现 -- 时钟
class Clock : public Subject
{
// 成员数据
private:std::vector<Observer *> _observers;int hourTime = 0;// 成员函数
public:// 重载添加函数void AddObserver(Observer *observer) override {this->_observers.push_back(observer);}// 重载移除函数void RemoveObserver(Observer *observer) override{for(unsigned int i = 0; i < _observers.size(); i++){if(this->_observers[i] == observer){delete this->_observers[i];this->_observers[i] = nullptr;}}}// 重载通知函数void NotifyObservers() override{for(Observer *observer : this->_observers){observer->UpdateTime(this->hourTime);}}// 模拟时间的推移void TimeRun(){this->hourTime = (this->hourTime + 1);NotifyObservers();}};// 具体的观察者 -- 学生
class Student : public Observer
{
// 成员数据
private:string _name;// 成员函数
public:// 重载构造函数 -- 利用学生姓名来具体实例化学生类Student(string name){this->_name = name;}// 重载更新时间函数void UpdateTime(int hour) override {std::cout << _name << " " << hour << endl;}
};// 客户端代码
int main()
{// 学生数量int stuNum = 0;// 输入std::cin >> stuNum;// 创建具体主题 -- 时钟类Clock * clocker = new Clock();// 创建学生类 -- 用基类创建Observer * student = nullptr;// 学生姓名string name = "";// 输入int i = 0;for(i = 0; i < stuNum; i++){std::cin >> name;// 创建学生student = new Student(name);// 添加学生clocker->AddObserver(student);}// 时钟更新次数int updateNum = 0;// 输入std::cin >> updateNum;for(i = 0; i < updateNum; i++){// 调用时间推移函数clocker->TimeRun();}// 析构if(student != nullptr){delete student;student = nullptr;}delete clocker;clocker = nullptr;return 0;
}
......
To be continued.