观察者模式的结构特性


相关源代码(github)


本博客主要阐述观察者模式的代码结构特性以及实现了一份应用范例。
主要从以下方面进行研究:

引用关键代码解释观察者设计模式在该应用场景中的适用性;
引入该设计模式后对系统架构和代码结构带来了哪些好处;
解释其中用到的多态机制,说明模块抽象封装的方法;
分析各个模块的内聚度和模块之间的耦合度;
提供该应用范例完整的源代码包括构建部署的操作过程,其中GitHub中的README.md将说明构建部署的操作过程。

一、观察者模式的适用性

观察者模式是行为模式之一,它的作用是当一个对象的状态发生变化时,能够自动通知其他关联对象,自动刷新对象状态。他提供给关联对象一种同步通信的手段,使某个对象与依赖它的其他对象之间保持状态同步。
  • 适用于:
    定义对象间一种一对多的依赖关系,使得每一个对象改变状态,则所有依赖于他们的对象都会得到通知。
  • 使用场合:

当一个抽象模型有两个方面,其中一个方面依赖于另一个方面,需要将这两个方面分别封装到独立的对象中,彼此独立地改变和复用的时候;
当一个系统中一个对象的改变需要同时改变其他对象内容,但是又不知道待改变的对象到底有多少个的时候;
当一个对象的改变必须通知其他对象做出相应的变化,但是不能确定通知的对象是谁的时候。

  • 使用场景(本例中):
    定义了一种一对多的关系,让多个观察对象(实验室同学)同时监听一个主题对象(门口的滔哥),主题对象状态发生变化时,会通知所有的观察者,使它们能够更新自己。

  • 观察者:

需要获取或同步被观察者消息的对象

  • 通知类:

监测被观察者,并将通知发给观察者

二、观察者模式的优劣

  • 观察者模式的优点:

观察者模式在被观察者和观察者之间建立一个抽象的耦合。被观察者角色所知道的只是一个具体现察者聚集,每一个具体观察者都符合一个抽象观察者的接口。被观察者并不认识任何一个具体观察者,它只知道它们都有一个共同的接口。由于被观察者和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
观察者模式支持广播通信。被观察者会向所有的登记过的观察者发出通知。

  • 观察者模式的缺点:

如果一个被观察者对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察考模式时要特别注意这一点。
虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。

三、观察者模式的多态及模块封装

观察者模式的多态

观察者模式由抽象主题(Subject)角色、具体主题(Concrete Subject)角色组成。通过抽象主题和具体主题可实现多态,当主题未标注时使用默认主题通知,在具体主题时通过多态来进行动态添加通知。

观察者模式的封装
  • 抽象主题(Subject)角色

把所有对观察者对象的引用保存在一个集合中,每个抽象主题角色都可以有任意数量的观察者。抽象主题提供一个接口,可以增加和删除观察者。一般用接口或抽象类来实现抽象主题角色。

  • 抽象观察者(Observer)角色

为具体的观察者定义一个更新接口,在得到主题的通知时更新自己。

  • 具体主题(Concrete Subject)角色

在具体主题内部状态改变时,给所有登记过的观察者发出通知。是抽象主题的子类(或实现)。

  • 具体观察者(Concrete Observer)角色

该角色实现抽象观察者角色所要求的更新接口,以便本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保存一个指向具体主题角色的引用。

具体如下:

四、观察者模式的耦合度分析

在观察者模式中,改变主题或观察者其中一方,并不会影响另一方,因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以自由地改变他们。
松耦合的设计之所以能让我们建立有弹性的OO系统,能够应付变化,是因为对象之间的互相依赖降到了最低。

五、具体实现

一、定义两个内含纯虚函数的基类
1、观察者基类,且内含如下纯虚函数
	更新消息的函数update(接受的内容),接受的内容是被观察者更新的内容。
2、被观察者基类,且内含如下纯虚函数
	(1)添加订阅者的函数,建立订阅者与自身的关系,即将订阅者加入到自身存放订阅者的容器中,后面使用的是set。
	(2)删除订阅者的函数,即从容器中去除指定订阅者。
	(3)更新内容的函数,即更新自身的消息,并逐一调用容器中订阅者的update函数。(精髓所在)

二、创建具体的被观察者和具体的观察者类,实现上述的纯虚函数。

三、主函数中的调用步骤为
	1、创建具体的被观察者的一个实例
	2、创建若干具体观察者的实例
	3、调用被观察者的订阅函数,建立关系
	4、更新被观察者的信息,从而借助这种模式订阅者可以自动获取本次更新的信息

结果展示:

  • 完整代码
#include <iostream>
#include "vector"
#include "string"
using namespace std;

class Secretary;

//玩游戏的小伙伴们(观察者)
class PlayserObserver
{
public:
	PlayserObserver(string name, Secretary *secretary)
	{
		m_name = name;
		m_secretary = secretary;
	}
	void update(string action)
	{
		cout << m_name <<  "观察者收到action:" << action << endl;
	}
private:
	string		m_name;
	Secretary	*m_secretary;
};

//滔哥(主题对象,通知者)
class Secretary
{
public:
	void addObserver(PlayserObserver *o)
	{
		v.push_back(o);
	}
	void Notify(string action)
	{
		for (vector<PlayserObserver *>::iterator it= v.begin(); it!=v.end(); it++ )
		{
			(*it)->update(action);
		}
	}
	void setAction(string action)
	{
		m_action = action;
		Notify(m_action);
	}
private:
	string m_action;
	vector<PlayserObserver *> v;
};

int main()
{
	//subject 被观察者
	Secretary *s1 = new Secretary;
    Secretary *s2 = new Secretary;

	//具体的观察者 被通知对象
	PlayserObserver *po1 = new PlayserObserver("成哥", s1);
	PlayserObserver *po2 = new PlayserObserver("杰哥", s2);
	s1->addObserver(po1);
	s2->addObserver(po2);
	s1->setAction("老师来了");
	s1->setAction("老师走了");

    s2->setAction("老师来了");
    s2->setAction("老师走了");

	return 0;
}