• C++ 观察者模式


    观察者模式

    观察者模式解决什么问题?

    经常看到一些对象调用其他对象的方法:因为实现复杂的任务通常需要几个对象一起合作完成,为了该目标,对象A为了调用对象B的方法,就必须知道对象B的存在及其接口。
    最简单的办法是让A.cpp包含(include)B.h,然后直接调用B class方法。但这样做,做A中引入了B class,导致A和B编译时依赖,迫使两个类紧耦合。后果是,A class通用性减弱。如果A还调用了C class、D class,那么对A的改变会影响这三个紧耦合的类。而且,编译时紧耦合会导致用户不能在运行时给系统动态添加新的依赖。

    观察者模式可以有效支持组件解耦,避免循环依赖。


    观察者模式概念

    观察者模式的意图定义为:对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都得到通知并被自动更新。

    典型观察者模式有两个概念:主题(Subject),观察者(Observer),也称为发布者,订阅者。其通知方式是这样:用户通过观察者

    观察者模式典型实现的UML见下图。主要工作是将Subject对Object的编译时依赖,转化为对接口类的依赖,从而实现解耦。

    观察者模式典型实现

    为主题,观察者定义抽象接口

    #include <map>
    #include <vector>
    
    class IObserver
    {
    public:
        virtual ~IObserver() {}
        virtual void Update(int message) = 0;
    };
    
    class ISubject
    {
    public:
        ISubject();
        virtual ~ISubject();
        virtual void Subsribe(int message, IObserver *observer);  // 观察者订阅指定类型事件
        virtual void Unsubsribe(int message, IObserver *observer); // 观察者取消指定类型事件的订阅
        virtual void Notify(int message); // 通知已订阅指定事件的观察者
    
    private:
        typedef std::vector<IObserver*> ObserverList;  // 观察者列表
        typedef std::map<int, ObserverList> ObserverMap; // (消息类型, 观察者), 用于观察者订阅关注的特定消息 
        ObserverMap observers_;
    };
    

    主题能为多个不同的消息类型注册并发出通知,这样观察者只需要订阅关心的特定消息即可。比如,对于一个表示一堆元素的主题,当添加或删除元素时,观察者可以选择订阅关注的“添加或删除元素”消息。

    定义一个具体通知者类

    #include "observer.h"
    
    class MySubject : public ISubject // 具体的主题类
    {
    public:
        enum Message {ADD, REMOVE};   // 消息类型
        void Subsribe(int message, IObserver *observer) // 订阅消息
        {
            if (observer) {
                auto it = observers_[message];
                if (it == observers_.end()) {
                    ObserverList list;
                    list.push_back(observer);
                    observers_[message] = list;
                } else {
                   it->second.push_back(observer);
                }
            }
        }
    
        void Unsubsribe(int message, IObserver *observer) // 取消订阅的消息
        {
            auto it = observers_.find(message);
            if (it != observers_.end()) {
                it->second.remove_if(it->second.begin(), it->second.end(), [&observer](const IObserver* obj){ return obj == observer; });
            }
        }
    
        void Notify(int message) // 通知订阅消息的所有观察者
        {
            auto it = observers_.find(message);
            if (it != observers_.end() && !it->second.empty()) {
                for (auto obj : it->second) {
                    if (obj) {
                        obj->Update(message);
                    }
                }
            }
        }
    };
    

    通过继承IObserver抽象基类,实现Update()来创建观察者对象

    #include "subject.h"
    #include <iostream>
    #include <string>
    
    class MyObserver : public IObserver
    {
    public:
        explicit MyObserver(const std::string &str) : name_(str)
        {}
    
        void Update(int message)
        {
            std::cout << name_ << " Received message";
            std::cout << message << std::endl;
        }
    
    private:
        std::string name_;
    };
    

    客户端

    int main()
    {
        // 构造3个观察者对象
        MyObserver observer1("observer1");
        MyObserver observer2("observer2");
        MyObserver observer3("observer3");
        // 构造1个主题对象
        MySubject subject;
        
        // 为观察者对象订阅关注的特定事件
        subject.Subscribe(MySubject::ADD, &observer1);
        subject.Subscribe(MySubject::ADD, &observer2);
        subject.Subscribe(MySubject::REMOVE, &observer2);
        subject.Subscribe(MySubject::REMOVE, &observer3);
        
        // 通知订阅特定事件的观察者
        subject.Notify(MySubject::ADD);
        subject.Notify(MySubject::REMOVE);
        return 0;
    };
    

    观察者模式对优势:主题(Subject)无需耦合某个具体的观察者(如MyObserver),而只需要知道其抽象接口IObserver即可。观察者对象需要事先在具体的主题(MySubject)中订阅关注的事件,当主题自身状态发生改变时,可Notify通知订阅了特定事件的所有观察者。整个过程中,MySubject跟MyObserver没有编译时依赖,也没有耦合。

    观察者模式对缺点:性能损耗,即在函数调用前遍历观察者列表的开销。另外,在销毁观察者对象前,必须取消订阅此观察者对象,否则通知一个已销毁的观察者可能导致程序崩溃。


    MVC架构

    桌面程序和web程序中,有一个常用的模型就是MVC架构模式,该模式中,业务逻辑(model,模型)和用户界面(view,视图)分离,控制器(controller)接收用户输入并协调另外两者以确保同步。

    MVC有以下优点:
    1)model和view组件的隔离,可以方便实现多个用户界面,而且这些界面能重用公共的业务逻辑核心。
    2)避免因多份UI实现而构建多份重复的底层模型代码的问题。-- 底层模型不再与View 1:1绑定,可复用
    3)modle和view代码的解耦简化了单元测试;-- 功能单一了,更容易编写单元测试用例
    4)组件的模块化允许核心逻辑开发者和GUI开发者并行工作,且互不影响。

    核心:MVC模式促使核心业务逻辑(or 模型)与用户界面(or 视图)分离。隔离了控制器逻辑,控制器逻辑会引起模型的改变并更新视图。

    下图展示了MVC模式对依赖关系:

    视图代码能调用模型代码:发现最最新状态并更新UI。但模型代码不能调用视图代码,因为这样会导致模型绑定在一个视图上。

    MVC本质上也是一种观察者模式。当模型状态改变时,控制器将这些改变传递给视图以更新UI。当然,在真实APP中,通常需要通过更新视图来反应底层模型的其他改变。


    参考

    [1]Martin Reddy, 刘晓娜, 臧秀涛,等. C++ API设计[M]. 人民邮电出版社, 2013.

  • 相关阅读:
    【codecombat】 试玩全攻略 第二章 边远地区的森林 一步错
    【codecombat】 试玩全攻略 第十八关 最后的kithman族
    【codecombat】 试玩全攻略 第二章 边远地区的森林 woodlang cubbies
    【codecombat】 试玩全攻略 第二章 边远地区的森林 羊肠小道
    【codecombat】 试玩全攻略 第十七关 混乱的梦境
    【codecombat】 试玩全攻略 第二章 边远地区的森林 林中的死亡回避
    【codecombat】 试玩全攻略 特别关:kithguard斗殴
    【codecombat】 试玩全攻略 第二章 边远地区的森林 森林保卫战
    【codecombat】 试玩全攻略 第二章 边远地区的森林
    实验3 类和对象||
  • 原文地址:https://www.cnblogs.com/fortunely/p/16443574.html
Copyright © 2020-2023  润新知