一天一个设计模式——观察者模式

401 阅读3分钟

参考:refactoringguru.cn/design-patt…

意图

观察者模式是一种行为设计模式, 允许你定义一种订阅机制, 可在对象事件发生时通知多个 “观察” 该对象的其他对象。

问题

UserApp Store为例。假设App Store即将上线一款热门游戏,且某些User对该游戏何时上线非常关心。那么有两种做法:

  1. User每时每刻查看该游戏是否上线。但是这将浪费User大量的时间,且大部分时候都无功而返。
  2. App Store在该游戏上线时,向所有的User发送通知邮件。但是这会打扰到对该游戏不感兴趣的User

解决方案

我们将拥有一些值得关注的状态的对象称为发布者, 它要将自身的状态改变通知给其他对象。 而这些关注发布者状态变化的其他对象称为订阅者 。

发布者维护着一个用于存储订阅者对象引用的列表成员变量和一些添加或删除订阅者的方法。每当发布者状态发生变化时,便遍历订阅者列表,并向订阅者们发送通知。

所有订阅者都必须实现相同的接口, 发布者仅通过该接口与订阅者交互。 接口中必须声明通知方法及其参数, 这样发布者在发出通知时还能传递一些上下文数据。

利用观察者模式可以解决问题。将App Store定义为发布者,而对即将发布的游戏感兴趣的User定义为订阅者即可。

观察者模式结构

观察者设计模式的结构

  1. Publisher:发布者,会向其他对象发送值得关注的事件。事件会在发布者自身状态改变或执行特定行为后发生。
  2. Subscriber:订阅者接口,声明了通知接口。在绝大多数情况下, 该接口仅包含一个 update更新方法。
  3. Concrete Subscriber:具体订阅者,可以执行一些操作来回应发布者的通知。 所有具体订阅者类都实现了同样的接口, 因此发布者仅通过接口与具体订阅者进行交互,降低了耦合度。
  4. 客户端 (Client) 会分别创建发布者和订阅者对象, 然后为订阅者注册发布者更新。

代码实现

#include<iostream>
#include<string>
#include<list>	
using namespace std;

//所有的具体订阅者都应该实现同一个接口
class IObserver {
public:
	virtual ~IObserver()
	{

	}
	virtual void Update(const string &message_from_subject) = 0;
};

//发布者接口
class ISubject {
public:
	virtual ~ISubject()
	{

	}
	virtual void Attach(IObserver *observer) = 0;
	virtual void Detach(IObserver *observer) = 0;
	virtual void Notify() = 0;
};

class Subject : public ISubject {
private:
	//发布者维护一个用于存储订阅者对象引用的列表成员变量;
	list<IObserver*> list_observer_;
	string message_;
public:
	virtual ~Subject()
	{
		std::cout << "Goodbye, I was the Subject.\n";
	}
	virtual void Attach(IObserver *observer) {
		list_observer_.push_back(observer);
	}
	virtual void Detach(IObserver *observer) {
		list_observer_.remove(observer);
	}
	virtual void Notify() {
		list<IObserver*>::iterator it = list_observer_.begin();
		HowManyObserver();
		while (it!=list_observer_.end())
		{
			(*it)->Update(message_);
			++it;
		}
	}
	//每当状态发生变化时,便可以使用通知方法向订阅者发布通知
	void CreateMessage(string message = "Empty") {
		this->message_ = message;
		Notify();
	}
	void HowManyObserver() {
		cout << "There are " << list_observer_.size() << " observers in the list" << endl;
	}

	void SomeBussinessLogic() {
		this->message_ = "change message message";
		Notify();
		cout << "I'm about to do some thing importnat" << endl;
	}
};

//具体订阅者
class Observer :public IObserver {
private:
	string message_from_subject_;
	Subject &subject_;
	static int static_number_;
	int number_;
public:
	//通过构造函数将发布者与订阅者绑定
	Observer(Subject &subject) : subject_(subject) {
		this->subject_.Attach(this);
		cout << "Hi, I'm the Observer \"" << ++Observer::static_number_ << "\".\n";
		this->number_ = Observer::static_number_;
	}
	virtual ~Observer()
	{
		cout<<"Goodbye, I was the Observer \""<<this->number_<<"\"."<<endl;
	}

	virtual void Update(const string &message_from_subject) {
		message_from_subject_ = message_from_subject;
		PrintInfo();
	}
	void RemoveMeFromTheList() {
		subject_.Detach(this);
		cout << "Observer \"" << number_ << "\" removed from the list.\n";
	}
	void PrintInfo() {
		cout << "Observer \"" << this->number_ << "\": a new message is available --> " << this->message_from_subject_ << "\n";
	}
};

int Observer::static_number_ = 0;

//客户端必须生成所需的全部订阅者, 并在相应的发布者处完成注册工作。
void ClientCode() {
	Subject *subject = new Subject;
	Observer *observer1 = new Observer(*subject);
	Observer *observer2 = new Observer(*subject);
	Observer *observer3 = new Observer(*subject);
	Observer *observer4;
	Observer *observer5;

	subject->CreateMessage("Hello World! :D");
	observer3->RemoveMeFromTheList();

	subject->CreateMessage("The weather is hot today! :p");
	observer4 = new Observer(*subject);

	observer2->RemoveMeFromTheList();
	observer5 = new Observer(*subject);

	subject->CreateMessage("My new car is great! ;)");
	observer5->RemoveMeFromTheList();

	observer4->RemoveMeFromTheList();
	observer1->RemoveMeFromTheList();

	delete observer5;
	delete observer4;
	delete observer3;
	delete observer2;
	delete observer1;
	delete subject;
}

int main() {
	ClientCode();
	return 0;
}

该模式的优缺点

  1. 优点
    • 符合开闭原则。 你无需修改发布者代码就能引入新的订阅者类 (如果是发布者接口则可轻松引入发布者类)。
    • 你可以在运行时建立对象之间的联系。
  2. 缺点
    • 订阅者的通知顺序是随机的。