首页 > 编程语言 >C++观察者模式的实现

C++观察者模式的实现

时间:2024-04-12 15:44:31浏览次数:27  
标签:const string observer 观察者 模式 C++ lstSubjects name

C++观察者模式的实现

观察者模式介绍

观察者模式是软件设计模式里面一种很常用又很重要的一种设计模式,观察者模式又叫做发布-订阅(Publish/Subscribe)模式。也就是主题对象(subject)发布通知,订阅该主题的多个观察者(observer)可以收到通知从而更新自己。

主题对象Subject发出通知时并不需要知道谁是它的观察者,也就是说具体观察者是谁,它根本不需要知道。而任何一个具体观察者不知道也不需要知道其他观察者的存在。

本质: 观察者模式本质是一个异步消息通知机制。
目的: 它可以起到多个对象通讯方之间的松耦合的目的。

什么时候使用观察者模式?

当一个对象的改变需要通知其他对象的时候,而且它不知道具体有多少对象有待通知的时候。

1. 经典观察者模式

经典观察者模式中的主题((subject))和观察者(observer),分别需要实现以下功能:

主题对象功能:

  • 添加订阅者
  • 删除订阅者
  • 更新主题内容
  • 通知订阅者

观察者功能:

  • 订阅主题
  • 获得主题的更新和通知

1.1 观察者模式类图

具体主题对象内部管理了一个观察者列表, 观察者订阅主题,也就是把自己“注册”给主题(调用主题对象的attach()方法),那么主题有内容更新时,就可以给所有登记过的观察者发出通知。

具体的观察实现对象,在主题更新时,会被通知并自动执行update()函数,所以观察者对象必须实现update()函数,并通过getmessage()来获得更新内容。

这个过程可以类比成,读者(观察者)订阅某本杂志(主题),当杂志有新的内容时,邮差小哥将新一期杂志投递给读者(通知),读者获得新的杂志后开始阅读。

1.2 实现

#include <stdint.h>
#include <iostream>
#include <string>
#include <map>
#include <mutex>

using namespace std;

class Observer
{
public:
    Observer(const string & name):m_name(name){}
    ~Observer(){}

    const string& getName()const {return m_name;}
    virtual void update(const string & name){cout << "!should'n go to here:"<< m_name << endl;}
    bool operator == ( const Observer& rhs){
        this->m_name == rhs.m_name ? true : false;
    }
protected:
    string m_name;
};

class Subject
{
public:
    Subject(const string & name):m_name(name){}
    ~Subject(){}
    
    const string& getName()const {return m_name;}
    bool attach(Observer *observer){
        lock_guard<mutex> lock_queue(mtx_map);
        if(nullptr == observer 
        || m_lstObserver.find(intptr_t(observer)) != m_lstObserver.end()) { //保证观察者对象不会重复添加
            return false;
        }
        m_lstObserver.insert({intptr_t(observer), observer}); 
        return true;
    }
    
    void detach(Observer *observer){
        if (nullptr != observer){
            lock_guard<mutex> lock_queue(mtx_map);
            m_lstObserver.erase(intptr_t(observer));      
        }
    }

    void notify(){
        for (auto& p : m_lstObserver)
            if (p.second){ p.second->update(m_name);}
    }

    /*****************/
    /*这里只是举例,在具体业务中需要更新的数据是多种多样的,
    /*此时可以选择合理的数据结构,并通过传入数据ID等更新和获取更精细的数据*/
    void setMessage(const string& message){
        if(m_message != message){
            m_message = message;
            notify();
        }
    }

    string getMessage() const{
        return m_message;
    }
    /****************/
    bool operator == (const Subject& rhs){
        this->m_name == rhs.m_name ? true : false;
    }
    
private:
    string m_name; 
    string m_message;
    map<intptr_t, Observer *> m_lstObserver;
    std::mutex mtx_map;     //考虑线程安全,对map和数据的访问需要加锁
};

class MagazineSubject : public Subject{
    using Subject::Subject;
};

class ReaderObserver : public Observer{
public:
    ReaderObserver(const string & name, Subject & subject)
    :Observer(name),m_subject(subject){
        m_subject.attach(this);
    };

    virtual void update(const string & name){cout << m_name << ": 收到了新的杂志" << m_subject.getMessage()  << endl;};
private:
    Subject & m_subject;
};

