设计模式10:观察者模式(订阅-发布)

系列总链接:《大话设计模式》学习记录_net 大话设计-CSDN博客

参考:简说设计模式------工厂方法模式 - JAdam - 博客园

参考:简单工厂模式(Simple Factory Pattern) - 回忆酿的甜 - 博客园

一:概述

观察者模式(Observer Pattern)是软件设计模式中的一种行为型模式。它定义了一种一对多的依赖关系,使得多个观察者对象同时监听一个主题对象(Subject)。当主题对象的状态发生改变时,所有依赖于它的观察者对象都会得到通知并自动更新。

想象一下你订阅了一份杂志,当你订阅了这份杂志之后,每当有新一期出版时,杂志社就会自动给你寄一份过来。在这个例子中,你是"观察者",而杂志社是"主题"。

把这个概念应用到软件开发中:

主题(Subject): 就像是杂志社,它持有一份订阅名单(即观察者的列表)。当主题的状态发生变化或发生了某些重要的事情时,它会通知所有在名单上的观察者。
观察者(Observer): 就像你和其他订阅者一样,观察者是对主题感兴趣的对象。它们注册到主题上,表示愿意接收更新。一旦主题有了新的信息,观察者们就会收到通知并作出相应的反应。

具体来说,如果你是一个用户,想要知道某个网站是否有新产品发布,你可以选择订阅该网站的邮件提醒服务。这个网站就是"主题",而你的电子邮件客户端就是"观察者"。当网站发布了新产品,它会通过发送邮件的方式通知你(以及其他订阅了相同服务的用户),这样你就知道了有新产品可以购买。

特点:

解耦: 你不需要知道是谁在管理这些邮件提醒,同样地,网站也不需要知道谁订阅了它的服务。双方只需要遵循一定的协议(如提供邮箱地址、点击订阅按钮等)。
灵活性: 你可以随时取消订阅或者重新订阅,这意味着你可以动态地加入或离开观察者名单。
广播式的通知: 只要有一个新产品发布,所有的订阅者都会同时收到通知,而不是一个接一个地通知。
**事件驱动:**这种模式非常适合用于那些需要对特定事件做出响应的情况,比如点击按钮、数据变化等。

二:结构与实现

结构:

借用下网络上的图:

来源:【设计模式系列】--观察者模式_观察者设计模式-CSDN博客

大抵分为:

**观察者(订阅者):**想要观察订阅某些主题的用户;

**主题:**也可叫频道,用来将消息数据分类,供用户观察订阅;

**主题代理(发布者):**管理主题和观察者(订阅者)关系的,当主题需发送消息时,由其向订阅者发送消息;

说明:

一般只要有个订阅类,发布者类即可,主题是在发布者类中进行定义,一般可以直接是字符串什么的。

实现:

mainwindow.ui:

mainwindow.h:

cpp 复制代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

//订阅者
class ISubscriber : public QObject
{
    Q_OBJECT
public:
    ISubscriber() {}
    virtual ~ISubscriber() {}

    virtual QString name() = 0;
    virtual void onMessage(QString channel, QString message) = 0;
};

//发布者
class IPublisher : public QObject
{
    Q_OBJECT
public:
    IPublisher() {}
    virtual ~IPublisher() {}

    QHash<QString, QVector<ISubscriber*>> m_allSubscribers;

    void subscribe(QString channel, ISubscriber* subscriber)
    {
        if(m_allSubscribers.contains(channel)){
            for(int i=0; i<m_allSubscribers.count(); i++)
            {
                if(m_allSubscribers.value(channel).at(i) == subscriber){
                    qDebug("Channel[%s] has been subscribed by subscriber[%s].",
                           channel.toLatin1().data(), subscriber->name().toLatin1().data());
                    return;
                }
            }
            m_allSubscribers[channel].append(subscriber);
            qDebug("Channel exist,  Subscriber[%s] subscribe channel[%s].",
                   subscriber->name().toLatin1().data(), channel.toLatin1().data());
        }else{
            QVector<ISubscriber*> vector;
            vector.append(subscriber);
            m_allSubscribers.insert(channel, vector);
            qDebug("Channel not exist,  Subscriber[%s] subscribe channel[%s].",
                   subscriber->name().toLatin1().data(), channel.toLatin1().data());
        }
    }

    void publish(QString channel, QString message)
    {
        if(m_allSubscribers.contains(channel)){
            for(int i=0; i<m_allSubscribers.value(channel).count(); i++)
            {
                m_allSubscribers.value(channel).at(i)->onMessage(channel, message);
            }
        }else{
            qDebug("Channel not exist,  publiser channel[%s] message no receiver.",channel.toLatin1().data());
        }
    }

