设计模式---订阅发布模式(Subscribe/Publish)

2024-04-12 14:58

本文主要是介绍设计模式---订阅发布模式(Subscribe/Publish),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

      订阅发布模式定义了一种一对多的依赖关系,让多个订阅者对象同时监听某一个主题对象。这个主题对象在自身状态变化时,会通知所有订阅者对象,使它们能够自动更新自己的状态。

       将一个系统分割成一系列相互协作的类有一个很不好的副作用,那就是需要维护相应对象间的一致性,这样会给维护、扩展和重用都带来不便。当一个对象的改变需要同时改变其他对象,而且它不知道具体有多少对象需要改变时,就可以使用订阅发布模式了。

       一个抽象模型有两个方面,其中一方面依赖于另一方面,这时订阅发布模式可以将这两者封装在独立的对象中,使它们各自独立地改变和复用。订阅发布模式所做的工作其实就是在解耦合。让耦合的双方都依赖于抽象,而不是依赖于具体,从而使得各自的变化都不会影响另一边的变化。

在我们日常写程序时,经常遇到下面这种情况:

public void 有告警信息产生()
{
    刷新界面();
    更新数据库();
    给管理员发Mail();
    ………………………………
}

当有告警信息产生时,依次要去执行:刷新界面()、更新数据库()、给管理员发Mail()等操作。表面上看代码写得很工整,其实这里面问题多多:

  • 首先,这完全是面向过程开发,根本不适合大型项目。
  • 第二,代码维护量太大。设想一下,如果产生告警后要执行10多个操作,那这将是个多么大,多少复杂的类呀,时间一长,可能连开发者自己都不知道如何去维护了。
  • 第三,扩展性差。如果产生告警后,要增加一个声音提示()功能,怎么办呢?没错,只能加在有告警信息产生()这个函数中,这样一来,就违反了“开放-关闭原则”。而且修改了原有的函数,那么在测试时,除了要测新增功能外,还要做原功能的回归测试;在一个大型项目中,做一次回归测试可能要花费大约两周左右的时间,而且前提是新增功能没有影响原来功能及产生新的bug。

那么如何把有告警信息产生()函数同其他函数进行解耦合呢?别着急,下面就介绍今天的主角----订阅发布模式。见下图:

订阅发布模式1

上面的流程就是对有告警信息产生()这个函数的描述。我们要做的,就是把产生告警和它需要通知的事件进行解耦,让它们之间没有相互依赖的关系,解耦合图如下:

订阅发布模式2

事件触发者被抽象出来,称为消息发布者,即图中的P。事件接受都被抽象出来,称为消息订阅者,即图中的S。P与S之间通过S.P(即订阅器)连接。这样就实现了P与S的解耦。首先,P就把消息发送到指定的订阅器上,从始至终,它并不知道也不关心要把消息发向哪个S。S如果想接收消息,就要向订阅器进行订阅,订阅成功后,S就可以接收来自S.P的消息了,从始至终,S并不知道也不关心消息来源于哪个具体的P。同理,S还可以向S.P进行退订操作,成功退订后,S就无法接收到来自指定S.P的消息了。这样就完美的解决了P与S之间的解耦。

等等,好像还有一个问题。从图上看,虽然P与S之间完成了解耦,但是P与S.P,S与S.P之间不就又耦合上了吗?其实这个问题好解决,想想我们上篇的装饰模式是怎么解耦的?对,就接口。这里我们同样使用接口来解决P与S.P和S与S.P之间的解耦,同时,使用delegate来解决多订阅多发布的机制。

下面给出实现代码。由于订阅发布模式涉及P, S.P和S三部份内容,所以代码比较多,也比较长。请大家耐心阅读。

首先,为了实现P与S.P, S与S.P之间的解耦,我们需要定义两个接口文件

ISubscribe.cs
namespace TJVictor.DesignPattern.SubscribePublish
{
    //定义订阅事件
    public delegate void SubscribeHandle(string str);
    //定义订阅接口
    public interface ISubscribe
    {
        event SubscribeHandle SubscribeEvent;
    }
}

IPublish
namespace TJVictor.DesignPattern.SubscribePublish
{
    //定义发布事件
    public delegate void PublishHandle(string str);
    //定义发布接口
    public interface IPublish
    {
        event PublishHandle PublishEvent;

