文章目录
1 前言
上一篇文章中对“模板模式”进行概述与总结,分别描述了模板模式含义、特点、优缺点、适用场景、实现过程步骤以及与“策略模式”的比较,并以C++语言实现具体例子。本文描述另一在实际项目中使用较为广泛的设计模式——观察者模式。
相关文章:
设计模式回顾——原型模式
设计模式回顾——观察者模式
设计模式回顾——模板模式
设计模式回顾——策略模式
设计模式回顾——适配器模式
设计模式回顾——建造者模式
设计模式回顾——工厂模式
设计模式回顾——单例模式
设计模式回顾——设计模式概念与基本原则
2 什么是观察者模式
观察者模式(Observer Pattern),指的是定义一种“一对多”的依赖关系,使得多个观察者对象同时监听某一主题对象,在主题对象的状态发生变化时,会通知所有依赖该对象的观察者并更新它们的信息。基于观察者模式的“一对多”的特点,观察者和被观察者类似“读者和邮局”的关系,读者订阅了报刊,报刊由邮局发布;因此又被称为**“发布—订阅模式(Publish-Subscribe Pattern)”**。
2.1 观察者模式组成
观察者模式由抽象观察者(Abstract Observer)、具体观察者(Concrete Observer)、抽象主题(Abstract Subject)、具体主题(Concrete Subject) 四个要素组成。
-
抽象观察者(Abstract Observer),声明一个具体观察者需获取主题发生改变时通知信号的统一更新方法接口
-
具体观察者(Concrete Observer),维护一个指向
Concrete Subject
对象的引用;存储与主题相关的状态,实现抽象观察者中的方法 -
抽象主题(Abstract Subject), 提供维护观察者对象集合的操作方法接口,如增加、删除观察者操作
-
具体主题(Concrete Subject), 将有关状态存入具体的观察者对象;具体主题状态改变时,通知所有已注册的观察者;实现抽象主题中的方法
2.2 观察者模式UML图
根据观察者模式的组成要素,以及它们之间的关系,画出观察者模式的UML图如下。
2.3 观察者模式作用
当一个主题对象(被观察者)改变状态时, 会通知其他所有依赖该主题对象的对象(观察者)。
3 观察者模式优缺点
优点:
-
解耦
被观察者和观察者之间建立一个抽象的耦合,被观察者无需知道具体观察者,所有观察者遵循一个共同抽象接口即可;符合“依赖倒转原则”原则。
-
消息同步
观察者模式实现了“消息广播”功能。
不足:
-
通知消息延迟
被观察者对象如果存在较多的直接或者间接观察者,观察者收到的通知消息将会花费一定的时间。
-
不支持循环依赖
如果观察者与被观察者之间存在循环依赖关系,被观察者会触发它们之间进行循环调用,导致系统崩溃。
-
多线程调用隐患
如果被观察者的通知消息是通过另一线程异步发送的话,系统必须保证该发送过程是以“自恰”的方式进行。
什么是自恰?
自洽,某个理论体系或者数学模型的内在逻辑一致,不含悖论。对于计算机软件,自恰定义为:一个计算机软件,在各个模块,各个函数,各个功能之间对相同问题,没有不同的看法。
-
只知道结果不知道过程
观察者只能知道被观察者发生了变化,但观察者模式未提供相应的机制使得观察者知道被观察者的目标对象是怎么发生变化的。
4 什么地方使用观察者模式
观察者模式的优点决定了其适用的场景,反过来其缺点即是其不适用的场景。观察者模式适用场景:
- 对象关联场景,一个或者多个对象依赖一个对象,一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变的
- 事件触发场景,一个对象触发其他对象发送变化
- 一个对象变化必须通知其它对象,但该对象又不知道这些具体对象,即这些对象是紧密耦合的
- 跨系统消息传输
- 重构历史遗留代码,将符合上述场景的代码以观察者模式重构
具体实例:
- 数据传输,同时支持本地UI显示、网页显示、数据库更新、Debug日志输出等
5 使用观察者模式要注意什么
- 避免观察者与被观察者存在循环依赖关系
- 多线程异步通知
6 观察者模式实现
实例功能:
实现一个“数据传输模型”,数据被分别被GUI对象、数据库存储对象、Debug日志输出对象使用。
整体步骤:
- 抽象观察者
Observer
声明数据更新接口Update
- 抽象主题
Subject
声明观察者操作方法Attach
、Detach
,及通知接口Notify
- 具体主题(被观察者)实现抽象主题方法,并增加一个数据改变方法
Change
- 分别实现
ConcreteObserverGui
、ConcreteObserverDb
、ConcreteObserverDebug
三个观察者及其方法 - 用户
client
调用被观察者对象修改数据,并通知观察者更新数据
实现过程:
- 第一步,声明抽象观察者类
Observe
/* observe.h */
#ifndef _OBSERVER_H_
#define _OBSERVER_H_
#include <stdbool.h>
#include <string>
class Observer
{
public:
virtual void Update(std::string &)= 0;
};
#endif
- 第二步,声明抽象主题类
Subject
/* Subject.h */
#ifndef _SUBJECT_H_
#define _SUBJECT_H_
#include <stdbool.h>
class Subject
{
public:
virtual void Attach(Observer *) = 0;
virtual void Detach(Observer *) = 0;
virtual void Notify() = 0;
};
#endif
- 第三步,声明具体主题类
ConcreteSubject
,继承抽象主题类
/* concrete_subject.h */
#ifndef _CONCRETE_SUBJECT_H_
#define _CONCRETE_SUBJECT_H_
#include <stdbool.h>
#include <list>
#include "observer.h"
#include "subject.h"
class ConcreteSubject : public Subject
{
public:
void Attach(Observer *pObserver);
void Detach(Observer *pObserver);
void Notify();
void ChangeData(std::string);
private:
std::list<Observer *> m_ObserverList;
std::string m_Data;
};
#endif
- 第四步,具体主题类
ConcreteSubject
方法实现
/* concrete_subject.cpp */
#include "concrete_subject.h"
#include "concrete_observer.h"
void ConcreteSubject::Attach(Observer *pObserver)
{
m_ObserverList.push_back(pObserver);
}
void ConcreteSubject::Detach(Observer *pObserver)
{
m_ObserverList.remove(pObserver);
}
void ConcreteSubject::Notify()
{
std::list<Observer *>::iterator it = m_ObserverList.begin();
for(; it != m_ObserverList.end(); it++)
{
(*it)->Update(m_Data); /* 更新所有观察者 */
}
}
void ConcreteSubject::ChangeData(std :: string str)
{
m_Data = str;
}
- 第五步,声明具体观察者类
ConcreteObserverGui
、ConcreteObserverDb
、ConcreteObserverDebug
,继承抽象观察者类
/* concrete_observer.h */
#ifndef _CONCRETE_OBSERVER_H_
#define _CONCRETE_OBSERVER_H_
#include <stdbool.h>
#include "observer.h"
#include "subject.h"
class ConcreteObserverGui:public Observer
{
public:
ConcreteObserverGui(Subject *pSubject);
void Update(std::string &);
private:
Subject *m_pSubjectGui;
};
class ConcreteObserverDb:public Observer
{
public:
ConcreteObserverDb(Subject *pSubject);
void Update(std::string &);
private:
Subject *m_pSubjectDb;
};
class ConcreteObserverDebug:public Observer
{
public:
ConcreteObserverDebug(Subject *pSubject);
void Update(std::string &);
private:
Subject *m_pSubjectDebug;
};
#endif
- 第六步,具体观察者类方法实现
/* concrete_observer.cpp */
#include <iostream>
#include "concrete_observer.h"
using namespace std;
ConcreteObserverGui::ConcreteObserverGui(Subject *pSubject)
{
m_pSubjectGui = pSubject;
}
void ConcreteObserverGui::Update(string &data)
{
cout << "GUI更新数据:"<<data<<endl;
}
ConcreteObserverDb::ConcreteObserverDb(Subject *pSubject)
{
m_pSubjectDb = pSubject;
}
void ConcreteObserverDb::Update(string &data)
{
cout << "数据库更新数据:"<<data<<endl;
}
ConcreteObserverDebug::ConcreteObserverDebug(Subject *pSubject)
{
m_pSubjectDebug = pSubject;
}
void ConcreteObserverDebug::Update(string &data)
{
cout << "Debug日志输出数据:"<<data<<endl;
}
- 第七步,客户调用被观察者对象更新数据,并通知观察者
/* client.cpp */
#include <iostream>
#include "concrete_subject.h"
#include "concrete_observer.h"
int main(int argc, char **arv)
{
/* 创建被观察者 */
ConcreteSubject *pSubject = new ConcreteSubject();
/* 创建观察者 */
Observer *pObserverGui = new ConcreteObserverGui(pSubject);
Observer *pObserverDb = new ConcreteObserverDb(pSubject);
Observer *pObserverDebug = new ConcreteObserverDebug(pSubject);
/* 添加观察者 */
pSubject->Attach(pObserverGui);
pSubject->Attach(pObserverDb);
pSubject->Attach(pObserverDebug);
pSubject->ChangeData("Hello Word");
pSubject->Notify();
std::cout << "删除Debug观察者"<<std::endl;
pSubject->Detach(pObserverDebug);
std::cout << "再次更新数据"<<std::endl;
pSubject->ChangeData("Hello Acuity");
pSubject->Notify();
delete pObserverGui;
delete pObserverDb;
delete pObserverDebug;
delete pSubject;
}
- 最后一步,编写Makefile文件
VERSION =1.00
CC =g++
DEBUG =
CFLAGS =-Wall
SOURCES =$(wildcard *.cpp)
INCLUDES =
LIB_NAMES =
LIB_PATH =
OBJ =$(patsubst %.cpp, %.o, $(SOURCES))
TARGET =client
#links
$(TARGET):$(OBJ)
@mkdir -p output
$(CC) $(OBJ) $(LIB_PATH) $(LIB_NAMES) -o output/$(TARGET)$(VERSION)
@rm -rf $(OBJ)
#compile
%.o: %.c
$(CC) $(INCLUDES) $(DEBUG) -c $(CFLAGS) $< -o $@
.PHONY:clean
clean:
@echo "Remove linked and compiled files......"
rm -rf $(OBJ) $(TARGET) output
执行结果:
acuity@ubuntu:/mnt/hgfs/LSW/STHB/design/observer$ make
g++ -c -o concrtee_observer.o concrtee_observer.cpp
g++ -c -o client.o client.cpp
g++ -c -o concrete_subject.o concrete_subject.cpp
g++ concrtee_observer.o client.o concrete_subject.o -o output/client1.00
acuity@ubuntu:/mnt/hgfs/LSW/STHB/design/observer$ ./output/client1.00
GUI更新数据:Hello Word
数据库更新数据:Hello Word
Debug日志输出数据:Hello Word
删除Debug观察者
再次更新数据
GUI更新数据:Hello Acuity
数据库更新数据:Hello Acuity