突破编程_C++_设计模式(观察者模式)

1 观察者模式的概念

观察者模式(Observer Pattern)是设计模式中的一种行为模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,它的所有依赖者(观察者)都会收到通知并自动更新。

在 C++ 中,观察者模式通常包含以下几个关键部分:

(1)主题(Subject): 主题是一个包含观察者列表的抽象类或接口。它提供注册、移除和通知观察者的方法。当主题状态发生变化时,它会调用通知方法来更新所有注册的观察者。

(2)具体主题(Concrete Subject): 具体主题实现主题的接口,通常包含具体的状态或数据。当这些状态或数据发生变化时,它会通知观察者。

(3)观察者(Observer): 观察者是一个接口或抽象类,定义了观察者接收到通知时需要执行的操作。

(4)具体观察者(Concrete Observer): 具体观察者实现观察者的接口,并在接收到通知时执行具体的操作。

2 观察者模式的实现步骤

在 C++ 实现观察者模式的实现步骤如下:

(1)定义观察者接口(Observer):

  • 创建一个观察者接口,通常包含一个或多个纯虚函数,用于在主题状态发生变化时被调用。

(2)实现具体观察者(ConcreteObserver):

  • 创建一个或多个具体观察者类,实现观察者接口,并定义当接收到通知时需要执行的具体操作。

(3)定义主题接口(Subject):

  • 创建一个主题接口,包含注册观察者、移除观察者和通知观察者的方法。

(4)实现具体主题(ConcreteSubject):

  • 创建一个具体主题类,实现主题接口,并维护一个观察者列表。
  • 提供注册观察者方法,允许将观察者添加到观察者列表中。
  • 提供移除观察者方法,允许从观察者列表中移除特定的观察者。
  • 实现通知观察者方法,当主题状态发生变化时,遍历观察者列表并调用观察者的更新方法。

(5)在主题中维护观察者列表:

  • 具体主题类内部应该有一个容器(如std::vector、std::list等)来存储所有注册的观察者对象。

(6)实现通知机制:

  • 在具体主题类中,当状态发生变化或某些事件发生时,调用通知观察者方法。
  • 通知方法应该遍历观察者列表,并调用每个观察者的更新方法,传递必要的信息。

(7)注册与移除观察者:

  • 允许客户端代码在运行时注册新的观察者或移除已注册的观察者。
  • 注册操作应将观察者添加到观察者列表中,移除操作应从列表中删除指定的观察者。

(8)客户端使用:

  • 在客户端代码中,创建具体主题和具体观察者的实例。
  • 将具体观察者注册到具体主题中。
  • 当具体主题的状态发生变化时,它将自动通知所有注册的观察者。

通过以上步骤,C++ 中的观察者模式就实现了。它提供了一种松耦合的方式来处理对象之间的依赖关系,使得主题对象可以在不直接依赖具体观察者的情况下,通知它们状态的变化。这增强了代码的可维护性和可扩展性。

如下为样例代码:

cpp 复制代码
#include <iostream>  
#include <vector>  
#include <memory>  
#include <algorithm> 
#include <string> 

// 观察者接口  
class Observer {
public:
	virtual ~Observer() = default;
	virtual void update(const std::string& message) = 0;
};

// 具体观察者  
class ConcreteObserver : public Observer {
public:
	void update(const std::string& message) override {
		std::cout << "ConcreteObserver received: " << message << std::endl;
	}
};

// 主题接口  
class Subject {
public:
	virtual ~Subject() = default;
	virtual void registerObserver(const std::shared_ptr<Observer>& observer) = 0;
	virtual void removeObserver(const std::shared_ptr<Observer>& observer) = 0;
	virtual void notifyObservers(const std::string& message) = 0;
};

// 具体主题  
class ConcreteSubject : public Subject {
public:
	void registerObserver(const std::shared_ptr<Observer>& observer) override {
		observers.push_back(observer);
	}

	void removeObserver(const std::shared_ptr<Observer>& observer) override {
		observers.erase(
			std::remove(observers.begin(), observers.end(), observer),
			observers.end()
		);
	}

	void notifyObservers(const std::string& message) override {
		for (auto& observer : observers) {
			observer->update(message);
		}
	}

private:
	std::vector<std::shared_ptr<Observer>> observers;
};