        void Notify(string str);
    }
}

然后我们来设计订阅器。显然订阅器要实现双向解耦,就一定要继承上面两个接口,这也是我为什么用接口不用抽象类的原因(类是单继承)。

namespace TJVictor.DesignPattern.SubscribePublish
{
    public class SubPubComponet : ISubscribe, IPublish
    {
        private string _subName;
        public SubPubComponet(string subName)
        {
            this._subName = subName;
            PublishEvent += new PublishHandle(Notify);
        }

        #region ISubscribe Members
        event SubscribeHandle subscribeEvent;
        event SubscribeHandle ISubscribe.SubscribeEvent
        {
            add { subscribeEvent += value; }
            remove { subscribeEvent -= value; }
        }
        #endregion

        #region IPublish Members
        public PublishHandle PublishEvent;

        event PublishHandle IPublish.PublishEvent
        {
            add { PublishEvent += value; }
            remove { PublishEvent -= value; }
        }
        #endregion

        public void Notify(string str)
        {
            if (subscribeEvent != null)
                subscribeEvent.Invoke(string.Format("消息来源{0}:消息内容:{1}", _subName, str));
        }
    }
}

接下来是设计订阅者S。S类中使用了ISubscribe来与S.P进行解耦。代码如下:

namespace TJVictor.DesignPattern.SubscribePublish
{
    public class Subscriber
    {
        private string _subscriberName;

        public Subscriber(string subscriberName)
        {
            this._subscriberName = subscriberName;
        }

        public ISubscribe AddSubscribe { set { value.SubscribeEvent += Show; } }
        public ISubscribe RemoveSubscribe { set { value.SubscribeEvent -= Show; } }

        private void Show(string str)
        {
            Console.WriteLine(string.Format("我是{0},我收到订阅的消息是:{1}", _subscriberName, str));
        }
    }
}

最后是发布者P,继承IPublish来对S.P发布消息通知。

namespace TJVictor.DesignPattern.SubscribePublish
{
    public class Publisher:IPublish
    {
        private string _publisherName;

        public Publisher(string publisherName)
        {
            this._publisherName = publisherName;
        }

        private event PublishHandle PublishEvent;
        event PublishHandle IPublish.PublishEvent
        {
            add { PublishEvent += value; }
            remove { PublishEvent -= value; }
        }

        public void Notify(string str)
        {
            if (PublishEvent != null)
                PublishEvent.Invoke(string.Format("我是{0},我发布{1}消息", _publisherName, str));
        }
    }
}

至此,一个简单的订阅发布模式已经完成了。下面是调用代码及运行结果。调用代码模拟了图2中的订阅发布关系,大家可以从代码,运行结果和示例图三方面对照着看。

#region TJVictor.DesignPattern.SubscribePublish
//新建两个订阅器
SubPubComponet subPubComponet1 = new SubPubComponet("订阅器1");
SubPubComponet subPubComponet2 = new SubPubComponet("订阅器2");
//新建两个发布者
IPublish publisher1 = new Publisher("TJVictor1");
IPublish publisher2 = new Publisher("TJVictor2");
//与订阅器关联
publisher1.PublishEvent += subPubComponet1.PublishEvent;
publisher1.PublishEvent += subPubComponet2.PublishEvent;
publisher2.PublishEvent += subPubComponet2.PublishEvent;
//新建两个订阅者
Subscriber s1 = new Subscriber("订阅人1");
Subscriber s2 = new Subscriber("订阅人2");
//进行订阅
s1.AddSubscribe = subPubComponet1;
s1.AddSubscribe = subPubComponet2;
s2.AddSubscribe = subPubComponet2;
//发布者发布消息
publisher1.Notify("博客1");
publisher2.Notify("博客2");
//发送结束符号
Console.WriteLine("".PadRight(50,'-'));
//s1取消对订阅器2的订阅
s1.RemoveSubscribe = subPubComponet2;
//发布者发布消息
publisher1.Notify("博客1");
publisher2.Notify("博客2");
//发送结束符号
Console.WriteLine("".PadRight(50, '-'));
#endregion

#region Console.ReadLine();
Console.ReadLine();
#endregion

 运行结果图:

订阅发布模式3

 

 

如需转载,请注明本文原创自CSDN TJVictor专栏:http://blog.csdn.net/tjvictor

