一、观察者模式概述
观察者模式是一种对象行为模式,它在软件设计中有着广泛的应用。这种模式定义了一种一对多的依赖关系,其中一个主题对象可被多个观察者对象同时监听。当主题对象的状态发生变化时,它会主动发出通知,使得所有依赖于它的观察者对象都能得到通知并被自动更新。
例如,在图形用户界面编程中,一个数据模型可以作为主题,而多个界面组件可以作为观察者。当数据模型中的数据发生变化时,它会通知所有注册的观察者,这些观察者可以是文本框、下拉菜单、图表等界面组件。这些组件会根据数据的变化自动更新自己的显示内容。
在业务对象之间的交互中,观察者模式也非常有用。比如,一个订单系统中,订单状态的变化可以作为主题,而物流系统、库存系统、客户通知系统等可以作为观察者。当订单状态发生变化时,比如从 "已下单" 变为 "已发货",这些观察者系统会收到通知并进行相应的处理,如更新物流信息、减少库存数量、发送通知给客户等。
观察者模式不仅被广泛应用于软件界面元素之间的交互,在权限管理等方面也有广泛的应用。例如,当用户的权限发生变化时,系统中的各个模块可以作为观察者,根据权限的变化进行相应的调整。
总的来说,观察者模式通过定义一种一对多的依赖关系,实现了主题对象和观察者对象之间的解耦,使得系统更加灵活和可维护。
二、观察者模式的角色
(一)抽象主题
抽象主题(Subject)是被观察的对象的抽象表示,它定义了一系列用于管理观察者的方法。通常,抽象主题会维护一个观察者列表,以便在状态发生变化时通知所有注册的观察者。
例如,在一个新闻发布系统中,抽象主题可以是新闻源,它定义了添加、删除记者(观察者)的方法,以及通知记者有新新闻发布的方法。抽象主题的主要作用是提供一个统一的接口,使得具体主题可以方便地管理观察者,而不需要关心具体的观察者类型。
(二)具体主题
具体主题(ConcreteSubject)是抽象主题的具体实现,它代表实际被观察的对象。具体主题通常会在内部状态发生变化时,调用通知方法通知所有注册的观察者。
以电商平台为例,具体主题可以是商品库存系统。当商品库存数量发生变化时,库存系统会通知所有关注该商品的商家(观察者),以便商家及时调整销售策略。具体主题需要实现抽象主题中定义的方法,并负责维护自身的状态和观察者列表。
(三)抽象观察者
抽象观察者(Observer)定义了观察者的通用接口,即响应通知的更新方法。这个方法会在具体主题状态发生变化时被调用,以便观察者可以根据变化做出相应的反应。
比如在天气预报系统中,抽象观察者可以是各种气象设备,它们都需要实现一个更新方法,以便在接收到天气变化的通知时进行相应的处理,如调整显示数据、发送警报等。
(四)具体观察者
具体观察者(ConcreteObserver)是抽象观察者的具体实现,它代表实际的观察者对象。具体观察者会在接收到具体主题的通知时,自动做出特定的响应。
以股市行情系统为例,具体观察者可以是不同的投资者。当股票价格发生变化(具体主题状态变化)时,投资者会根据自己的投资策略做出相应的操作,如买入、卖出或持有股票。具体观察者需要实现抽象观察者中定义的更新方法,并根据具体的业务需求进行相应的处理。
三、观察者模式的实现方式
(一)创建抽象主题角色
在 C++ 中,可以定义一个抽象主题类Subject,它包含一个观察者列表和一系列管理观察者的方法。以下是一个简单的实现:
cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;
// 定义抽象基类 Subject,表示被观察的主题
class Subject {
public:
// 纯虚函数,用于向主题添加一个观察者
// 参数 observer 是指向 Observer 类型对象的指针,表示要添加的观察者
// 由于该函数没有函数体且被声明为纯虚函数,所以任何继承自 Subject 的具体类都必须实现该函数
virtual void addObserver(Observer* observer) = 0;
// 纯虚函数,用于从主题中移除一个观察者
// 参数 observer 同样是指向 Observer 类型对象的指针,表示要移除的观察者
// 继承自 Subject 的具体类必须实现此函数,以提供移除观察者的具体逻辑
virtual void removeObserver(Observer* observer) = 0;
// 纯虚函数,用于通知所有已注册的观察者
// 当主题状态发生变化或有事件发生时,调用此函数来通知观察者进行相应处理
// 具体的通知方式和时机由继承自 Subject 的具体类来决定,因此该函数也是纯虚函数,需要在派生类中实现
virtual void notifyObservers() = 0;
private:
// 定义一个私有成员变量 observers,用于存储所有已注册的观察者对象指针
// 这里使用 std::vector 容器来管理观察者列表,方便添加、删除和遍历操作
vector<Observer*> observers;
};
这个抽象主题类定义了添加观察者、删除观察者和通知观察者的纯虚函数,具体的实现将在具体主题类中完成。
(二)创建具体主题角色
具体主题类ConcreteSubject继承自抽象主题类Subject,实现了具体的业务逻辑和通知观察者的方法。例如:
cpp
// 定义抽象基类 Observer,它将由具体的观察者类继承并实现其抽象方法
class Observer
{
public:
// 纯虚函数,用于定义当被观察主题状态改变时观察者的更新行为
// 具体的更新逻辑将在派生类中实现
virtual void update() = 0;
};
// 定义具体主题类 ConcreteSubject,它继承自抽象基类 Subject
class ConcreteSubject : public Subject
{
public:
// 实现抽象基类中的添加观察者方法
// 将传入的观察者指针添加到观察者列表中
void addObserver(Observer* observer) override
{
observers.push_back(observer);
}
// 实现抽象基类中的移除观察者方法
// 遍历观察者列表,找到与传入指针相等的观察者并将其从列表中删除
void removeObserver(Observer* observer) override
{
for (auto it = observers.begin(); it!= observers.end(); ++it)
{
if (*it == observer)
{
observers.erase(it);
break;
}
}
}
// 实现抽象基类中的通知观察者方法
// 遍历观察者列表,调用每个观察者的 update 方法,通知它们主题状态已改变
void notifyObservers() override
{
for (auto observer : observers)
{
observer->update();
}
}
// 设置主题的数据,并在数据更新后通知所有观察者
void setData(int newData)
{
data = newData;
notifyObservers();
}
private:
// 存储主题相关的数据
int data;
// 存储所有注册到该主题的观察者指针
vector<Observer*> observers;
};
在这个具体主题类中,实现了添加、删除观察者和通知观察者的方法。当数据发生变化时,通过调用setData方法,会通知所有注册的观察者。
(三)创建抽象观察者
抽象观察者类Observer定义了一个更新方法,具体观察者将实现这个方法来响应主题的状态变化。
cpp
// 定义抽象基类 Observer,用于表示观察者
class Observer
{
public:
// 纯虚函数 update,用于定义当被观察的主题状态发生变化时,观察者需要执行的更新操作
// 具体的更新逻辑将由继承自 Observer 的具体观察者类来实现
// 由于该函数是纯虚函数,所以 Observer 类不能被实例化,只能作为基类被继承
virtual void update() = 0;
};
(四)创建具体观察者
具体观察者类ConcreteObserver实现了抽象观察者类的更新方法。例如:
cpp
// 定义具体观察者类 ConcreteObserver,继承自抽象基类 Observer
class ConcreteObserver : public Observer
{
public:
// 构造函数,用于初始化观察者对象的名称
// 参数 name 是观察者的名称,将其存储在成员变量 observerName 中
ConcreteObserver(const std::string& name) : observerName(name) {}
// 实现抽象基类 Observer 中的纯虚函数 update
// 当被观察的主题状态发生变化时,该函数会被主题调用,用于执行观察者的特定更新操作
// 在这个例子中,它只是简单地输出一条消息,表明该观察者接收到了更新,并显示观察者的名称
void update() override {
std::cout << "Observer " << observerName << " received update." << std::endl;
}
private:
// 存储观察者的名称
string observerName;
};
(五)测试类验证
以下是一个测试类来验证观察者模式的实现:
cpp
int main()
{
// 创建一个具体主题对象 subject
ConcreteSubject subject;
// 创建两个具体观察者对象 observer1 和 observer2,并分别传入不同的名称进行初始化
ConcreteObserver observer1("Observer 1");
ConcreteObserver observer2("Observer 2");
// 将 observer1 和 observer2 添加到 subject 的观察者列表中
subject.addObserver(&observer1);
subject.addObserver(&observer2);
// 调用 subject 的 setData 方法,设置主题的数据,并触发通知观察者的操作
subject.setData(10);
return 0;
}
在业务中,比如在一个问答系统中,可以使用 JDK 封装的方法实现观察者模式。假设问题是抽象主题,回答者是观察者。当问题状态发生变化(如被编辑、有新的回答等)时,通知所有关注这个问题的回答者。具体实现可以类似如下:
1.定义问题类(抽象主题):
cpp
class Question {
public:
virtual void addAnswerer(Answerer* answerer) = 0;
virtual void removeAnswerer(Answerer* answerer) = 0;
virtual void notifyAnswerers() = 0;
private:
std::vector<Answerer*> answerers;
};
2.具体问题类:
cpp
class ConcreteQuestion : public Question {
public:
void addAnswerer(Answerer* answerer) override {
answerers.push_back(answerer);
}
void removeAnswerer(Answerer* answerer) override {
for (auto it = answerers.begin(); it!= answerers.end(); ++it) {
if (*it == answerer) {
answerers.erase(it);
break;
}
}
}
void notifyAnswerers() override {
for (auto answerer : answerers) {
answerer->update();
}
}
void editQuestion() {
// 问题被编辑,通知回答者
notifyAnswerers();
}
};
3.定义回答者类(抽象观察者):
cpp
class Answerer {
public:
virtual void update() = 0;
};
4.具体回答者类:
cpp
class ConcreteAnswerer : public Answerer {
public:
ConcreteAnswerer(const std::string& name) : answererName(name) {}
void update() override {
std::cout << "Answerer " << answererName << " received update about question change." << std::endl;
}
private:
std::string answererName;
};
4.测试:
cpp
int main() {
ConcreteQuestion question;
ConcreteAnswerer answerer1("Answerer 1");
ConcreteAnswerer answerer2("Answerer 2");
question.addAnswerer(&answerer1);
question.addAnswerer(&answerer2);
question.editQuestion();
return 0;
}
四、观察者模式的应用场景
(一)事件驱动编程
在事件驱动编程中,观察者模式是基础。许多框架和库都采用了这种模式,例如 Java Swing 和.NET WinForms 中的事件处理机制。当一个特定的事件发生时,如按钮被点击、鼠标移动等,相关的观察者会收到通知并执行相应的操作。这样可以实现代码的解耦,使得不同的功能模块可以独立开发和维护。
(二)界面组件与数据绑定
在 GUI 编程中,界面组件通常需要与数据源进行绑定和同步更新。观察者模式可以很好地解决这种问题。以一个天气应用为例,当天气数据发生变化时,作为主题的天气数据源会通知作为观察者的界面组件,如温度显示标签、天气图标等,使其自动更新显示。这样可以确保用户界面始终显示最新的天气信息。
(三)消息订阅与发布
在分布式系统或消息队列中,消息的订阅和发布可以使用观察者模式来实现解耦。例如,在一个微服务架构中,不同的服务可以作为观察者订阅特定的消息主题。当有新的消息发布到该主题时,所有订阅的服务都会收到通知并进行相应的处理。这样可以实现服务之间的异步通信和松耦合。
(四)属性绑定
在一些富客户端应用程序中,属性之间的绑定可以使用观察者模式来实现。例如 JavaFX 中的属性绑定机制。当一个属性的值发生变化时,与之绑定的其他属性会自动更新。这样可以简化代码,提高开发效率。
(五)状态管理
在复杂的状态管理场景中,观察者模式可以帮助管理状态变化的订阅和通知。例如 Redux 和 Vuex 等状态管理库。当应用的状态发生变化时,相关的组件会收到通知并更新自己的显示。这样可以确保用户界面始终与应用的状态保持一致。
(六)触发器和回调
在系统中某个事件发生时,需要触发相应的回调函数。观察者模式可以方便地实现这种机制。例如,在一个文件上传系统中,当文件上传完成时,可以触发一个回调函数通知用户上传结果。这样可以提高系统的响应性和用户体验。
五、观察者模式的优缺点
(一)优点
- 解耦性良好:观察者模式将观察者和被观察者分离开来,两者之间仅通过抽象的接口进行交互。这使得它们之间的依赖关系变得松散,一个对象的变化不会直接影响到另一个对象,提高了系统的可维护性和可扩展性。例如在一个电商系统中,商品的库存变化是被观察者,而关注该商品的消费者可以作为观察者。当库存发生变化时,系统只需通知消费者,而无需关心消费者具体如何处理这个变化。
- 灵活性高:观察者模式允许在运行时动态地添加和删除观察者,使得系统能够灵活地适应不同的需求。比如在一个新闻发布系统中,新的用户可以随时订阅新闻,而已经订阅的用户也可以随时取消订阅,系统无需进行大规模的修改。
- 建立触发机制:观察者模式提供了一种有效的触发机制,当被观察者的状态发生变化时,能够自动通知所有的观察者。这种机制可以确保相关的对象及时得到更新,提高了系统的响应性。例如在一个股票交易系统中,当股票价格发生变化时,所有关注该股票的投资者都能及时收到通知。
(二)缺点
- 通知耗时:如果一个被观察者对象有很多直接和间接的观察者,那么在被观察者状态发生变化时,通知所有观察者会花费很多时间。以一个大型社交网络平台为例,当一个热门话题出现变化时,可能会有大量的用户作为观察者需要被通知,这可能会导致系统性能下降。
- 循环依赖风险:如果观察者和观察目标之间存在循环依赖关系,那么可能会导致系统崩溃。例如,在一个复杂的软件系统中,如果两个模块相互观察对方的状态变化,并且在某些情况下形成了循环调用,就可能引发严重的问题。
- 缺乏变化细节:观察者模式只通知观察者目标对象发生了变化,但没有提供相应的机制让观察者知道目标对象是怎么发生变化的。这在一些需要详细了解变化过程的场景下可能会带来不便。比如在一个数据分析系统中,观察者可能只知道数据发生了变化,但不知道具体是哪些数据发生了变化以及变化的原因。