int main()
{
    MagazineSubject magazine1("中国地理");
    MagazineSubject magazine2("时代周刊");

    ReaderObserver readerA("读者A",magazine1);
    ReaderObserver readerB("读者B",magazine1);
    ReaderObserver readerC("读者C",magazine2);
    ReaderObserver readerD("读者D",magazine2);

    magazine1.setMessage("《鸡你太美》");
    magazine2.setMessage("《泰裤辣》");
    return 0;
}

这里的实现中,观察者获取数据的方式是,是主动从主题中拉取数据通过使用getMessage()这种统一接口。这么做的目的是为了让观察者能够灵活的获取自己所需的数据,而不是被动的被推送一大堆不是自己需要的数据,同时在增加新的观察者时,不需要修改update传参来满足不同的数据推送需求。

当然也可以是主题在调用update()时通过传参主动推送,这样不用主题内部保存多分数据的拷贝,降低复杂度和耦合,这两种方式各有优缺点。

2. 增强版观察者模式

从上面的介绍,大家可以看到的一个问题,就是观察者需要获得主题对象的引用,才能完成订阅的操作,也就是观察者依赖于主题(存在耦合)。就好比读者必须知道杂志的出版商,并到出版商那里去订阅。

同时,一个观察者可能还需要订阅多个主题,就像一个读者可能会订阅多份杂志一样, 当然在1.2实现章节的代码中,我们可以增加一个接口来样观察者订阅多个主题,但是这不是最终的解决方案。

针对以上问题,我们需要一个“增强版”的观察者模式,来进一步解决观察者和主题之间的相互依赖的问题。

这时候我们引入一个第三方的“杂志发布和订阅平台”, 杂志商只需要往平台上出版发布杂志信息,读者在平台上浏览所有的杂志信息,并根据自己的喜好来订阅多本杂志。杂志商不再需要把自己推销给具体的读者,读者也不需要找到具体的多个杂志出版商来订阅杂志。这时的平台充当中介的角色,这样就达到了彻底解耦的目的。

2.1 增强版观察者模式类图


由上图可以看出,xxxObserver类和xxxSubject类之间的关联或者依赖就不存在了,达到了松耦的目的

2.2 实现

在1.2章节的实现基础上,去掉在具体观察者对象构造函数中的主题对象引用传参,同时实现SubjectManage类即可

class SubjectManage
{
public:
    ~SubjectManage(){
    };

    static SubjectManage & Instance(){ static SubjectManage s_instance; return s_instance;}
    void AddSubject(Subject& subject){
        if (m_lstSubjects.find(subject.getName()) == m_lstSubjects.end()){
            m_lstSubjects.insert({subject.getName(), &subject});
        }
    }

    bool CreateSubCribe(Observer& observer, const string& sub_name){
        if (m_lstSubjects.find(sub_name) != m_lstSubjects.end()){
           return m_lstSubjects.at(sub_name)->attach(&observer);
        }
        return false;
    }

    bool deleteSubcribe(Observer& observer, const string& sub_name){
        if (m_lstSubjects.find(sub_name) != m_lstSubjects.end()){
            m_lstSubjects.at(sub_name)->detach(&observer);
        }
        return true;
    }

    string getMessage(const string& sub_name){
        if (m_lstSubjects.find(sub_name) != m_lstSubjects.end()){
            return m_lstSubjects.at(sub_name)->getMessage();
        }

        return string("error");
    }
private:
    SubjectManage(){m_lstSubjects.clear();};
    map<string, Subject *> m_lstSubjects;
};

class MagazineSubject : public Subject{
    using Subject::Subject;
};

class ReaderObserver : public Observer{
public:
    using Observer::Observer;
    virtual void update(const string & sub_name){cout << m_name
     << ": 收到了"<< sub_name <<"的杂志" << SubjectManage::Instance().getMessage(sub_name)  << endl;}
};

int main()
{
    MagazineSubject magazine1("中国地理");
    MagazineSubject magazine2("时代周刊");

    ReaderObserver readerA("读者A");
    ReaderObserver readerB("读者B");
    ReaderObserver readerC("读者C");
    ReaderObserver readerD("读者D");

    SubjectManage::Instance().AddSubject(magazine1);
    SubjectManage::Instance().AddSubject(magazine2);


    SubjectManage::Instance().CreateSubCribe(readerA, "中国地理");
    SubjectManage::Instance().CreateSubCribe(readerB, "中国地理");
    SubjectManage::Instance().CreateSubCribe(readerC, "中国地理");

    SubjectManage::Instance().CreateSubCribe(readerA, "时代周刊");
    SubjectManage::Instance().CreateSubCribe(readerB, "时代周刊");
    SubjectManage::Instance().CreateSubCribe(readerD, "时代周刊");

    magazine1.setMessage("《鸡你太美》");
    magazine2.setMessage("《泰裤辣》");
    return 0;
}