这篇关于设计模式---订阅发布模式(Subscribe/Publish)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/897455

相关文章

SpringKafka消息发布之KafkaTemplate与事务支持功能

《SpringKafka消息发布之KafkaTemplate与事务支持功能》通过本文介绍的基本用法、序列化选项、事务支持、错误处理和性能优化技术,开发者可以构建高效可靠的Kafka消息发布系统,事务支... 目录引言一、KafkaTemplate基础二、消息序列化三、事务支持机制四、错误处理与重试五、性能优

新特性抢先看! Ubuntu 25.04 Beta 发布:Linux 6.14 内核

《新特性抢先看!Ubuntu25.04Beta发布:Linux6.14内核》Canonical公司近日发布了Ubuntu25.04Beta版,这一版本被赋予了一个活泼的代号——“Plu... Canonical 昨日(3 月 27 日)放出了 Beta 版 Ubuntu 25.04 系统镜像,代号“Pluc

SpringBoot如何通过Map实现策略模式

《SpringBoot如何通过Map实现策略模式》策略模式是一种行为设计模式,它允许在运行时选择算法的行为,在Spring框架中,我们可以利用@Resource注解和Map集合来优雅地实现策略模式,这... 目录前言底层机制解析Spring的集合类型自动装配@Resource注解的行为实现原理使用直接使用M

C#原型模式之如何通过克隆对象来优化创建过程

《C#原型模式之如何通过克隆对象来优化创建过程》原型模式是一种创建型设计模式,通过克隆现有对象来创建新对象,避免重复的创建成本和复杂的初始化过程,它适用于对象创建过程复杂、需要大量相似对象或避免重复初... 目录什么是原型模式?原型模式的工作原理C#中如何实现原型模式?1. 定义原型接口2. 实现原型接口3

大数据spark3.5安装部署之local模式详解

《大数据spark3.5安装部署之local模式详解》本文介绍了如何在本地模式下安装和配置Spark,并展示了如何使用SparkShell进行基本的数据处理操作,同时,还介绍了如何通过Spark-su... 目录下载上传解压配置jdk解压配置环境变量启动查看交互操作命令行提交应用spark,一个数据处理框架

Nginx实现前端灰度发布

《Nginx实现前端灰度发布》灰度发布是一种重要的策略,它允许我们在不影响所有用户的情况下,逐步推出新功能或更新,通过灰度发布,我们可以测试新版本的稳定性和性能,下面就来介绍一下前端灰度发布的使用,感... 目录前言一、基于权重的流量分配二、基于 Cookie 的分流三、基于请求头的分流四、基于请求参数的分

定价129元!支持双频 Wi-Fi 5的华为AX1路由器发布

《定价129元!支持双频Wi-Fi5的华为AX1路由器发布》华为上周推出了其最新的入门级Wi-Fi5路由器——华为路由AX1,建议零售价129元,这款路由器配置如何?详细请看下文介... 华为 Wi-Fi 5 路由 AX1 已正式开售,新品支持双频 1200 兆、配有四个千兆网口、提供可视化智能诊断功能,建

Java实现状态模式的示例代码

《Java实现状态模式的示例代码》状态模式是一种行为型设计模式,允许对象根据其内部状态改变行为,本文主要介绍了Java实现状态模式的示例代码,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来... 目录一、简介1、定义2、状态模式的结构二、Java实现案例1、电灯开关状态案例2、番茄工作法状态案例

五大特性引领创新! 深度操作系统 deepin 25 Preview预览版发布

《五大特性引领创新!深度操作系统deepin25Preview预览版发布》今日,深度操作系统正式推出deepin25Preview版本,该版本集成了五大核心特性:磐石系统、全新DDE、Tr... 深度操作系统今日发布了 deepin 25 Preview,新版本囊括五大特性:磐石系统、全新 DDE、Tree

Linux Mint Xia 22.1重磅发布: 重要更新一览

《LinuxMintXia22.1重磅发布:重要更新一览》Beta版LinuxMint“Xia”22.1发布,新版本基于Ubuntu24.04,内核版本为Linux6.8,这... linux Mint 22.1「Xia」正式发布啦!这次更新带来了诸多优化和改进,进一步巩固了 Mint 在 Linux 桌面