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

  • 相关阅读:
    使用阿里云接口进行银行卡四要素实名认证
    使用阿里云接口进行银行卡三要素实名认证(阿里云api接口java)
    如何使用阿里云进行人脸和身份证头像验证比对(人证核验接口API)--java
    PHP语言对用户输入的身份证信息进行实名认证(api接口)
    Python 语言如何对身份证真实性进行实名认证(api接口)
    如何对网站用户进行实名认证
    PLSQL Developer 连接oracle(64)(instantclient_32)
    struts2的防止表单重复提交
    JSP静态化(伪静态)
    Jmeter中压力测试带验证码的登录功能
  • 原文地址:https://www.cnblogs.com/itjeff/p/7095398.html
Copyright © 2020-2023  润新知