int main() 
{
	// 使用智能指针管理主题和观察者  
	std::shared_ptr<ConcreteSubject> subject = std::make_shared<ConcreteSubject>();
	std::shared_ptr<Observer> observer1 = std::make_shared<ConcreteObserver>();
	std::shared_ptr<Observer> observer2 = std::make_shared<ConcreteObserver>();

	// 注册观察者  
	subject->registerObserver(observer1);
	subject->registerObserver(observer2);

	// 通知观察者  
	subject->notifyObservers("Hello, Observers!");

	// 移除一个观察者  
	subject->removeObserver(observer1);

	// 再次通知观察者  
	subject->notifyObservers("Hello again, Observers!");

	return 0;
}

上面代码的输出为:

ConcreteObserver received: Hello, Observers!
ConcreteObserver received: Hello, Observers!
ConcreteObserver received: Hello again, Observers!

3 观察者模式的应用场景

C++ 中的观察者模式允许对象之间建立一种一对多的依赖关系,使得当一个对象状态发生改变时,它的所有依赖者(即观察者)都会收到通知并自动更新。这种设计模式在多种应用场景中都能发挥重要作用,以下是一些具体的应用场景:

(1)图形用户界面(GUI)开发:

当用户与GUI进行交互时,如点击按钮或拖动滑块,观察者模式可以用于处理这些事件。例如,按钮的点击事件可以被注册为观察者的多个组件监听,当按钮被点击时,所有监听该事件的组件都会收到通知并执行相应的操作。

(2)实时数据监控:

在需要实时监控数据变化的系统中,如股票交易系统、环境监测系统等,观察者模式可以用于在数据发生变化时通知所有相关的观察者。观察者可以根据接收到的数据更新其状态或执行其他操作。

(3)游戏开发:

在游戏中,角色的状态(如生命值、位置等)可能会频繁变化。通过使用观察者模式,游戏引擎可以在角色状态发生变化时通知所有相关的游戏对象(如界面元素、AI系统等),以便它们能够相应地更新或做出反应。

(4)消息传递和通知系统:

在分布式系统或微服务架构中,观察者模式可以用于实现发布/订阅机制。当某个服务发布消息或事件时,所有订阅了该消息或事件的观察者都会收到通知并进行处理。

(5)网络编程:

在网络编程中,服务器可能会接收到来自客户端的各种请求或消息。通过使用观察者模式,服务器可以将这些请求或消息广播给所有相关的处理器或观察者,以便它们能够进行相应的处理。

(6)多线程编程:

在多线程环境中,线程之间的通信和同步是一个重要问题。观察者模式可以用于实现线程之间的解耦和异步通知。当一个线程的状态或数据发生变化时,它可以通知其他线程进行相应的操作,而无需直接依赖或同步这些线程。

(7)配置文件或数据库变化监听:

当配置文件或数据库中的数据发生变化时,观察者模式可以用于通知相关的应用程序组件进行更新或重新加载配置。

3.1 观察者模式应用于图形用户界面(GUI)开发

下面是一个简化的示例,展示了如何在图形用户界面(GUI)开发中实现观察者模式。

首先,定义观察者接口和具体观察者:

cpp 复制代码
#include <iostream>  
#include <memory>  
#include <vector>  
#include <algorithm> 
#include <string> 
  
// 观察者接口  
class Observer {  
public:  
    virtual ~Observer() = default;  
    virtual void update() = 0;  
};  
  
// 具体观察者 - 处理按钮点击事件的类  
class ButtonClickHandler : public Observer {  
public:  
    void update() override {  
        std::cout << "Button was clicked! Handling the event..." << std::endl;  
        // 执行实际的按钮点击处理逻辑  
    }  
};

接下来,定义主题接口和具体主题:

cpp 复制代码
// 主题接口  
class Subject {
public:
	virtual ~Subject() = default;
	virtual void registerObserver(const std::shared_ptr<Observer>& observer) = 0;
	virtual void removeObserver(const std::shared_ptr<Observer>& observer) = 0;
	virtual void notifyObservers() = 0;
};

// 具体主题 - 代表一个按钮  
class Button : public Subject {
public:
	void registerObserver(const std::shared_ptr<Observer>& observer) override {
		observers.push_back(observer);
	}

