设计模式之观察者模式

2018-09-21 20:57:03

观察者模式

  观察者模式又叫做发布-订阅(Publish/Subscribe)模式。它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时,会通知所有的观察者对象,使它们能够自动更新自己。

观察者(Observer)模式UML类图

Subject类(一般叫做主题或者抽象统治者,通常用一个抽象类实现),它把所有对观察者对象的引用保存在一个集合里,每个主题都可以有任何数量的观察者。抽象主题提供一系列方法,可以增加和删除观察者对象。

Observer类,抽象观察者,为所有的具体观察者定义一个接口,在得到主题的通知时更新自己。更新方法通常包含一个Update()方法。

ConcreteSubject类,具体主题或者具体通知者,将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。

ConcreteObserver类,具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。通常用一个具体子类实现。

代码示例

  还读书的时候,有节课我们都很喜欢,就是自习课,因为大家可以在自习课上做很多有意思的事情,比如睡觉、看小说、玩手机等等。可是万一被老师抓到了那可就麻烦了,玩手机的手机会被没收,看小说的小说也要上交,要是睡觉,那你中大奖了,可能要被罚站。所以怎么办呢,如果老师来了,有人通知,那么就不会被罚了,只要及时做好认真学习的样子(^_^)。这里就隐含了一个可以使用观察者模式的场景,在这个场景中,老师就是要订阅的主题,班级里没好好学习的学生就是观察者。

1.抽象观察者(每个观察者都必须要有一个Update方法,或者功能类似的方法,方法的功能就是根据订阅主题的变化,自己做出相应的动作)(UML中的Observer)

#ifndef OBSERVER_H_
#define OBSERVER_H_
#include <string>
class Observer
{
public:
    virtual void update(std::string strTeacherName) = 0;
    Observer() = default;
    virtual ~Observer() = default;
};
#endif
Observer

2.具体观察者

#ifndef PLAYINGSTUDENT_H_
#define PLAYINGSTUDENT_H_

#include "Observer.h"
#include <iostream>
#include <string>
class PlayingStudent:public Observer
{
public:
    void update(const std::string strTeacherName) override;
    PlayingStudent(const std::string strStudentName) : m_strNameOfStudent(strStudentName){};
    ~PlayingStudent() = default;
private:
    std::string m_strNameOfStudent;
};
#endif

#include "PlayingStudent.h"

void PlayingStudent::update(const std::string strTeacherName)
{
    std::cout << "Subject: "<< strTeacherName << " is coming!" << std::endl;
    std::cout << "Observer:" << m_strNameOfStudent << " :Where ?" << std::endl;
}

#ifndef SLEEPINGSTUDENT_H_
#define SLEEPINGSTUDENT_H_

#include "Observer.h"
#include <iostream>
#include <string>
class SleepingStudent:public Observer
{
public:
    void update(const std::string strTeacherName) override;
    //Construct Function: or you can declare it as Observer(ConcreteSubject objConcretestruct,const std::string strName) for get state of Subject
    SleepingStudent(const std::string strStudentName):m_strNameOfStudent(strStudentName){};
    ~SleepingStudent() = default;
private:
    std::string m_strNameOfStudent;
};
#endif

#include "SleepingStudent.h"

void SleepingStudent::update(const std::string strTeacherName)
{
    std::cout << m_strNameOfStudent << "," << strTeacherName << "is comming,weak up" << std::endl;
}
ConcreteOberver

3.抽象主题

#ifndef SUBJECT_H_
#define SUBJECT_H_
#include "Observer.h"

class Subject
{
public:
    virtual void attach(Observer *) = 0;  //Add Observer
    virtual void detach(Observer *) = 0;  //Remove Observer
    virtual void notify() = 0;        //Note Observer
    Subject() = default;
    virtual ~Subject() = default;
};
#endif
Subject

4.具体主题

#ifndef CONCRETESUBJECT_H_
#define CONCRETESUBJECT_H_