注意:上述示例代码基于C++11及以上的标准,编译时请增加-std=c++11参数

标签:const,string,observer,观察者,模式,C++,lstSubjects,name
From: https://www.cnblogs.com/HuangLiDi/p/18131453

相关文章

  • C++陷阱 — C++ auto变量类型推导
    问题描述C++使用auto类型声明一个单例对象的引用时,通过该auto变量来访问单例,是否等同于使用单例类::Instance()来访问单例呢?试看如下的例子:#include<stdint.h>#include<iostream>#include<string>#include<map>usingnamespacestd;classSingleClass{public:......
  • 消息中间件RabbitMQ_RabbitMQ的工作模式4
    一、Workqueues工作队列模式1、模式说明WorkQueues:与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。2、代码编写WorkQueues与入门程序的简......
  • 汇编语言简易教程(8):寻址模式
    汇编语言简易教程(8):寻址模式寻址模式是使用正在访问(读取或写入)的数据项的地址来访问内存中的值的受支持方法。这可能包括变量的名称或数组中的位置。基本的寻址模式包含:寄存器立即数内存寻址注意事项使用[]需要注意:访问内存的唯一方法是使用方括号([]'s)。省略括号......
  • 汇编语言简易教程(8):寻址模式
    汇编语言简易教程(8):寻址模式寻址模式是使用正在访问(读取或写入)的数据项的地址来访问内存中的值的受支持方法。这可能包括变量的名称或数组中的位置。基本的寻址模式包含:寄存器立即数内存寻址注意事项使用[]需要注意:访问内存的唯一方法是使用方括号([]'s)。省略括号......
  • 建造者模式
    建造者模式也称为Builder模式,在开发中也是经常用到的,下面围绕两个问题来看下:1、为什么需要建造者模式假设在开发中我们有这样一个需求:需要定义一个资源池配置类ResourcePoolConfig。这里的资源池,你可以简单理解为线程池、连接池、对象池等。在这个资源池配置类中,有......
  • pycharm使用debug模式调试不生效问题
    1、设置中配置 TRANSLATEwithxEnglishArabicHebrewPolishBulgarianHindiPortugueseCatalanHmongDawRomanianChineseSimplifiedHungarianRussianChineseTraditionalIndonesianSlovakCzechItalianSlovenianDanishJapaneseSp......
  • String类型转LPCTSTR -----理解C++中的字符串类型转换
    在看代码时,发现有时候会把string类型转换为LPCTSTR,刚开始不理解为什么要做这个转换,所以做了一些调查,现在记录如下是这样的,STRING是代表C++中的字符串string,而LPCTSTR代表的是Windows系统中的字符串类型。也就是说,这样转换的目的是为了把C++中的字符串string转换为Windows系......
  • 2022年蓝桥杯C++B组国赛-试题D-最大数字
    0.题目问题描述给定一个正整数N。你可以对N的任意一位数字执行任意次以下2种操作:将该位数字加1。如果该位数字已经是9,加1之后变成0。将该位数字减1。如果该位数字已经是0,减1之后变成9。你现在总共可以执行1号操作不超过A次,2号操作不超过......
  • 读论文-基于序列模式的电子商务推荐系统综述(A Survey of Sequential Pattern Based E
    前言今天读的论文为一篇于2023年10月3日发表在《算法》(Algorithms)的论文,这篇文章综述了基于序列模式的电子商务推荐系统,强调了通过整合用户购买和点击行为的序列模式来提高推荐准确性、减少数据稀疏性、增加推荐新颖性,并改善推荐系统的可扩展性。文章详细分析了现有推荐系统的......
  • TypeScript的严格模式,any和unknown都是顶级类型,any 和 unknown 的区别
    TypeScript中的严格模式在TS的严格模式中;不可以将null,undefined赋值给void;但是在非严格模式中就可以。Tip:在项目中;我们还是要开启严格模式,否则会出现意想不到的错误,我们可以在tsconfig.json文件的compilerOptions的strict中去配置tsconfig.json{"compilerOpti......