C# 基于消息發(fā)布訂閱模型的示例(上)
在我們的開(kāi)發(fā)過(guò)程中,我們經(jīng)常會(huì)遇到這樣的場(chǎng)景就是一個(gè)對(duì)象的其中的一些狀態(tài)依賴(lài)于另外的一個(gè)對(duì)象的狀態(tài),而且這兩個(gè)對(duì)象之間彼此是沒(méi)有關(guān)聯(lián)的,及兩者之間的耦合性非常低,特別是在這種基于容器模型的開(kāi)發(fā)中遇到的會(huì)非常多,比如Prism框架或者M(jìn)EF這種框架中,而我們會(huì)發(fā)現(xiàn)在這樣的系統(tǒng)中我們經(jīng)常使用一種Publish和Subscribe的模式來(lái)進(jìn)行交互,這種交互有什么好處呢?基于帶著這些問(wèn)題的思考,我們來(lái)一步步來(lái)剖析!
首先第一步就是定義一個(gè)叫做IEventAggregator的接口,里面定義了一些重載的Subscribe和Publish方法,我們具體來(lái)看一看這個(gè)接口:
/// <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);
}
有了這個(gè)接口,接下來(lái)就是怎樣去實(shí)現(xiàn)這個(gè)接口中的各種方法,我們來(lái)看看具體的實(shí)現(xiàn)過(guò)程。
/// <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的內(nèi)部維護(hù)了一個(gè)LIst<Handler>的List對(duì)象,用來(lái)存放一系列的Handle,那么這個(gè)嵌套類(lèi)Handler到底起什么作用呢?
我們會(huì)發(fā)現(xiàn)在每一次當(dāng)執(zhí)行這個(gè)Subscribe的方法的時(shí)候,會(huì)將當(dāng)前object類(lèi)型的參數(shù)instance傳入到Handler這個(gè)對(duì)象中,在Handler這個(gè)類(lèi)的構(gòu)造函數(shù)中,首先將這個(gè)instance放入到一個(gè)弱引用中去,然后再獲取這個(gè)對(duì)象所有繼承的接口,并查看是否繼承了IHandle<TMessage>這個(gè)泛型的接口,如果能夠獲取到,那么就通過(guò)反射獲取到當(dāng)前instance中定義的Handle方法,并獲取到其中定義的表示泛型類(lèi)型的類(lèi)型實(shí)參或泛型類(lèi)型定義的類(lèi)型形參,并把這兩個(gè)對(duì)象放到內(nèi)部定義的一個(gè)Dictionary<Type, MethodInfo>字典之中,這樣就把這樣一個(gè)活得具體的處理方法的Handler對(duì)象放到了一個(gè)List<Handler>集合中,這個(gè)就是訂閱消息的核心部分,所以當(dāng)前的對(duì)象要想訂閱一個(gè)消息,那么必須實(shí)現(xiàn)泛型接口IHandle<TMessage>,并且實(shí)現(xiàn)接口中的方法,同時(shí)最重要的就是在當(dāng)前對(duì)象的構(gòu)造函數(shù)函數(shù)中去訂閱消息(即執(zhí)行Subscribe(this),我們來(lái)看一看這個(gè)泛型接口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這個(gè)方法后,后面我們就來(lái)看看Unsubscribe方法吧,這個(gè)思路其實(shí)很簡(jiǎn)單就是找到List<Handler>中的這個(gè)對(duì)象,并且移除當(dāng)前的對(duì)象就可以了,那么下面我們關(guān)注的重點(diǎn)就是Publish這個(gè)方法中到底實(shí)現(xiàn)了什么?首先來(lái)看看代碼,然后再來(lái)做一步步分析?!?/p>
/// <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);
}
}
}
});
}
我們看到,在發(fā)布一個(gè)object類(lèi)型的message的時(shí)候,必然對(duì)應(yīng)著另外的一個(gè)對(duì)象來(lái)處理這個(gè)消息,那么怎樣找到這個(gè)消息的處理這呢?
對(duì),我們?cè)赟ubscribe一個(gè)對(duì)象的時(shí)候不是已經(jīng)通過(guò)反射將訂閱這個(gè)消息的對(duì)象及方法都存在了一個(gè)List<Handler>中去了嗎?那么我們只需要在這個(gè)List中找到對(duì)應(yīng)的和message類(lèi)型一致的那個(gè)對(duì)象并執(zhí)行里面的Handle方法不就可以了嗎?確實(shí)是一個(gè)很好的思路,這里我們看代碼也是這樣實(shí)行的。
這里面還有一個(gè)要點(diǎn)就是,如果執(zhí)行的方法返回了false,就是執(zhí)行不成功,那么就從當(dāng)前的List<Handler>中移除掉這個(gè)對(duì)象,因?yàn)檫@樣的操作是沒(méi)有任何意義的,通過(guò)這樣的過(guò)程我們就能夠完沒(méi)地去實(shí)現(xiàn)兩個(gè)對(duì)象之間的消息傳遞了,另外我們通過(guò)總結(jié)以后就能夠發(fā)現(xiàn),這個(gè)思路實(shí)現(xiàn)的重點(diǎn)包括以下方面:
1 所有消息訂閱的對(duì)象必須實(shí)現(xiàn)統(tǒng)一的接口IHandle<TMessage>,并實(shí)現(xiàn)里面的Handel方法。
2 整個(gè)EventAggregator必須是單實(shí)例或者是靜態(tài)的,這樣才能夠在統(tǒng)一的集合中去實(shí)現(xiàn)上述的各種操作。
最后還是按照之前的慣例,最后給出一個(gè)具體的實(shí)例來(lái)做相關(guān)的說(shuō)明,請(qǐng)點(diǎn)擊此處進(jìn)行下載,在下篇中我們將介紹一種簡(jiǎn)單版的基于事件的發(fā)布和訂閱模式的例子。
以上就是C# 基于消息發(fā)布訂閱模型的示例(上)的詳細(xì)內(nèi)容,更多關(guān)于c# 發(fā)布訂閱模型的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- c#使用win32api實(shí)現(xiàn)獲取光標(biāo)位置
- C# WPF 自定義按鈕的方法
- C# 中對(duì)象序列化XML的方法
- C# MJPEG 客戶(hù)端簡(jiǎn)單實(shí)現(xiàn)方法
- c# WPF中通過(guò)雙擊編輯DataGrid中Cell的示例(附源碼)
- C# WPF Image控件的綁定方法
- c# WPF設(shè)置軟件界面背景為MediaElement并播放視頻
- c# WPF中如何自定義MarkupExtension
- C# 基于消息發(fā)布訂閱模型的示例(下)
- c# 調(diào)用Win32Api關(guān)閉當(dāng)前應(yīng)用的方法
相關(guān)文章
C#數(shù)據(jù)結(jié)構(gòu)之雙向鏈表(DbLinkList)實(shí)例詳解
這篇文章主要介紹了C#數(shù)據(jù)結(jié)構(gòu)之雙向鏈表(DbLinkList),結(jié)合實(shí)例形式較為詳細(xì)的講解了雙向鏈表的概念及C#實(shí)現(xiàn)雙向鏈表的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11
winform實(shí)現(xiàn)拖動(dòng)文件到窗體上的方法
這篇文章主要介紹了winform實(shí)現(xiàn)拖動(dòng)文件到窗體上的方法,以實(shí)例分析了C#中WinForm操作窗體及文件的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-09-09
C#使用TcpListener及TcpClient開(kāi)發(fā)一個(gè)簡(jiǎn)單的Chat工具實(shí)例
下面小編就為大家分享一篇C#使用TcpListener及TcpClient開(kāi)發(fā)一個(gè)簡(jiǎn)單的Chat工具實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2017-12-12
C#中單問(wèn)號(hào)(?)和雙問(wèn)號(hào)(??)的用法整理
本文詳細(xì)講解了C#中單問(wèn)號(hào)(?)和雙問(wèn)號(hào)(??)的用法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-05-05