#include "Subject.h"
#include "Observer.h"
#include <list>
#include <string>
class ConcreteSubject : public Subject
{
public:
    void attach(Observer* objObserver) override;
    void detach(Observer *objObserver) override;
    void notify() override;
    void setTeacherName(const std::string strName)
    {
    m_strNameOfTeacher = strName;
    }
    ConcreteSubject() = default;
    ~ConcreteSubject() = default;
private:
    std::string m_strNameOfTeacher;
    std::list<Observer* > m_listObserver;
};
#endif

#include "ConcreteSubject.h"

void ConcreteSubject::attach(Observer* objObserver)
{
    m_listObserver.push_back(objObserver);
}

void ConcreteSubject::detach(Observer *objObserver)
{
    m_listObserver.remove(objObserver);
}

void ConcreteSubject::notify()
{
    for(auto value : m_listObserver)
    {
    value->update(m_strNameOfTeacher);
    }
}
ConcreteSubject

5.client

#include "ConcreteSubject.h"
#include "SleepingStudent.h"
#include "PlayingStudent.h"
using namespace std;

int main(int argc,char *argv[])
{
   SleepingStudent objStudentA("H&T");
   ConcreteSubject objSubject;
   objSubject.setTeacherName("Mr Su");
   objSubject.attach(&objStudentA);
   
   SleepingStudent objStudentB("M");
   objSubject.attach(&objStudentB);

   PlayingStudent objPlayingStudentA("N");
   objSubject.attach(&objPlayingStudentA);
   
   PlayingStudent objPlayingStudentB("Z");
   objSubject.attach(&objPlayingStudentB);
   objSubject.detach(&objPlayingStudentB);
   objSubject.notify();
   return(1);
}
Client

观察者模式的特点

适用场景:

  将一个系统分隔成一系列相互协作的类带来的一个副作用是:需要维护相关对象的一致间的一致性。我们不希望为了维护一致性而使得各类紧密耦合,这样会给维护、扩展和重用都带来不便。而观察者模式的关键对象是主题Subject和观察者Observer,一个Subject可以有任意数目的依赖它的Observer,一旦Subject的状态发生了改变,所有的Observe都可以得到通知。Subject发出通知时并不需要知道谁是它的观察者,也就是说,具体观察者是谁,它根本不需要知道。而任何一个具体观察者也不需要知道其它观察者的存在。

  那么当一个对象的改变需要同时改变其它对象的时候,并且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。就是说,一个抽象模型有两个方面,其中一方面依赖于另一方面,这时用观察者模式可以将这两者封装在独立的对象中使它们各自独立的改变和复用。

  观察者所做的事情就是解耦合,让耦合的双方都依赖于抽象,而不依赖具体,从而使得各自的变换都不会影响另一边的变化。

优点:

  1.一个抽象模型的两个方面相互依赖,使用观察者模式将其解耦为观察者和主题,配合使用虚基类,使得两者的耦合度降低(依赖抽象而不依赖具体),这能使双方的变化可以独自进行以及复用。

缺点:

  1.如果一个主题有很多个观察者,将所有观察者挨个通知一遍会消耗时间

  2.观察者模式没有一个机制让观察者知道变化是怎么发生的,而仅知道发生了这样的变化。

       3.观察目标和观察者之间如果有循环依赖的话(观察者和观察目标彼此拥有一个存储对方对象的集合),观察目标会触发两个模块之间的循环调用,这有可能会引发系统崩溃

  4.虽然通过抽象观察者和抽象主题,解除了实现之间的依赖,但是依然存在抽象观察和抽象主题这样的依赖,对一个已经开发好的框架,假设这两个缺少其一,那么就不能添加主题的通知功能,另外观察者接到通知后的方法名可能有很多种,但是通知者和观察者彼此之间并不知道, 此时可以由客户端来决定通知谁。

  

posted on 2018-09-22 01:47  古I月  阅读(246)  评论(0编辑  收藏  举报

导航