	void removeObserver(const std::shared_ptr<Observer>& observer) override {
		observers.erase(
			std::remove(observers.begin(), observers.end(), observer),
			observers.end()
		);
	}

	void notifyObservers() override {
		for (const auto& observer : observers) {
			observer->update();
		}
	}

	// 假设这是按钮被点击时调用的方法  
	void onClick() {
		std::cout << "Button clicked. Notifying observers..." << std::endl;
		notifyObservers();
	}

private:
	std::vector<std::shared_ptr<Observer>> observers;
};

最后,在主函数中使用这些类来模拟 GUI 中的按钮点击事件处理:

cpp 复制代码
int main() 
{
	// 创建具体观察者(按钮点击处理器)  
	std::shared_ptr<Observer> buttonClickHandler = std::make_shared<ButtonClickHandler>();

	// 创建具体主题(按钮)  
	std::shared_ptr<Subject> button = std::make_shared<Button>();

	// 将按钮转换为Button类型以便注册观察者  
	std::shared_ptr<Button> buttonPtr = std::static_pointer_cast<Button>(button);

	// 注册观察者到主题(将按钮点击处理器注册到按钮)  
	buttonPtr->registerObserver(buttonClickHandler);

	// 模拟按钮被点击的事件  
	buttonPtr->onClick();

	// 移除观察者(如果需要的话)  
	// buttonPtr->removeObserver(buttonClickHandler);  

	return 0;
}

上面代码的输出为:

Button clicked. Notifying observers...
Button was clicked! Handling the event...

上面代码使用了 std::shared_ptr 来管理观察者和主题的生命周期。当 std::shared_ptr 的引用计数变为0时,对应的对象会被自动删除。但是,请注意,这种方法可能会导致循环引用问题,如果主题和观察者相互持有对方的 std::shared_ptr,则它们将永远不会被删除。为了避免这种情况,通常建议仅在必要时使用 std::shared_ptr,并在可能的情况下使用原始指针、裸指针或 std::weak_ptr。

3.2 观察者模式应用于实时数据监控

以下是一个使用观察者模式实现实时数据监控的示例:

首先,定义观察者接口和主题接口:

cpp 复制代码
#include <iostream>  
#include <memory>  
#include <vector>  
#include <mutex>  
  
// 观察者接口  
class Observer {  
public:  
    virtual ~Observer() = default;  
    virtual void update(const double& data) = 0;  
};  
  
// 主题接口  
class Subject {  
public:  
    virtual ~Subject() = default;  
    virtual void attach(const std::shared_ptr<Observer>& observer) = 0;  
    virtual void detach(const std::shared_ptr<Observer>& observer) = 0;  
    virtual void notify(const double& data) = 0;  
};

接着,实现具体的观察者和主题类:

cpp 复制代码
// 具体观察者类  
class RealtimeDataObserver : public Observer {
public:
	void update(const double& data) override {
		std::cout << "Observer received new data: " << data << std::endl;
		// 处理实时数据的逻辑  
	}
};

// 具体主题类  
class RealtimeDataSubject : public Subject {
public:
	void attach(const std::shared_ptr<Observer>& observer) override {
		std::lock_guard<std::mutex> lock(mtx);
		observers.push_back(observer);
	}

	void detach(const std::shared_ptr<Observer>& observer) override {
		std::lock_guard<std::mutex> lock(mtx);
		observers.erase(
			std::remove(observers.begin(), observers.end(), observer),
			observers.end()
		);
	}

	void notify(const double& data) override {
		std::lock_guard<std::mutex> lock(mtx);
		for (const auto& observer : observers) {
			observer->update(data);
		}
	}

private:
	std::vector<std::shared_ptr<Observer>> observers;
	mutable std::mutex mtx;
};

在上面的代码中,RealtimeDataSubject 维护了一个观察者列表,并在数据更新时调用所有观察者的 update 方法。使用 std::mutex 确保在多线程环境中对观察者列表的修改是线程安全的。

最后,在主函数中使用这些类来模拟实时数据监控:

