C#基于消息发布订阅模型详解(上)
在我们的开发过程中,我们经常会遇到这样的场景就是一个对象的其中的一些状态依赖于另外的一个对象的状态,而且这两个对象之间彼此是没有关联的,及两者之间的耦合性非常低,特别是在这种基于容器模型的开发中遇到的会非常多,比如Prism框架或者MEF这种框架中,而我们会发现在这样的系统中我们经常使用一种Publish和Subscribe的模式来进行交互,这种交互有什么好处呢?基于带着这些问题的思考,我们来一步步来剖析!
首先第一步就是定义一个叫做IEventAggregator的接口,里面定义了一些重载的Subscribe和Publish方法,我们具体来看一看这个接口:
/// <summary> /// Enables loosely-coupled publication of and subscription to events. /// </summary> public interface IEventAggregator { /// <summary> /// Gets or sets the default publication thread marshaller. /// </summary> /// <value> /// The default publication thread marshaller. /// </value> Action<System.Action> PublicationThreadMarshaller { get; set; } /// <summary> /// Subscribes an instance to all events declared through implementations of <see cref = "IHandle{T}" /> /// </summary> /// <param name = "instance">The instance to subscribe for event publication.</param> void Subscribe(object instance); /// <summary> /// Unsubscribes the instance from all events. /// </summary> /// <param name = "instance">The instance to unsubscribe.</param> void Unsubscribe(object instance); /// <summary> /// Publishes a message. /// </summary> /// <param name = "message">The message instance.</param> /// <remarks> /// Uses the default thread marshaller during publication. /// </remarks> void Publish(object message); /// <summary> /// Publishes a message. /// </summary> /// <param name = "message">The message instance.</param> /// <param name = "marshal">Allows the publisher to provide a custom thread marshaller for the message publication.</param> void Publish(object message, Action<System.Action> marshal); }
有了这个接口,接下来就是怎样去实现这个接口中的各种方法,我们来看看具体的实现过程。
/// <summary> /// Enables loosely-coupled publication of and subscription to events. /// </summary> public class EventAggregator : IEventAggregator { /// <summary> /// The default thread marshaller used for publication; /// </summary> public static Action<System.Action> DefaultPublicationThreadMarshaller = action => action(); readonly List<Handler> handlers = new List<Handler>(); /// <summary> /// Initializes a new instance of the <see cref = "EventAggregator" /> class. /// </summary> public EventAggregator() { PublicationThreadMarshaller = DefaultPublicationThreadMarshaller; } /// <summary> /// Gets or sets the default publication thread marshaller. /// </summary> /// <value> /// The default publication thread marshaller. /// </value> public Action<System.Action> PublicationThreadMarshaller { get; set; } /// <summary> /// Subscribes an instance to all events declared through implementations of <see cref = "IHandle{T}" /> /// </summary> /// <param name = "instance">The instance to subscribe for event publication.</param> public virtual void Subscribe(object instance) { lock(handlers) { if (handlers.Any(x => x.Matches(instance))) { return; } handlers.Add(new Handler(instance)); } } /// <summary> /// Unsubscribes the instance from all events. /// </summary> /// <param name = "instance">The instance to unsubscribe.</param> public virtual void Unsubscribe(object instance) { lock(handlers) { var found = handlers.FirstOrDefault(x => x.Matches(instance)); if (found != null) { handlers.Remove(found); } } } /// <summary> /// Publishes a message. /// </summary> /// <param name = "message">The message instance.</param> /// <remarks> /// Does not marshall the the publication to any special thread by default. /// </remarks> public virtual void Publish(object message) { Publish(message, PublicationThreadMarshaller); } /// <summary> /// Publishes a message. /// </summary> /// <param name = "message">The message instance.</param> /// <param name = "marshal">Allows the publisher to provide a custom thread marshaller for the message publication.</param> public virtual void Publish(object message, Action<System.Action> marshal) { Handler[] toNotify; lock (handlers) { toNotify = handlers.ToArray(); } marshal(() => { var messageType = message.GetType(); var dead = toNotify .Where(handler => !handler.Handle(messageType, message)) .ToList(); if(dead.Any()) { lock(handlers) { foreach(var handler in dead) { handlers.Remove(handler); } } } }); } protected class Handler { readonly WeakReference reference; readonly Dictionary<Type, MethodInfo> supportedHandlers = new Dictionary<Type, MethodInfo>(); public Handler(object handler) { reference = new WeakReference(handler); var interfaces = handler.GetType().GetInterfaces() .Where(x => typeof(IHandle).IsAssignableFrom(x) && x.IsGenericType); foreach(var @interface in interfaces) { var type = @interface.GetGenericArguments()[0]; var method = @interface.GetMethod("Handle"); supportedHandlers[type] = method; } } public bool Matches(object instance) { return reference.Target == instance; } public bool Handle(Type messageType, object message) { var target = reference.Target; if(target == null) return false; foreach(var pair in supportedHandlers) { if(pair.Key.IsAssignableFrom(messageType)) { pair.Value.Invoke(target, new[] { message }); return true; } } return true; } } }
首先在EventAggregator的内部维护了一个LIst<Handler>的List对象,用来存放一系列的Handle,那么这个嵌套类Handler到底起什么作用呢?
我们会发现在每一次当执行这个Subscribe的方法的时候,会将当前object类型的参数instance传入到Handler这个对象中,在Handler这个类的构造函数中,首先将这个instance放入到一个弱引用中去,然后再获取这个对象所有继承的接口,并查看是否继承了IHandle<TMessage>这个泛型的接口,如果能够获取到,那么就通过反射获取到当前instance中定义的Handle方法,并获取到其中定义的表示泛型类型的类型实参或泛型类型定义的类型形参,并把这两个对象放到内部定义的一个Dictionary<Type, MethodInfo>字典之中,这样就把这样一个活得具体的处理方法的Handler对象放到了一个List<Handler>集合中,这个就是订阅消息的核心部分,所以当前的对象要想订阅一个消息,那么必须实现泛型接口IHandle<TMessage>,并且实现接口中的方法,同时最重要的就是在当前对象的构造函数函数中去订阅消息(即执行Subscribe(this),我们来看一看这个泛型接口IHandle<TMessage>
public interface IHandle {} /// <summary> /// Denotes a class which can handle a particular type of message. /// </summary> /// <typeparam name = "TMessage">The type of message to handle.</typeparam> public interface IHandle<TMessage> : IHandle { /// <summary> /// Handles the message. /// </summary> /// <param name = "message">The message.</param> void Handle(TMessage message); }
在看完了Subscribe这个方法后,后面我们就来看看Unsubscribe方法吧,这个思路其实很简单就是找到List<Handler>中的这个对象,并且移除当前的对象就可以了,那么下面我们关注的重点就是Publish这个方法中到底实现了什么?首先来看看代码,然后再来做一步步分析。
/// <summary> /// Publishes a message. /// </summary> /// <param name = "message">The message instance.</param> /// <param name = "marshal">Allows the publisher to provide a custom thread marshaller for the message publication.</param> public virtual void Publish(object message, Action<System.Action> marshal) { Handler[] toNotify; lock (handlers) { toNotify = handlers.ToArray(); } marshal(() => { var messageType = message.GetType(); var dead = toNotify .Where(handler => !handler.Handle(messageType, message)) .ToList(); if(dead.Any()) { lock(handlers) { foreach(var handler in dead) { handlers.Remove(handler); } } } }); }
我们看到,在发布一个object类型的message的时候,必然对应着另外的一个对象来处理这个消息,那么怎样找到这个消息的处理这呢?
对,我们在Subscribe一个对象的时候不是已经通过反射将订阅这个消息的对象及方法都存在了一个List<Handler>中去了吗?那么我们只需要在这个List中找到对应的和message类型一致的那个对象并执行里面的Handle方法不就可以了吗?确实是一个很好的思路,这里我们看代码也是这样实行的。
这里面还有一个要点就是,如果执行的方法返回了false,就是执行不成功,那么就从当前的List<Handler>中移除掉这个对象,因为这样的操作是没有任何意义的,通过这样的过程我们就能够完没地去实现两个对象之间的消息传递了,另外我们通过总结以后就能够发现,这个思路实现的重点包括以下方面:
1 所有消息订阅的对象必须实现统一的接口IHandle<TMessage>,并实现里面的Handel方法。
2 整个EventAggregator必须是单实例或者是静态的,这样才能够在统一的集合中去实现上述的各种操作。
最后还是按照之前的惯例,最后给出一个具体的实例来做相关的说明,请点击此处进行下载,在下篇中我们将介绍一种简单版的基于事件的发布和订阅模式的例子。