深入了解C#设计模式之订阅发布模式

所属分类: 软件编程 / C#教程 阅读数: 133
收藏 0 赞 0 分享

什么是Pub-Sub

发布订阅是一种设计模式,它允许应用程序组件之间进行松散耦合。
其实订阅发布设计中主要是发布者生成事件通道,用于在不了解任何订阅者存在的情况下通知订阅者。

当然委托EventHandlers和Event关键字在此事件处理机制中担任着重要的角色。下面我们来看看如何使用它们。

Pub和Sub的使用

首先我们看一个简单地订阅发布模式.

定义一个Action委托,无返回值.

namespace PubSubPattern
{
  public class Pub
  {
    public Action OnChange { get; set; }

    public void Raise()
    {
      if (OnChange != null)
      {
        //Invoke OnChange Action
        OnChange();
      }
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      var p = new Pub();
      p.OnChange += () => Console.WriteLine("Sub 1");

      p.OnChange += () => Console.WriteLine("Sub 2");

      p.Raise();

      Console.WriteLine("Press enter !");
      Console.ReadLine();

    }
  }
}

如上代码我们创建了一个发布者,并且我们调用委托进行创建我们匿名方法来订阅。由于委托提供了多播功能,因此我们可以OnChange属性上使用+=.

虽然说我们看着如上代码执行无误,但是程序中仍然存在一些问题,如果使用=而不是+=,那么OnChange属性中将会删除第一个订阅者。
由于OnChange是公共属性,因此该类的任何外部用户都可以进行调用p.OnChange().

使用Event关键字的发布订阅

下面我们来看看使用event关键字后的代码

  public class Pub
  {
    public event Action OnChange = delegate { };

    public void Raise()
    {
      OnChange();
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      Pub p = new Pub();
      p.OnChange += () => Console.WriteLine("Sub 1");
      p.OnChange += () => Console.WriteLine("Sub 2");
      p.Raise();
      Console.WriteLine("Press enter !");
      Console.ReadLine();
    }
  }

通过如上代码我们试着去解决我们第一处所说的问题,我们会发现使用event关键字后可以保护我们OnChange免受不必要的访问。它不允许使用=也就是说他不允许直接进行分配委托,因此我们现在可以避免使用=,从而避免应用程序不必要的麻烦。

可能大家也会发现OnChange初始化为空委托delegate{}。这样可以确保我们的OnChange永远不会为空。因为当我们其他进行对他调用的时候我们可以在代码中进行删除对他的非空检查.

使用EventHandlers的发布订阅

其实在订阅发布中,发布者和订阅者都不知道彼此的存在。有个EventHandler,它被称为消息代理或者说事件总线,发布者和订阅者都应该知道它,它接收所有传入的消息并且将它们进行转发.

因此呢,在如下片段中我们使用EventHandler而不是用Action.

public delegate void EventHandler(
  object sender,
  EventArgs e
)

默认情况下,EventHandler将发送对象和一些事件参数作为参数。

 public class MyEventArgs : EventArgs
    {
      public int Value { get; set; }

      public MyEventArgs(int value)
      {
        Value = value;
      }
    }

    public class Pub
    {
      public event EventHandler<MyEventArgs> OnChange = delegate { };
      public void Raise()
      {
        OnChange(this, new MyEventArgs(1));
      }
    }
    class Program
    {
      static void Main(string[] args)
      {
        Pub p = new Pub();
        p.OnChange += (sender, e) => Console.WriteLine("Sub 1.Value:" + e.Value);
        p.OnChange += (sender, e) => Console.WriteLine("Sub 2.Value:" + e.Value);
        p.Raise();
        Console.WriteLine("Press enter !");
        Console.ReadLine();
      }
    }

如上代码中通过pub类使用通用的EventHandler,它触发EventHandler OnChange时需要传递的事件参数类型,在上面代码片段中为MyArgs

事件中的异常

我们继续说一种情况.大家看如下代码片段

  public class MyEventArgs : EventArgs
  {
    public int Value { get; set; }

    public MyEventArgs(int value)
    {
      Value = value;
    }
  }

  public class Pub
  {
    public event EventHandler<MyEventArgs> OnChange = delegate { };
    public void Raise()
    {
      OnChange(this, new MyEventArgs(1));
    }
  }
  class Program
  {
    static void Main(string[] args)
    {
      Pub p = new Pub();
      p.OnChange += (sender, e) => Console.WriteLine("Sub 1.Value:" + e.Value);
      p.OnChange += (sender, e) => { throw new Exception(); };
      p.OnChange += (sender, e) => Console.WriteLine("Sub 2.Value:" + e.Value);
      p.Raise();
      Console.WriteLine("Press enter !");
      Console.ReadLine();
    }
  }

