Observer模式(观察者模式)---行为型模式

Observer 模式应该可以说是应用最多、影响最广的模式之一,因为 Observer 的一个实 例 Model/View/Control(MVC)结构在系统开发架构设计中有着很重要的地位和意义,MVC 实现了业务逻辑和表示层的解耦。个人也认为 Observer 模式是软件开发过程中必须要掌握 和使用的模式之一。在 MFC 中,Doc/View(文档视图结构)提供了实现 MVC 的框架结构 (有一个从设计模式(Observer 模式)的角度分析分析 Doc/View 的文章正在进一步的撰写 当中,遗憾的是时间:))。在 Java 阵容中,Struts 则提供和 MFC 中 Doc/View 结构类似的实 现 MVC 的框架。另外 Java 语言本身就提供了 Observer 模式的实现接口,这将在讨论中给 出。

        当然,MVC 只是 Observer 模式的一个实例。Observer 模式要解决的问题为:建立一个 一(Subject)对多(Observer)的依赖关系,并且做到当“一”变化的时候,依赖这个“一” 的多也能够同步改变。最常见的一个例子就是:对同一组数据进行统计分析时候,我们希望 能够提供多种形式的表示(例如以表格进行统计显示、柱状图统计显示、百分比统计显示等)。 这些表示都依赖于同一组数据,我们当然需要当数据改变的时候,所有的统计的显示都能够 同时改变。Observer 模式就是解决了这一个问题。

         这里的目标 Subject 提供依赖于它的观察者 Observer 的注册(Attach)和注销(Detach) 操作,并且提供了使得依赖于它的所有观察者同步的操作(Notify)。观察者 Observer 则提 供一个 Update 操作,注意这里的 Observer 的 Update 操作并不在 Observer 改变了 Subject 目 标状态的时候就对自己进行更新,这个更新操作要延迟到 Subject 对象发出 Notify 通知所有Observer 进行修改(调用 Update)。

一、什么是观察者模式

在许多设计中,经常涉及多个对象都对一个特殊对象中的数据变化感兴趣,而且这多个对象都希望跟踪那个特殊对象中的数据变化,也就是说当对象间存在一对多关系时,在这样的情况下就可以使用观察者模式。当一个对象被修改时,则会自动通知它的依赖对象。

观察者模式是关于多个对象想知道一个对象中数据变化情况的一种成熟的模式。观察者模式中有一个称作“主题”的对象和若干个称作“观察者”的对象,“主题”和“观察者”间是一种一对多的依赖关系,当“主题”的状态发生变化时,所有“观察者”都得到通知。

主要解决:一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

二、观察者模式的结构

 观察者模式的结构中包含四种角色:

(1)主题(Subject):主题是一个接口,该接口规定了具体主题需要实现的方法,比如,添加、删除观察者以及通知观察者更新数据的方法。

(2)观察者(Observer):观察者是一个接口,该接口规定了具体观察者用来更新数据的方法。

(3)具体主题(ConcreteSubject):具体主题是实现主题接口类的一个实例,该实例包含有可以经常发生变化的数据。具体主题需使用一个集合,比如ArrayList,存放观察者的引用,以便数据变化时通知具体观察者。

(4)具体观察者(ConcreteObserver):具体观察者是实现观察者接口类的一个实例。具体观察者包含有可以存放具体主题引用的主题接口变量,以便具体观察者让具体主题将自己的引用添加到具体主题的集合中,使自己成为它的观察者,或让这个具体主题将自己从具体主题的集合中删除,使自己不再是它的观察者。

三、观察者模式的使用场景

(1)当一个对象的数据更新时需要通知其他对象,但这个对象又不希望和被通知的那些对象形成紧耦合。

(2)当一个对象的数据更新时,这个对象需要让其他对象也各自更新自己的数据,但这个对象不知道具体有多少对象需要更新数据。

观察者模式在实际项目的应用中非常常见,比如你到 ATM 机器上取钱,多次输错密码,卡就会被 ATM吞掉,吞卡动作发生的时候,会触发哪些事件呢?第一摄像头连续快拍,第二,通知监控系统,吞卡发生;第三,初始化 ATM 机屏幕,返回最初状态,你不能因为就吞了一张卡,整个 ATM 都不能用了吧,一般前两个动作都是通过观察者模式来完成的。观察者可以实现消息的广播,一个消息可以触发多个事件,这是观察者模式非常重要的功能。

