Skip to content

风君子博客

  • 首页
  • 业界
  • 前端
  • 运维
  • 建站
  • 软件
  • 生活
  • 后端
  • 创投
  • 运营

设计模式—订阅发布模式(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

Published by

风君子

独自遨游何稽首 揭天掀地慰生平 View all posts by 风君子

Posted on 2024年3月7日Author 风君子Categories 软件Tags 设计模式---订阅发布模式(Subscribe/Publish)

文章导航

Previous Previous post: 初学者的卡尔曼滤波——扩展卡尔曼滤波(一)
Next Next post: 移动端px转rem的两种方法

近期文章

  • 出轨后如何重建信任
  • 昆明哪里可以摘油桃
  • 人体彩绘颜料哪里有卖
  • 如何办营业执照
  • 如何将pdf转换成jpg
  • 凯度秋季发布会亮相四款新品,以“紫科技”重塑厨房体验
  • p 图软件哪个好
  • 苹果手机最近删除在哪里
  • 如何判断是不是心绞痛
  • 有哪些公众号有资源
怎么判断自己有没有抑郁 26个英文字母表 农历公历转换 安全期计算器 孕周计算器 预产期计算器 胎儿体重计算器 孩子血型计算器 生男生女清宫图 生肖查询 日期计算器 农历查询 生辰八字 年龄计算器 退休年龄计算器 在线秒表 24节气 时间戳转换 12时辰 倒计时器 IP地址计算器 IPV6测试 eomji表情大全

标签

  • AI
  • AMD
  • iphone
  • IT资讯
  • 三星
  • 京东
  • 人工智能
  • 信用卡
  • 利息
  • 华为
  • 小米
  • 微软
  • 快科技
  • 手机
  • 投资理财
  • 支付宝
  • 教程
  • 新能源汽车
  • 显卡
  • 汽车
  • 流量
  • 淘宝
  • 游戏
  • 特斯拉
  • 理财知识
  • 电动汽车
  • 电脑
  • 科技
  • 秘籍
  • 程序
  • 网上
  • 美国
  • 股票
  • 腾讯
  • 芯片
  • 英特尔
  • 苹果
  • 荣耀
  • 谷歌
  • 贷款
  • 路由器
  • 银行
  • 银行卡
  • 额度
  • 马斯克

Copyright © 2025 风君子博客 豫ICP备2022027272号

Designed by 风君子. 11次查询,耗时0.233秒..