运行如上代码后,大家会发现第一个订阅者已经执行成功了,第二个订阅者引发了异常,而第三个订阅者未被调用.这是一个很尴尬的事情.

如果说我们觉得如上的过程不是我们预期的,我们需要手动引发事件并处理异常,这时候我们可以使用Delegate基类中定义的GetInvoctionList来帮助我们实现这些。

我们继续看如下代码

public class MyEventArgs : EventArgs
    {
      public int Value { get; set; }

      public MyEventArgs(int value)
      {
        Value = value;
      }
    }

    public class Pub
    {

      public event EventHandler<MyEventArgs> OnChange = delegate { };

      public void Raise()
      {
        MyEventArgs eventArgs = new MyEventArgs(1);

        List<Exception> exceptions = new List<Exception>();

        foreach (Delegate handler in OnChange.GetInvocationList())
        {
          try
          {
            handler.DynamicInvoke(this, eventArgs);
          }
          catch (Exception e)
          {
            exceptions.Add(e);
          }
        }

        if (exceptions.Any())
        {
          throw new AggregateException(exceptions);
        }
      }
    }
    class Program
    {
      static void Main(string[] args)
      {
        Pub p = new Pub();
        p.OnChange += (sender, e) => Console.WriteLine("Sub 1.Value:" + e.Value);
        p.OnChange += (sender, e) => { throw new Exception(); };
        p.OnChange += (sender, e) => Console.WriteLine("Sub 2.Value:" + e.Value);
        p.Raise();
        Console.WriteLine("Press enter !");
        Console.ReadLine();
      }
    }

Reference

https://github.com/hueifeng/DesignPatterns-Samples/tree/master/PubSubPattern

https://hackernoon.com/observer-vs-pub-sub-pattern-50d3b27f838c

以上就是深入了解C#设计模式之订阅发布模式的详细内容,更多关于c# 订阅发布模式的资料请关注脚本之家其它相关文章!

更多精彩内容其他人还在看

c#开发word批量转pdf源码分享

已经安装有Office环境,借助一些简单的代码即可实现批量Word转PDF,看下面的实例源码吧
收藏 0 赞 0 分享

c# xml API操作的小例子

这篇文章主要介绍了c# xml API操作的小例子,有需要的朋友可以参考一下
收藏 0 赞 0 分享

c#唯一值渲染实例代码

这篇文章主要介绍了c#唯一值渲染实例代码,有需要的朋友可以参考一下
收藏 0 赞 0 分享

淘宝IP地址库采集器c#代码

这篇文章主要介绍了淘宝IP地址库采集器c#代码,有需要的朋友可以参考一下
收藏 0 赞 0 分享

C#在后台运行操作(BackgroundWorker用法)示例分享

BackgroundWorker类允许在单独的专用线程上运行操作。如果需要能进行响应的用户界面,而且面临与这类操作相关的长时间延迟,则可以使用BackgroundWorker类方便地解决问题,下面看示例
收藏 0 赞 0 分享

c#文本加密程序代码示例

这是一个加密软件,但只限于文本加密,加了窗口控件的滑动效果,详细看下面的代码
收藏 0 赞 0 分享

c#生成站点地图(SiteMapPath)文件示例程序

这篇文章主要介绍了c#生成站点地图(SiteMapPath)文件的示例,大家参考使用
收藏 0 赞 0 分享

C# 键盘Enter键取代Tab键实现代码

这篇文章主要介绍了C# 键盘Enter键取代Tab键实现代码,有需要的朋友可以参考一下
收藏 0 赞 0 分享

C# WinForm导出Excel方法介绍

在.NET应用中,导出Excel是很常见的需求,导出Excel报表大致有以下三种方式:Office PIA,文件流和NPOI开源库,本文只介绍前两种方式
收藏 0 赞 0 分享

C#串口通信程序实例详解

在.NET平台下创建C#串口通信程序,.NET 2.0提供了串口通信的功能,其命名空间是System.IO.Ports,创建C#串口通信程序的具体实现是如何的呢?让我们开始吧
收藏 0 赞 0 分享
查看更多