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

作者:HueiFeng 时间:2021-08-14 05:31:39 

什么是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

来源:https://www.cnblogs.com/yyfh/p/12933597.html

标签:c#,设计模式,订阅发布模式
0
投稿

猜你喜欢

  • C# URL短地址压缩算法及短网址原理解析

    2023-03-26 08:36:31
  • java String的深入理解

    2022-10-30 23:50:37
  • Spring Cloud Config配置文件使用对称加密的方法

    2021-08-09 08:50:02
  • SpringBoot2 Jpa 批量删除功能的实现

    2023-06-18 04:54:35
  • IDEA教程之Activiti插件图文详解

    2023-11-14 23:06:45
  • java 数据结构单链表的实现

    2022-07-24 09:45:33
  • c#创建圆形类Circle、矩形类实现代码

    2023-08-18 19:59:12
  • Java多线程实现第三方数据同步

    2023-06-30 10:57:57
  • Java web访问localhost报404错误问题的解决方法

    2023-07-27 05:28:55
  • 必须要学会的JMM与volatile

    2021-07-30 14:07:53
  • Java 转型(向上或向下转型)详解及简单实例

    2021-10-17 14:29:27
  • java生成json数据示例

    2023-10-30 04:06:45
  • Java中包装类介绍与其注意事项

    2023-03-20 18:26:36
  • java web中 HttpClient模拟浏览器登录后发起请求

    2022-10-27 23:07:05
  • C#动态加载dll扩展系统功能的方法

    2022-08-08 23:23:15
  • SpringMVC bean加载控制的实现分析

    2023-08-08 18:46:19
  • Java制作智能拼图游戏原理及代码

    2022-08-02 21:46:57
  • 聊聊Java的switch为什么不支持long

    2023-08-24 17:35:14
  • 详解java接口基础知识附思维导图

    2023-11-09 10:07:28
  • 一文给你通俗易懂的讲解Java异常

    2021-12-20 14:40:56
  • asp之家 软件编程 m.aspxhome.com