设计模式之观察者模式

235 阅读4分钟

**一、动机(Motivation)  **

在软件构建过程中,我们需要为某些对象建立一种“通知依赖关系”——一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知。如果这样的依赖关系过于紧密,将使软件不能很好地抵御变化。 使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。  

在GOF的《设计模式:可复用面向对象软件的基础》一书中对观察者模式是这样说的:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。当一个对象发生了变化,关注它的对象就会得到通知;这种交互也称为发布-订阅(publish-subscribe)。 

Observer 模式应该可以说是应用最多、影响最广的模式之一,因为 Observer 的一个实例 Model/View/Control( MVC) 结构在系统开发架构设计中有着很重要的地位和意义, MVC实现了业务逻辑和表示层的解耦。最常见的一个例子就是: 对同一组数据进行统计分析时候, 我们希望能够提供多种形式的表示 (例如以表格进行统计显示、柱状图统计显示、百分比统计显示等)。这些表示都依赖于同一组数据, 我们当然需要当数据改变的时候, 所有的统计的显示都能够同时自动改变。Observer 模式就是解决了这一个问题。

二、代码示例

/*
    该例子是设计模式中观察者模式的举例
    假设观察者有英国人、俄罗斯人,他们都订阅了美国一家消息公司的消息,并通过自己的方式展示这些消息
*/

#include <iostream>
#include <string>
#include <list>
using namespace std;

/* 所有观察者或者说订阅者的抽象 */
class Observer
{
public:
    // 抽象观察者并不知道怎样展示他们得到的消息,所以以接口方式(纯虚函数)提供,不做具体实现
    virtual void Update(string) = 0;
    virtual ~Observer() {
        cout << "This is base class Observer" << endl;
    }
};

/*  
    消息公司的抽象, 当然这个可有可无,如果消息公司不做抽象,
    则实际的观察者中的m_pSubject就用具体的消息公司定义
    对消息公司进行抽象可以理解为给消息公司指定了一系列的总体
    框架(Attach,Detach,Notify),具体这些框架的内容由实际
    消息公司制定
 */
class Subject
{
public:
    virtual void Attach(Observer*) = 0;
    virtual void Detach(Observer*) = 0;
    virtual void Notify() = 0;
    virtual ~Subject() {
        cout << "This is base class Subject" << endl;
    }
};

/* 实际的英国人观察者,他自己定义了利用消息的方式(他认为美国人发布的消息都是重要的) */
class ConcreteObserverUK : public Observer
{
public:
    ConcreteObserverUK(Subject* pSubject) : m_pSubject(pSubject) {}

    void Update(string msg)
    {
        cout << "Important news: " << msg << endl;
    }
    ~ConcreteObserverUK() {
        cout << "This is concrete class ConcreteObserverUK" << endl;
    }
private:
    Subject* m_pSubject;
};

/* 实际的俄国人观察者,他自己定义了利用消息的方式(他认为美国人发布的消息都是无聊的) */
class ConcreteObserverRU : public Observer
{
public:
    ConcreteObserverRU(Subject* pSubject) : m_pSubject(pSubject) {}

    void Update(string msg)
    {
        cout << "Boring news: " << msg << endl;
    }
    ~ConcreteObserverRU() {
        cout << "This is concrete class ConcreteObserverRU" << endl;
    }
private:
    Subject* m_pSubject;
};

/* 实际的美国人创办的消息机构 */
class ConcreteSubject : public Subject
{
public:
    void Attach(Observer* pObserver);
    void Detach(Observer* pObserver);
    void Notify();  // 美国人创立的消息机构只负责通知(Notify)
    void SetMsg(string msg)
    {
        m_msg = msg;
    }
    ~ConcreteSubject() {
        cout << "This is concrete class ConcreteSubject" << endl;
    }
private:
    std::list<Observer*> m_ObserverList;
    string m_msg;
};

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();
    while (it != m_ObserverList.end())
    {
        /* 具体观察者们怎么利用美国人发布的消息,由他们自己决定(Update) */
        (*it)->Update(m_msg);
        ++it;
    }
}

int main()
{
    // Create Subject
    ConcreteSubject* pSubject = new ConcreteSubject();

    // Create Observer
    Observer* pObserverUK = new ConcreteObserverUK(pSubject);
    Observer* pObserverRU = new ConcreteObserverRU(pSubject);

    // Register the observer
    pSubject->Attach(pObserverUK);
    pSubject->Attach(pObserverRU);

    // Change the state
    pSubject->SetMsg("特朗普竞选总统失败!");
    pSubject->Notify();

    // Unregister the observer
    pSubject->Detach(pObserverRU);

    pSubject->SetMsg("特朗普感染新冠病毒啦!");
    pSubject->Notify();

    delete pObserverUK;
    delete pObserverRU;
    delete pSubject;

    return 0;
}
输出:
Important news: 特朗普竞选总统失败!
Boring news: 特朗普竞选总统失败!
Important news: 特朗普感染新冠病毒啦!
This is concrete class ConcreteObserverUK
This is base class Observer
This is concrete class ConcreteObserverRU
This is base class Observer
This is concrete class ConcreteSubject
This is base class Subject

三、好处

1.做到了发布一个消息,所有的观察者都同步自动地收到的效果。虚析构函数也使得资源的释放变得简单方便。

2.可以看到,如果后续又有新的观察者,这要新增新观察者类即可,不用去改任何基类,也不用增加什么if-else语句,可以说是一种很独立的增加方式(通常在工程中,每个类都是用一个文件来表示的,因此不会影响到其他文件,其他文件编译的二进制文件都不会改变,实现了ABI级的复用(注意这种复用比在源代码层面复用一个函数还要好)),可以有效减少bug产生;

 3.如果这个程序被用到俄国,那么他们可能并不关心英国人怎么看待这个消息,因此俄国人类相关的代码可以放到高速缓存中,英国人类相关的代码可以放在内存甚至虚拟内存(磁盘)中,这样代码的本地局部性更好。