使用观察者模式也有两个重点问题要解决: 

广播链的问题

如果你做过数据库的触发器,你就应该知道有一个触发器链的问题,比如表 A 上写了一个触发器,内容是一个字段更新后更新表 B 的一条数据,而表 B 上也有个触发器,要更新表 C,表 C 也有触发器…,完蛋了,这个数据库基本上就毁掉了!我们的观察者模式也是一样的问题,一个观察者可以有双重身份,即使观察者,也是被观察者,这没什么问题呀,但是链一旦建立,这个逻辑就比较复杂,可维护性非常差,根据经验建议,在一个观察者模式中最多出现一个对象既是观察者也是被观察者,也就是说消息最多转发一次(传递两次),这还是比较好控制的;


异步处理问题

被观察者发生动作了,观察者要做出回应,如果观察者比较多,而且处理时间比较长怎么办?那就用异步呗,异步处理就要考虑线程安全和队列的问题,这个大家有时间看看 Message Queue,就会有更深的了解。

四、观察者模式的优缺点

优点:

  1、具体主题和具体观察者是松耦合关系。由于主题接口仅仅依赖于观察者接口,因此具体主题只是知道它的观察者是实现观察者接口的某个类的实例,但不需要知道具体是哪个类。同样,由于观察者仅仅依赖于主题接口,因此具体观察者只是知道它依赖的主题是实现主题接口的某个类的实例,但不需要知道具体是哪个类。

  2、观察者模式满足“开-闭原则”。主题接口仅仅依赖于观察者接口,这样,就可以让创建具体主题的类也仅仅是依赖于观察者接口,因此,如果增加新的实现观察者接口的类,不必修改创建具体主题的类的代码。。同样,创建具体观察者的类仅仅依赖于主题接口,如果增加新的实现主题接口的类,也不必修改创建具体观察者类的代码。

缺点: 

  1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

  2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。

  3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

五、观察者模式的实现

Observer类---抽象观察者,为所有具体观察者定义一个接口,在得到主题通知时更新自己。

这个接口叫做更新接口,抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包括一个Update方法,这个方法叫做更新方法。

#pragma once

//Subject.h

#include <list>

#include <string>

using namespace std;

typedef string State;

class Observer;

class Subject

{

public:

virtual ~Subject();

virtual void Attach(Observer* obv);

virtual void Detach(Observer* obv);

virtual void Notify();

virtual void SetState(const State& st) = 0;

virtual State GetState() = 0; protected:

Subject();

private:

list<Observer* >* _obvs;

};

class ConcreteSubject:public Subject

{

public:

ConcreteSubject();

~ConcreteSubject();

State GetState();

void SetState(const State& st);

protected:

private:

State _st;

};


//Subject.cpp

#include "stdafx.h"

#include "Subject.h"

#include "Observer.h"

#include <iostream>

#include <list>

using namespace std;

typedef string state;

Subject::Subject()

{

//****在模板的使用之前一定要 new,创建

_obvs = new list<Observer*>;

}

Subject::~Subject()

{

}

void Subject::Attach(Observer* obv)

{

_obvs->push_front(obv);

}

void Subject::Detach(Observer* obv)

{

if (obv != NULL)

_obvs->remove(obv);

}

void Subject::Notify()

{

list<Observer*>::iterator it;

it = _obvs->begin();

for (;it != _obvs->end();it++)

{

//关于模板和 iterator 的用法

(*it)->Update(this);

}

}

ConcreteSubject::ConcreteSubject()

{

_st = '\0';

}

ConcreteSubject::~ConcreteSubject()

{

}

State ConcreteSubject::GetState()

{

return _st;

}

void ConcreteSubject::SetState(const State& st)

{

_st = st;

}


#pragma once

//Observer.h

#include "Subject.h"

#include <string>

using namespace std;

typedef string State;

class Observer

{

public:

virtual ~Observer();

virtual void Update(Subject* sub) = 0;

virtual void PrintInfo() = 0;

protected:

Observer();

State _st;

private:

};

