• 【设计模式】 观察者模式


    1、定义

    1.1 标准定义

      观察者模式(Observer Pattern)也叫做发布订阅模式(Publish/subscribe,它是一个在项目中经常使用的模式,其定义如下:
      Define a one-to-many dependency between objects so that when one object changes state,all its dependents are notified and updated automatically.(定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。)

    1.2 通用类图

      我们先来解释一下观察者模式的几个角色名称:

      ● Subject被观察者
      定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽象类或者是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。

      ● Observer观察者
      观察者接收到消息后,即进行update(更新方法)操作,对接收到的信息进行处理。

      ● ConcreteSubject具体的被观察者
      定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。

      ● ConcreteObserver具体的观察者
      每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑。

    2、实现

    2.1 类图

      Subject类,可翻译为主题或抽象通知者,一般用一个抽象类或者一个借口实现。它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。抽象主题提供一个借口,可以增加和删除观察者对象。
      Observer类,抽象观察者,为所有的具体观察者定义一个借口,在得到主题的通知时更新自己。这个借口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现。更新接口通常包含一个Update()方法。
      ConcreteSubject类,叫做具体主题或具体通知者,将有关状态存入具体通知者对象;在具体主题的内部状态改变时,给所有等级过的观察者发出通知。通常用一个具体子类实现。
      ConcreteObserver类,具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。具体观察者角色可以保存一个指向一个具体主题对象的引用。

    2.2 代码

    2.2.1 观察者

    // Observer.h
    
    #ifndef _OBSERVER_H_
    #define _OBSERVER_H_
    
    #include <string>
    #include <list>
    using namespace std;
    
    class Subject;
    
    class Observer
    {
    public:
        ~Observer();
        virtual void Update(Subject*)=0;
    protected:
        Observer();
    private:
    };
    
    class ConcreteObserverA : public Observer
    {
    public:
        ConcreteObserverA();
        ~ConcreteObserverA();
        virtual void Update(Subject*);
    protected:
    private:
        string m_state;
    };
    
    class ConcreteObserverB : public Observer
    {
    public:
        ConcreteObserverB();
        ~ConcreteObserverB();
        virtual void Update(Subject*);
    protected:
    private:
        string m_state;
    };
    
    class Subject
    {
    public:
        ~Subject();
        virtual void Notify();
        virtual void Attach(Observer*);
        virtual void Detach(Observer*);
        virtual string GetState();
        virtual void SetState(string state);
    protected:
        Subject();
    private:
        string m_state;
        list<Observer*> m_lst;
    };
    
    class ConcreteSubjectA : public Subject
    {
    public:
        ConcreteSubjectA();
        ~ConcreteSubjectA();
    protected:
    private:
    };
    
    class ConcreteSubjectB : public Subject
    {
    public:
        ConcreteSubjectB();
        ~ConcreteSubjectB();
    protected:
    private:
    };
    
    #endif
    // Observer.cpp
    
    #include "Observer.h"
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    Observer::Observer(){}
    
    Observer::~Observer(){}
    
    ConcreteObserverA::ConcreteObserverA(){}
    
    ConcreteObserverA::~ConcreteObserverA(){}
    
    void ConcreteObserverA::Update(Subject* pSubject)
    {
        this->m_state = pSubject->GetState();
        cout << "The ConcreteObserverA is " << m_state << std::endl;
    }
    
    ConcreteObserverB::ConcreteObserverB(){}
    
    ConcreteObserverB::~ConcreteObserverB(){}
    
    void ConcreteObserverB::Update(Subject* pSubject)
    {
        this->m_state = pSubject->GetState();
        cout << "The ConcreteObserverB is " << m_state << std::endl;
    }
    
    Subject::Subject(){}
    
    Subject::~Subject(){}
    
    void Subject::Attach(Observer* pObserver)
    {
        this->m_lst.push_back(pObserver);
        cout << "Attach an Observer
    ";
    }
    
    void Subject::Detach(Observer* pObserver)
    {
        list<Observer*>::iterator iter;
        iter = find(m_lst.begin(),m_lst.end(),pObserver);
        if(iter != m_lst.end())
        {
            m_lst.erase(iter);
        }
        cout << "Detach an Observer
    ";
    }
    
    void Subject::Notify()
    {
        list<Observer*>::iterator iter = this->m_lst.begin();
        for(;iter != m_lst.end();iter++)
        {
            (*iter)->Update(this);
        }
    }
    
    string Subject::GetState()
    {
        return this->m_state;
    }
    
    void Subject::SetState(string state)
    {
        this->m_state = state;
    }
    
    ConcreteSubjectA::ConcreteSubjectA(){}
    
    ConcreteSubjectA::~ConcreteSubjectA(){}
    
    ConcreteSubjectB::ConcreteSubjectB(){}
    
    ConcreteSubjectB::~ConcreteSubjectB(){}

    2.2.2 调用

    // main.cpp
    
    #include "Observer.h"
    #include <iostream>
    
    using namespace std;
    
    int main()
    {
        Observer* p1 = new ConcreteObserverA();
        Observer* p2 = new ConcreteObserverB();
        Observer* p3 = new ConcreteObserverA();
    
        Subject* pSubject = new ConcreteSubjectA();
        pSubject->Attach(p1);
        pSubject->Attach(p2);
        pSubject->Attach(p3);
    
        pSubject->SetState("old");
    
        pSubject->Notify();
    
        cout << "-------------------------------------" << endl;
        pSubject->SetState("new");
    
        pSubject->Detach(p3);
        pSubject->Notify();
    
        return 0;
    }

    2.2.3 执行结果

    3、总结

    3.1 优点

      ● 观察者和被观察者之间是抽象耦合
      如此设计, 则不管是增加观察者还是被观察者都非常容易扩展

      ● 建立一套触发机制
      根据单一职责原则,每个类的职责是单一的,那么怎么把各个单一的职责串联成真实世界的复杂的逻辑关系呢? 比如, 我们去打猎, 打死了一只母鹿, 母鹿有三个幼崽,因失去了母鹿而饿死,尸体又被两只秃鹰争抢,因分配不均,秃鹰开始斗殴,然后羸弱的秃鹰死掉,生存下来的秃鹰,则因此扩大了地盘……这就是一个触发机制,形成了一个触发链。观察者模式可以完美地实现这里的链条形式。

    3.2 缺点

      观察者模式需要考虑一下开发效率和运行效率问题, 一个被观察者,多个观察者,开发和调试就会比较复杂,如果通知默认是顺序执行, 一个观察者卡壳,会影响整体的执行效率。 在这种情况下, 一般考虑采用异步的方式。
    多级触发时的效率更是让人担忧,大家在设计时注意考虑。

    3.3 使用场景

      ● 关联行为场景,当一个对象的改变需要同时改变其他对象的时候,而且它不知道具体有多少对象有待改变时。 需要注意的是, 关联行为是可拆分的, 而不是组合关系。
      ● 事件多级触发场景。
      ● 跨系统的消息交换场景, 如消息队列的处理机制。

    3.4 注意事项

      ● 广播链的问题
      如果你做过数据库的触发器,你就应该知道有一个触发器链的问题,比如表A上写了一个触发器,内容是一个字段更新后更新表B的一条数据,而表B上也有个触发器,要更新表C,表C也有触发器……完蛋了,这个数据库基本上就毁掉了!我们的观察者模式也是一样的问题,一个观察者可以有双重身份,既是观察者,也是被观察者, 这没什么问题呀,但是链一旦建立, 这个逻辑就比较复杂, 可维护性非常差, 根据经验建议,在一个观察者模式中最多出现一个对象既是观察者也是被观察者, 也就是说消息最多转发一次( 传递两次) , 这还是比较好控制的。
      注意:它和责任链模式的最大区别就是观察者广播链在传播的过程中消息是随时更改的, 它是由相邻的两个节点协商的消息结构; 而责任链模式在消息传递过程中基本上保持消息不可变,如果要改变,也只是在原有的消息上进行修正。

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

  • 相关阅读:
    Shell基础:什么是shell脚本、2种脚本解释器、#!约定解释器类型、运行shell脚本的2种方式、shell变量命令规范/赋值/如何使用/只读变量/删除变量/变量类型、shell字符串及其常用方法、shell数组及其常用方法、shell注释
    Linux su命令:su命令语法、su root与su
    docker容器内使用apt报错E: List directory /var/lib/apt/lists/partial is missing.
    浅析事务是什么、mysql是如何实现事务提交和回滚的、保证事务持久性redo log的实现原理、保证事务一致性undo log的实现原理、事务ACID特性及其实现原理
    浅析前后端分离架构下的API安全问题:JWT保证token不被盗用的方案(即如何防范Replay Attacks)
    浅析如何保证缓存与数据库的双写一致性:4种更新缓存的设计模式理解
    浅析SpringCloud中断路器是什么、断路器的作用以及在Feign中使用断路器
    浅析后端微服务涉及到定时任务时如何解决多集群定时任务重复执行并发的方案对比
    Linux连续执行多条命令的写法区别
    Dockerfile中RUN/CMD/ENTRYPOINT命令区别
  • 原文地址:https://www.cnblogs.com/ChinaHook/p/7296030.html
Copyright © 2020-2023  润新知