    void unSubscribe(QString channel, ISubscriber* subscriber)
    {
        if(m_allSubscribers.contains(channel)){
            for(int i=0; i<m_allSubscribers.count(); i++)
            {
                if(m_allSubscribers.value(channel).at(i) == subscriber){
                    m_allSubscribers[channel].remove(i);

                    qDebug("Remove subscriber[%s] of channel[%s].",subscriber->name().toLatin1().data(),
                           channel.toLatin1().data());
                    return;
                }
            }
        }else{
            qDebug("unSubscribe, Channel not exist.",channel.toLatin1().data());
        }
    }
};

class MySubscriber : public ISubscriber
{
    Q_OBJECT
public:
    MySubscriber(QString name) : m_name(name) {}
    virtual ~MySubscriber() {}

    QString name(){
        return m_name;
    }
    void onMessage(QString channel, QString message){
        qDebug("%s,name:%s, channel:%s, message:%s",Q_FUNC_INFO, m_name.toLatin1().data(),
               channel.toLatin1().data(), message.toLatin1().data());
    }

private:
    QString m_name;
};

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:

    void on_btn_sub1_clicked();

    void on_btn_sub2_clicked();

    void on_btn_pub_clicked();

private:
    Ui::MainWindow *ui;

    MySubscriber* m_subscriber1;
    MySubscriber* m_subscriber2;
    IPublisher* m_publisher;
};
#endif // MAINWINDOW_H

mainwindow.cpp:

cpp 复制代码
#include "MainWindow.h"
#include "ui_MainWindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    m_subscriber1 = new MySubscriber("sub1");
    m_subscriber2 = new MySubscriber("sub2");

    m_publisher = new IPublisher;
}

MainWindow::~MainWindow()
{
    delete ui;

    if(m_subscriber1){
        m_subscriber1->deleteLater();
    }
    if(m_subscriber2){
        m_subscriber2->deleteLater();
    }
    if(m_publisher){
        m_publisher->deleteLater();
    }
}

void MainWindow::on_btn_sub1_clicked()
{
    if(!ui->lineEdit_sub1->text().isEmpty()){
        m_publisher->subscribe(ui->lineEdit_sub1->text(), m_subscriber1);
    }
}

void MainWindow::on_btn_sub2_clicked()
{
    if(!ui->lineEdit_sub2->text().isEmpty()){
        m_publisher->subscribe(ui->lineEdit_sub2->text(), m_subscriber2);
    }
}

void MainWindow::on_btn_pub_clicked()
{
    if(!ui->lineEdit_pub->text().isEmpty()){
        m_publisher->publish(ui->lineEdit_pub->text(), ui->lineEdit_message->text());
    }
}

三:应用

1.Redis的订阅发布机制;

2.手机新闻订阅哪些频道的新闻,该频道出了新消息会推送出来;

四:优缺点及适用环境

优点:

解耦合: 主题和观察者之间低耦合,主题不需要知道观察者的具体实现。
灵活性: 可以动态添加或移除观察者,系统更灵活。
广播通信: 适合用于需要向多个对象同时发送通知的情况。

缺点:

性能问题: 大量观察者可能导致通知过程效率低下。
复杂性增加: 可能引入不必要的复杂性,特别是当更新链很长时。
依赖管理: 如果不妥善管理,可能会造成内存泄漏或循环依赖。

适用环境:

1.当一个对象的状态变化需要影响其他多个对象时。

2.适用于事件驱动系统,如GUI事件处理、消息发布/订阅系统等。

3.需要保持对象间的一致性但又不想直接耦合时。

简而言之,观察者模式非常适合用于需要维护对象间松散耦合并且能够方便地添加或删除依赖关系的场景。

相关推荐
Crazy learner1 小时前
C 和 C++ 动态库的跨语言调用原理
c语言·c++
金士顿4 小时前
MFC 文档模板 每个文档模板需要实例化吧
c++·mfc
人才程序员7 小时前
QML z轴(z-order)前后层级
c语言·前端·c++·qt·软件工程·用户界面·界面
w(゚Д゚)w吓洗宝宝了7 小时前
C vs C++: 一场编程语言的演变与对比
c语言·开发语言·c++
小老鼠不吃猫9 小时前
C++点云大文件读取
开发语言·c++
姚先生979 小时前
LeetCode 35. 搜索插入位置 (C++实现)
c++·算法·leetcode
NorthCastle9 小时前
设计模式-创建型模式-简单工厂模式详解
设计模式·简单工厂模式
CoderCodingNo10 小时前
【GESP】C++二级考试大纲知识点梳理, (4)流程图
开发语言·c++·流程图
小小unicorn10 小时前
【C++初阶】STL详解(十三)—— 用一个哈希表同时封装出unordered_map和unordered_set
java·c++·散列表
慕羽★10 小时前
详细介绍如何使用rapidjson读取json文件
linux·c++·windows·json·file·param·rapidjson