cpp 复制代码
int main() 
{
	// 创建主题对象  
	std::shared_ptr<Subject> subject = std::make_shared<RealtimeDataSubject>();

	// 创建观察者对象  
	std::shared_ptr<Observer> observer1 = std::make_shared<RealtimeDataObserver>();
	std::shared_ptr<Observer> observer2 = std::make_shared<RealtimeDataObserver>();

	// 将观察者附加到主题上  
	subject->attach(observer1);
	subject->attach(observer2);

	// 模拟实时数据更新  
	for (int i = 0; i < 5; ++i) {
		double newData = static_cast<double>(i); // 假设的实时数据  
		subject->notify(newData);
		// 可以在这里添加一些延迟以模拟实时数据更新的间隔  
	}

	// 移除某个观察者(如果需要)  
	// subject->detach(observer1);  

	return 0;
}

上面代码的输出为:

Observer received new data: 0
Observer received new data: 0
Observer received new data: 1
Observer received new data: 1
Observer received new data: 2
Observer received new data: 2
Observer received new data: 3
Observer received new data: 3
Observer received new data: 4
Observer received new data: 4

在这个例子中,创建了一个 RealtimeDataSubjec t对象和两个 RealtimeDataObserver 对象。当实时数据更新时,通过调用 notify 方法来通知所有观察者。此外,由于实时数据监控可能涉及多线程,所以使用了 std::mutex 来确保线程安全。在实际应用中,还应该考虑更复杂的并发控制策略,比如使用读写锁(std::shared_mutex)来优化性能,如果读操作远多于写操作的话。

4 观察者模式的优点与缺点

C++ 观察者模式的优点主要包括:

(1)解耦: 观察者模式降低了主题和观察者之间的耦合度。主题和观察者可以独立地改变和复用,只要他们遵守观察者模式的接口,就能无缝地集成在一起。

(2)灵活性: 可以动态地增加和删除观察者。这使得程序能够在运行时根据需求调整通知机制。

(3)支持广播通信: 主题可以通知多个观察者,无需知道它们的具体数量或类型。这使得实现一对多的通信变得非常简单。

(4)遵循开闭原则: 对扩展开放,对修改封闭。可以添加新的观察者类,而无需修改已有的主题类或观察者类。

然而,C++ 观察者模式也存在一些缺点:

(1)性能问题: 如果观察者数量非常多,或者通知操作非常耗时,那么每次主题状态变化时,都可能导致大量的通知操作,从而影响性能。

(2)依赖管理: 如果观察者之间也存在依赖关系,那么可能会导致复杂的依赖网络,使得代码难以理解和维护。

(3)内存泄漏风险: 如果使用 std::shared_ptr 而没有正确管理生命周期,可能会导致循环引用和内存泄漏。尽管可以通过 std::weak_ptr 来避免循环引用,但这会增加实现的复杂性。

(4)可能导致过度通知: 在某些情况下,可能不需要每次主题状态变化都通知所有观察者。过度的通知可能会浪费计算资源,并可能导致不必要的副作用。

(5)接口标准化: 观察者模式要求主题和观察者之间通过统一的接口进行通信。这可能导致接口过于复杂或不够灵活,以适应所有可能的观察者和主题类型。

相关推荐
hunandede20 分钟前
av_image_get_buffer_size 和 av_image_fill_arrays
c++
闲人一枚(学习中)1 小时前
设计模式-创建型-抽象工厂模式
设计模式·抽象工厂模式
怀澈1222 小时前
高性能服务器模型之Reactor(单线程版本)
linux·服务器·网络·c++
chnming19872 小时前
STL关联式容器之set
开发语言·c++
威桑2 小时前
MinGW 与 MSVC 的区别与联系及相关特性分析
c++·mingw·msvc
熬夜学编程的小王3 小时前
【C++篇】深度解析 C++ List 容器:底层设计与实现揭秘
开发语言·数据结构·c++·stl·list
yigan_Eins3 小时前
【数论】莫比乌斯函数及其反演
c++·经验分享·算法
Mr.133 小时前
什么是 C++ 中的初始化列表?它的作用是什么?初始化列表和在构造函数体内赋值有什么区别?
开发语言·c++
阿史大杯茶3 小时前
AtCoder Beginner Contest 381(ABCDEF 题)视频讲解
数据结构·c++·算法
C++忠实粉丝3 小时前
计算机网络socket编程(3)_UDP网络编程实现简单聊天室
linux·网络·c++·网络协议·计算机网络·udp