zoukankan      html  css  js  c++  java
  • C++设计模式之观察者模式

    模式概述

    观察者模式又叫做发布-订阅模式(Publish-Subscribe)模式,是使用频率最高的设计模式之一; 在观察者模式中,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。观察者模式定义如下:

    观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象,这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己;观察者模式的基本结构图如下:

    这里写图片描述

    在这个UML结构图中主要包含以下几个角色:

    • subject类:它是目标类,也叫主题对象,提供了增加和删除观察者的接口,以及提供状态变化时的Notify通知接口;它可以是抽象类或者具体类;

    • ConcreteSubject类:具体目标类,它实现了subject类中定义的接口,同时把所有观察者对象的指针保存在一个list中,具体观察者的数量不受限制,可以动态变化;如果有需要可以再定义目标类的状态变量,当它发生变化时,通过Notify接口通知注册的所有观察者;

    • Observer类:抽象观察者,为所有的具体观察者定义一个Update接口;在得到主题对象的通知时,更新自己;

    • ConcreteObserver类:具体观察者,实现抽象观察者所要求的Update接口,以便使自身的状态与目标状态保持一致,或者触发其他动态;

    在有些更加复杂的情况下,具体观察者类ConcreteObserver的update()方法在执行时需要使用到具体目标类ConcreteSubject中的状态(属性),因此在ConcreteObserver与ConcreteSubject之间有时候还存在关联或依赖关系,在ConcreteObserver中定义一个ConcreteSubject实例,通过该实例获取存储在ConcreteSubject中的状态。
    如果ConcreteObserver的update()方法不需要使用到ConcreteSubject中的状态属性,则可以对观察者模式的标准结构进行简化,在具体观察者ConcreteObserver和具体目标ConcreteSubject之间无须维持对象引用。如果在具体层具有关联关系,系统的扩展性将受到一定的影响。

    实际应用

    下面代码演示的是,微信公众号申请成功,微信用户关注该公众号,或者取消关注的模拟;当公众号主题有状态更新时,所有关注的微信用户将得到通知。这里的公众号就是目标类,微信是观察者,其具体代码如下:

    #include "stdafx.h"
    #include <list>
    #include <string>
    
    class CUser;
    //微信公众号抽象类
    class CPublicAccount
    {
    public:
        CPublicAccount(){}
        virtual ~CPublicAccount(){}
        virtual void Attach(CUser* pUser) = 0;
        virtual void Detach(CUser* pUser) = 0;
        virtual void Notify() = 0;
        virtual void PushArticles(string strName) = 0;
    };
    
    //微信用户
    class CUser
    {
    public:
        CUser(string strUserName):m_strUserName(strUserName){}
        virtual ~CUser(){}
        string GetUserName() const
        {
            return m_strUserName;
        }
    public:
        //更新状态接口
        virtual void Update() = 0;
    
    private:
        string m_strUserName;
    };
    
    class CConcreteAccount: public CPublicAccount
    {
    public:
        CConcreteAccount(string strName):m_strAccountName(strName)
        {
            cout << m_strAccountName << "申请公众号成功
    " << endl;
        }
    
        virtual ~CConcreteAccount()
        {
            m_lsUser.clear();
        }
    
        //订阅
        void Attach(CUser* pUser)
        {
          if (NULL != pUser)
          {
              m_lsUser.push_back(pUser);
              cout << pUser->GetUserName() + "已关注" << m_strAccountName << endl;
          }
        }
    
        //取消订阅
        void Detach(CUser* pUser)
        {
           if (!m_lsUser.empty() && NULL != pUser)
           {
               m_lsUser.remove(pUser);
               cout << pUser->GetUserName() + "取消关注" << endl << endl;
           }
        }
    
        //通知订阅者
        void Notify()
        {
            list<CUser*>::const_iterator it = m_lsUser.begin();
            for (; it != m_lsUser.end(); it++)
            {
                (*it)->Update();
            }
        }
    
        //发布文章
        void PushArticles(string strTitle)
        {
            m_SubjectState =  m_strAccountName+"发布新文章"+"<<"+strTitle+">>";
    
            //状态变化
            cout << m_SubjectState  << endl << endl;
    
            //通知订阅者
            Notify();
        }
        void SetState(string strState)
        {
            m_SubjectState = strState;
        }
    
        string GetState()
        {
            return m_SubjectState;
        }
    
    private:
        string              m_strAccountName;//公众号名称
        list<CUser*>        m_lsUser;        //订阅用户
        string              m_SubjectState; //目标状态
    };
    
    
    //微信用户
    class CConcreteUser:public CUser
    {
    public:
        CConcreteUser(string strUserName,CConcreteAccount* pAccount):
          CUser(strUserName),m_pAccount(pAccount){}
    
        ~CConcreteUser(){}
    
        //更新状态
        void Update()
        {
            //更新观察者状态
            m_strObserverState = m_pAccount->GetState();
    
            cout << GetUserName() << "的状态是"<<"""<< m_strObserverState <<"""; 
            cout << endl;
        }
    private:
        string m_strObserverState;
        CConcreteAccount* m_pAccount;
    };

    客户端代码

    int _tmain(int argc, _TCHAR* argv[])
    {
        //注册公众号
        CConcreteAccount account("幕水");
    
        CUser* pUser1 = new CConcreteUser("张三",&account);
        CUser* pUser2 = new CConcreteUser("李四",&account);
        CUser* pUser3 = new CConcreteUser("陈五",&account);
        CUser* pUser4 = new CConcreteUser("王六",&account);
    
        //注册订阅者
        account.Attach(pUser1);
        account.Attach(pUser2);
        account.Attach(pUser3);
        account.Attach(pUser4);
        cout << endl;
    
        //公众号发布文章,引发状态变化
        account.PushArticles("今日股市复盘");
    
        cout << endl;
    
        //用户4取消关注
        account.Detach(pUser4);
        //公众号发布文章,引发状态变化
        account.PushArticles("未来股市分析");
    
        SAFE_DELETE_PTR(pUser1);
        SAFE_DELETE_PTR(pUser2);
        SAFE_DELETE_PTR(pUser3);
        SAFE_DELETE_PTR(pUser4);
        return 0;
    }

    运行结果
    这里写图片描述

    模式总结

    观察者模式是一种使用频率非常高的设计模式,无论是移动应用、Web应用或者桌面应用,观察者模式几乎无处不在,它为实现对象之间的联动提供了一套完整的解决方案,凡是涉及到一对一或者一对多的对象交互场景都可以使用观察者模式。

    (1) 主要优点

    观察者模式的主要优点如下:

    1. 观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。

    2. 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。

    3. 观察者模式满足“开闭原则”的要求,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。

    (2) 主要缺点

    观察者模式的主要缺点如下:

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

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

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

    (3) 适用场景

    在以下情况下可以考虑使用观察者模式:

    (1) 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用。

    (2) 一个对象的改变将导致一个或多个其他对象也发生改变,而并不知道具体有多少对象将发生改变,也不知道这些对象是谁。

    (3) 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

    参考资料: http://blog.csdn.net/lovelion/article/details/17517213

  • 相关阅读:
    Mysql多个字段同时满足多组条件
    spring-boot 配置Druid监控
    回顾存储过程简单使用
    win10环境下使用docker部署spring-boot项目
    LeetCode 35. 搜索插入位置
    二分查找
    归并排序(二)
    归并排序
    剑指 Offer 68
    剑指 Offer 68
  • 原文地址:https://www.cnblogs.com/jinxiang1224/p/8468210.html
Copyright © 2011-2022 走看看