class ConcreteObserverA:public Observer

{

public:

virtual Subject* GetSubject();

ConcreteObserverA(Subject* sub);

virtual ~ConcreteObserverA();

//传入 Subject 作为参数,这样可以让一个 View 属于多个的 Subject。

void Update(Subject* sub);

void PrintInfo();

protected:

private:

Subject* _sub;

};

class ConcreteObserverB:public Observer

{

public:

virtual Subject* GetSubject();

ConcreteObserverB(Subject* sub);

virtual ~ConcreteObserverB();

//传入 Subject 作为参数,这样可以让一个 View 属于多个的 Subject。

void Update(Subject* sub);

void PrintInfo();

protected:

private:

Subject* _sub;

};


//Observer.cpp

#include "stdafx.h"

#include "Observer.h"

#include "Subject.h"

#include <iostream>

#include <string>

using namespace std;

Observer::Observer()

{

_st = '\0';

}

Observer::~Observer()

{

}

ConcreteObserverA::ConcreteObserverA(Subject* sub)

{

_sub = sub;

_sub->Attach(this);

}

ConcreteObserverA::~ConcreteObserverA()

{

_sub->Detach(this);

if (_sub != 0)

{

delete _sub;

}

}

Subject* ConcreteObserverA::GetSubject()

{

return _sub;

}

void ConcreteObserverA::PrintInfo()

{

cout<<"ConcreteObserverA observer.... "<<_sub->GetState()<<endl;

}

void ConcreteObserverA::Update(Subject* sub)

{

_st = sub->GetState();

PrintInfo();

}

ConcreteObserverB::ConcreteObserverB(Subject* sub)

{

_sub = sub;

_sub->Attach(this);

}

ConcreteObserverB::~ConcreteObserverB()

{

_sub->Detach(this);

if (_sub != 0)

{

delete _sub;

}

}

Subject* ConcreteObserverB::GetSubject()

{

return _sub;

}

void ConcreteObserverB::PrintInfo()

{

cout<<"ConcreteObserverB observer.... "<<_sub->GetState()<<endl;

}

void ConcreteObserverB::Update(Subject* sub)

{

_st = sub->GetState();

PrintInfo();

}


int main(int argc, _TCHAR* argv[])

{

ConcreteSubject* sub = new ConcreteSubject();

Observer* o1 = new ConcreteObserverA(sub);

Observer* o2 = new ConcreteObserverB(sub);

sub->SetState("old");

sub->Notify();

sub->SetState("new"); //也可以由 Observer 调用

sub->Notify();

return 0;

}

       在 Observer 模式的实现中,Subject 维护一个 list 作为存储其所有观察者的容器。每当调用Notify操作就遍历list 中的Observer对象,并广播通知改变状态(调用Observer的Update 操作)。目标的状态 state 可以由 Subject 自己改变(示例),也可以由 Observer 的某个操作引 起 state 的改变(可调用 Subject 的 SetState 操作)。Notify 操作可以由 Subject 目标主动广播(示例),也可以由 Observer 观察者来调用(因为 Observer 维护一个指向 Subject 的指针)。

         运行示例程序,可以看到当 Subject 处于状态“old”时候,依赖于它的两个观察者都显 示“old”,当目标状态改变为“new”的时候,依赖于它的两个观察者也都改变为“new”。     

六、观察者模式和委托的结合

上述代码尽管已经用了依赖倒转原则,但是“抽象通知者”还是依赖“抽象观察者”,也就是说,万一没有了抽象观察者这样的接口,这个通知功能就发送不了。
另外就是每个具体观察者,它不一定是Update的方法调用。
目的:通知者和观察者之间根本就互相不知道,由客户端来决定通知谁

七、总结

实现观察者模式的时候要注意,观察者和被观察对象之间的互动关系不能体现成类之间的直接调用,否则就将使观察者和被观察对象之间紧密的耦合起来,从根本上违反面向对象的设计的原则。无论是观察者“观察”观察对象,还是被观察者将自己的改变“通知”观察者,都不应该直接调用

猜你喜欢

转载自blog.csdn.net/weixin_41882459/article/details/112688096