zoukankan      html  css  js  c++  java
  • 深度理解观察者模式

    观察者模式,程序员让被观察者主动通知观察者相应事件的发生,也就存在一个必要的背景,程序员可以获取当前被观察者的状态。

    所谓观察者模式,一般采用方法注册,以实现解耦的思维,被观察者内部可注册所有观察者的观察并处理的方法。程序员可以根据当前被观察者的状态来决定是否调用应用观察者的方法。

    这么多观察者的事件处理全都是基于被观察者的状态的,当然应该在被观察者这头进行事件更新(或称为事件通知,通知观察者)的调用,我觉得观察者模式里这一条是最重要的,

    即: 核心是被观察者负责主动通知观察者 。

    至于方法注册、抽象接口啥的,完全可以根据当前场景的需求来选择性配置:

    高配(注册思维+抽象接口)、低配(for循环+没有抽象接口),下面具体解释:

    备注:上述注册思维,一般是结合内核链表来实现,观察者注册观察方法时,通过一个字符串(被观察者对象名字符串)来唯一绑定具体某个被观察者,

    某个被观察者遍历调用观察者方法时,只能调用和自身绑定的观察者的方法,不能越界调用其他的观察者的方法。

    例如,电梯对象作为观察者要观察楼层按键对象(可以重写下之前的电梯升降小项目,核心思路即:按键对象负责检测用户输入键值,和键值上报到电梯对象?(No, 电梯对象需要从公共通道获取键值。键值直接上报到电梯对象,会造成不同层次间的代码耦合),电梯对象提供上行下行方法,),

    通信对象(负责和传达室通信)作为观察者要观察麦克风对象。

    当麦克风有输入时,只能调用和自身绑定的观察者,所以需要调用的时通信对象提供的通信方法,而不能调用电梯提供的上行下行方法。

    而for循环则无需链表,只需要遍历一个存储观察者方法的函数指针数组即可。

    加深理解:

    按键作为硬件驱动层,检测用户输入,适合直接调用观察患者们的处理方法吗? 不合适。 第一点:各个观察者的处理方法不一样,有的会阻塞,有的可能不阻塞,直接在按键驱动层调用各个观察者的方法肯定会造成各个观察者方法的拥堵;  第二点: 代码要遵循一定的设计原则,要保持分层,硬件驱动层肯定不能直接调用业务逻辑层的处理。     

    经过分析后,既然不能直接在硬件驱动层调用观察方法,那么只能够把按键状态上报给各个观察者交由其独立处理了,具体上报给哪个观察者呢? 观察者1,还是观察者2? 这是未知的,甚至是随着业务的扩展有可能会新增观察者,所以观察者应该采用注册思维,联系register_input_status_listener该函数名,顾名思义吧。 同时,各个观察者最好从一个公共的通道来获取这个键值,让键值缓冲在这里,等待各个键值处理完成(这就还需要一个键值处理并阻塞等待反馈的逻辑), 缓冲键值,这就使得工程中肯定需要一个队列。 键值从按键驱动层入队,出队时调用各个观察者的处理方法。该层可以理解为观察者和被观察者的中间hal层,起到缓冲键值的作用。 

    那么如何保证出队时不造成各个观察者处理方法的拥堵呢? 使用基于事件触发的多线程模型,即各个观察者的处理方法内仅仅是发送一个信号,来唤醒各自观察者的针对硬件按键的键值处理线程,这样可以达到并行的处理。

    锁具代码:

    两组观察者和被观察者。 

    两组之间: 通过条件变量+队列的方式进行事件消息的传递。

    这样,最底层的被观察者就可以通知最上层的观察者了,这就是叠加使用多组观察者模式的效果。还可以视需求,可在中间层的观察者方法内做一些对事件消息的中间数据处理。 

    联系register_input_status_listener 该注册方法和 input_status_report_handler该被注册的观察者方法,

    本文所谈可能会对于,理解Linux input子系统实现原理 有些许帮助。

     CPP版本:

    observer.h

    #ifndef _OBSERVER_H_
    #define _OBSERVER_H_
    
    #include <string>
    #include <list>
    using namespace std;
    
    class Subject;
    
    /*  抽象类  Observer 
    定义:声明了纯虚函数的类,都称为抽象类。
    
    主要特点:抽象类只能作为基类来派生新类,不能声明抽象类的对象。
    (既然都是一个抽象概念了,纯虚函数没有具体实现方法,故不能创造该类的实际的对象)
    
    但是可以声明指向抽象类的指针变量或引用变量,通过指针或引用,就可以指向并访问派生类对象,
    进而访问派生类的成员。(体现了多态性)
    
    作用:因为其特点,基类只是用来继承,可以作为一个接口,具体功能在派生类中实现(接口)
    */
    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:
    };
    
    // 被观察者,例如这是touch
    class ConcreteSubjectB : public Subject
    {
    public:
        ConcreteSubjectB();
        ~ConcreteSubjectB();
    protected:
    private:
    };
    
    #endif
    

    observer.cpp

    #include "observer.h"
    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    /*
    Subject类,可翻译为主题或抽象通知者,一般用一个抽象类或者一个借口实现。
    它把所有对观察者对象的引用保存在一个聚集里,每个主题都可以有任何数量的观察者。
    抽象主题提供一个借口,可以增加和删除观察者对象。
    
    Observer类,抽象观察者,为所有的具体观察者定义一个借口,在得到主题的通知时更新自己。
    这个借口叫做更新接口。抽象观察者一般用一个抽象类或者一个接口实现。
    更新接口通常包含一个Update()方法。
    
    ConcreteSubject类,叫做具体主题或具体通知者,将有关状态存入具体通知者对象;
    在具体主题的内部状态改变时,给所有等级过的观察者发出通知。通常用一个具体子类实现。
    
    ConcreteObserver类,具体观察者,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。
    具体观察者角色可以保存一个指向一个具体主题对象的引用。
    
    特点:将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相关对象间的一致性。
    我们不希望为了维持一致性而使各类紧密耦合,这样会给维护、扩展和重用都带来不便。
    
    何时使用:
    当一个对象的改变需要同时改变其他对象的时候,而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式。
    观察者模式所做的工作其实就是在解除耦合。让耦合的双方都依赖于抽象,而不是依赖于具体。
    从而使得各自的变化都不会影响另一边的变化。
    */
    
    // --- 下面是 观察者类实现 ---
    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;
    
        //find():  find失败时,返回值就是传进去的第2个实参,即这里的m_lst.end()
        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++)
        {
            //调用观察者的更新接口,传入被观察者类自己的this指针
            (*iter)->Update(this);
        }
    }
    
    //我的门锁使用了队列,可以起到缓冲键值的功能,从队列内获取键值。
    //此处本博客cpp版观察者模式相当于是直接获取键值。设计劣于我。
    
    string Subject::GetState()
    {
        return this->m_state;
    }
    
    void Subject::SetState(string state)
    {
        this->m_state = state;
    }
    
    
    // ---本演示demo未使用到---
    ConcreteSubjectA::ConcreteSubjectA()
    {}
    ConcreteSubjectA::~ConcreteSubjectA()
    {}
    ConcreteSubjectB::ConcreteSubjectB()
    {}
    ConcreteSubjectB::~ConcreteSubjectB()
    {}
    

    observer_mode.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;
    }
    

    RUN:

     

    .

    /************* 社会的有色眼光是:博士生、研究生、本科生、车间工人; 重点大学高材生、普通院校、二流院校、野鸡大学; 年薪百万、五十万、五万; 这些都只是帽子,可以失败千百次,但我和社会都觉得,人只要成功一次,就能换一顶帽子,只是社会看不见你之前的失败的帽子。 当然,换帽子决不是最终目的,走好自己的路就行。 杭州.大话西游 *******/
  • 相关阅读:
    centos下修改hosts
    metasploit rpc
    使用Suricata和ELK进行网络入侵检测
    查询存储设备的UUID
    CentOS基础命令大全
    两个有序数组合并到一个新数组
    dubbo
    redis基本数据类型【3】-List类型
    redis基本数据类型【2】-Hash类型
    redis基本数据类型【1】-String类型
  • 原文地址:https://www.cnblogs.com/happybirthdaytoyou/p/13515286.html
Copyright © 2011-2022 走看看