C++设计模式之观察者模式(observer)(行为型)

一 定义

观察者模式:定义了一种一对多的依赖关系,让多个观察者对象同时监听某一主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使他们能够自动更新自己。

观察者模式又叫做发布-订阅(Publish-Subscribe)模式、模型-视图(Model-View)模式、源-监听器(Source-Listener)模式。

Observer模式应该可以说是应用最多、影响最广的模式之一,因为Observer的一个实例Model/View/Control(MVC)结构在系统开发架构设计中有着很重要的地位和意义,MVC实现了业务逻辑和表示层的解耦。

 

图2

观测模式允许一个对象关注其他对象的状态,并且,观测模式还为被观测者提供了一种观测结构,或者说是一个主体和一个客体。主体,也就是被观测者,可以用来联系所有的观测它的观测者。客体,也就是观测者,用来接受主体状态的改变 观测就是一个可被观测的类(也就是主题)与一个或多个观测它的类(也就是客体)的协作。不论什么时候,当被观测对象的状态变化时,所有注册过的观测者都会得到通知。
观测模式将被观测者(主体)从观测者(客体)种分离出来。这样,每个观测者都可以根据主体的变化分别采取各自的操作。(观测模式和Publish/Subscribe模式一样,也是一种有效描述对象间相互作用的模式。)观测模式灵活而且功能强大。对于被观测者来说,那些查询哪些类需要自己的状态信息和每次使用那些状态信息的额外资源开销已经不存在了。另外,一个观测者可以在任何合适的时候进行注册和取消注册。你也可以定义多个具体的观测类,以便在实际应用中执行不同的操作。
将一个系统分割成一系列相互协作的类有一个常见的副作用:需要维护相关对象间的一致性。我们不希望为了维持一致性而使各类紧密耦合,因为这样降低了它们的可重用性。

观察者模式所做的工作其实就是在解除耦合,让耦合的双方都依赖于抽象,而不是依赖于具体。从而使得各自的变化都不会影响另一边的变化。

二 ULM图

角色:

● Subject(目标):目标又称为主题,它是指被观察的对象。在目标中定义了一个观察者集合,一个观察目标可以接受任意数量的观察者来观察,它提供一系列方法来增加和删除观察者对象,同时它定义了通知方法notify()。目标类可以是接口,也可以是抽象类或具体类。 

● ConcreteSubject(具体目标):具体目标是目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;同时它还实现了在目标类中定义的抽象业务逻辑方法(如果有的话)。如果无须扩展目标类,则具体目标类可以省略。 

● Observer(观察者):观察者将对观察目标的改变做出反应,观察者一般定义为接口,该接口声明了更新数据的方法collect(),因此又称为抽象观察者。 

● ConcreteObserver(具体观察者):在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致;它实现了在抽象观察者Observer中定义的collect()方法。

一共四个类,两个接口,两个接口实现类,被观察者方法参数引用的是观察者对象。

观察者只定义一个自己的行为。具体观察者重写观察者的行为后还提供了构造方法为客户端调用。具体被观察者,这个类有些复杂,简单的说就是把外界消息通过该类的方法调用 notifyObserver() 方法给提前准备好的Observer (观察者) 接口中的方法去发送。

模式优点:

  1. 观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色。
  2. 在观察目标和观察者之间建立一个抽象的耦合 :一个目标所知道的仅仅是它有一系列观察者 , 每个都符合抽象的Observer类的简单接口。目标不知道任何一个观察者属于哪一个具体的类。这样目标和观察者之间的耦合是抽象的和最小的。因为目标和观察者不是紧密耦合的, 它们可以属于一个系统中的不同抽象层次。一个处于较低层次的目标对象可与一个处于较高层次的观察者通信并通知它 , 这样就保持了系统层次的完整。如果目标和观察者混在一块 , 那么得到的对象要么横贯两个层次 (违反了层次性), 要么必须放在这两层的某一层中(这可能会损害层次抽象)。
  3. 支持广播通信 :不像通常的请求, 目标发送的通知不需指定它的接收者。通知被自动广播给所有已向该目标对象登记的有关对象。目标对象并不关心到底有多少对象对自己感兴趣 ;它唯一的责任就是通知它的各观察者。这给了你在任何时刻增加和删除观察者的自由。处理还是忽略一个通知取决于观察者。
  4. 观察者模式符合“开闭原则”的要求。

模式缺点:

  1. 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  2. 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  3. 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
  4. 意外的更新 因为一个观察者并不知道其它观察者的存在 , 它可能对改变目标的最终代价一无所知。在目标上一个看似无害的的操作可能会引起一系列对观察者以及依赖于这些观察者的那些对象的更新。此外 , 如果依赖准则的定义或维护不当,常常会引起错误的更新 , 这种错误通常很难捕捉。

适用场景:

  (1)当一个对象的改变需要同时改变其他对象的时候;

(2)而且不知道具体有多少对象有待改变时,应该考虑使用观察者模式;

(3)当一个抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使他们各自独立地改变和复用。

三 实例

1. 博客

当博主发表新文章的时候,即博主状态发生了改变,那些订阅的读者就会收到通知,然后进行相应的动作,比如去看文章,或者收藏起来。博主与读者之间存在种一对多的依赖关系。下面给出相应的UML图设计。 

 可以看到博客类中有一个观察者链表(即订阅者),当博客的状态发生变化时,通过Notify成员函数通知所有的观察者,告诉他们博客的状态更新了。而观察者通过Update成员函数获取博客的状态信息。

#include <iostream>
#include <string>
#include <list>
#include <memory>

//observer
class Observer
{
public:
	Observer() = default;
	virtual ~Observer() = default;
	virtual void Update() = 0;
};

//subject
class Blog
{
public:
	Blog() = default;
	virtual ~Blog() = default;
	void Attach(std::shared_ptr<Observer> observer)
	{ 
		observers.push_back(observer); 
	}
	void Remove(std::shared_ptr<Observer> observer)
	{ 
		observers.remove(observer); 
	}
	void Notify()
	{
		for (auto iter = observers.begin(); iter != observers.end(); iter++)
		{
			(*iter)->Update();
		}
	}
	virtual void SetStatus(const std::string& status)
	{ 
		this->status = status;
	}
	virtual std::string GetStatus() 
	{ 
		return status; 
	}
private:
	std::list<std::shared_ptr<Observer>> observers; //观察者链表
protected:
	std::string status; //状态
};

//concrete subject
class BlogCSDN : public Blog
{
public:
	BlogCSDN(const std::string& bloggerName) : bloggerName{ bloggerName }{}
	~BlogCSDN() = default;

	void SetStatus(const std::string& state) 
	{ 
		status = "CSDN通知 : " + bloggerName + state;
	} 
	std::string GetStatus() 
	{
		return status; 
	}
private:
	std::string bloggerName;
};

//concreteObserver
class ObserverBlog : public Observer
{
private:
	std::string observerName;
	std::shared_ptr<Blog> blog;   //观察的博客,当然以链表形式更好,就可以观察多个博客
public:
	ObserverBlog(const std::string& observerName, std::shared_ptr<Blog> blog)
		: observerName{ observerName }
		, blog{ blog }
	{}
	~ObserverBlog() = default;
	void Update()
	{
		std::string status = blog->GetStatus();//收到更新通知后,阅读关注博客更新的内容等操作
		std::cout << observerName << status << std::endl;
	}
};

int main()
{
	auto blog = std::make_shared<BlogCSDN>("秋云");//创建一个CSDN博客的博主---秋云
	auto observer = std::make_shared<ObserverBlog>("子之", blog);//子之关注了秋云的博客
	blog->Attach(observer);//把子之放到了博主秋云的粉丝集合中
	blog->SetStatus("发表设计模式——观察者模式");//SetStatus是博主更新了内容。
	blog->Notify();

	return 0;
}

 

2. 猫狗老鼠

猫"喵喵"叫了一声,狗也跟着"汪汪"叫,老鼠听到猫叫声后跑了。

#include <iostream>
#include <vector>

//Observer
class MyObserver 
{
public:
	virtual void response() = 0;
};

//Subject
class MySubject 
{
public:

	void attach(MyObserver* observer) 
	{
		observers.push_back(observer);
	}

	void detach(MyObserver* observer) 
	{
		for (auto it = observers.begin(); it != observers.end(); it++) 
		{
			if (*it == observer) {
				observers.erase(it);
				break;
			}
		}
	}
protected:
	std::vector<MyObserver*> observers;
};

//concrete subject
class Cat : public MySubject 
{
public:
	void cry() 
	{
		std::cout << "猫喵喵叫!" << std::endl;
		for (auto obs : observers) 
		{
			obs->response();
		}
	}
};

//concrete observer
class Mouse : public MyObserver 
{
public:
	void response() 
	{
		std::cout << "老鼠逃跑!" << std::endl;
	}
};

//concrete observer
class Dog : public MyObserver 
{
public:
	void response() 
	{
		std::cout << "狗汪汪叫!" << std::endl;
	}
};


int main(void) 
{
	Cat cat;
	Mouse m1, m2;
	Dog dog;

	cat.attach(&m1);
	cat.attach(&m2);
	cat.attach(&dog);

	cat.cry();

	return 0;
}

运行结果如下:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章