系列主題:基于消息的軟體架構模型演變
.net中事件模型很優雅的實作了觀察者模式,同時被大量的使用在各種架構中。如果我們非要給事件模型挑毛病,我覺得有兩點:
- 實作起來略微繁瑣
- 正如我們上篇文章分析,事件模型在特定的情況下會發生記憶體洩漏
于是我們想到了更加簡單易用的模型:EventAggregator,正如該名稱所描述,EventAggregator将觀察者都聚合在一個容器裡,向EventAggregator釋出一個主題,EventAggregator會找到對該主題感興趣的觀察者并通知他。

Prism架構中實作了一個典型的EventAggregator,有時候我們又把此類實作叫做EventBus。
MVVM Light中實作了一個叫Messenger的bus,Messenger是EventBus、EventAggregator等概念的抽象,因為此時的主題已經表現的像是消息一樣,是以Messenger這一名稱也更加靠近基于消息架構這一主題。
一、實作一個簡單的EventBus
EventBus的職責有兩點:
- 注冊觀察者
- 釋出主題
是以接口定義為:
public interface ISimpleEventBus
{
void Register<TEvent>(Action<TEvent> action);
void Publish<TEvent>(TEvent @event);
}
所有的主題都可以用Action<TEvent> action來表示,實作起來也很簡單:
public class SimpleEventBus
{
public static ConcurrentDictionary<Type,List<Action<object>>> Dictionary=new ConcurrentDictionary<Type, List<Action<object>>>();
public void Register<TEvent>(Action<TEvent> action)
{
List<Action<object>> actionList;
if (!Dictionary.TryGetValue(typeof (TEvent), out actionList))
{
actionList=new List<Action<object>>();
Dictionary[typeof (TEvent)] = actionList;
}
actionList.Add(o=>action((TEvent)o));
}
public void Publish<TEvent>(TEvent @event)
{
List<Action<object>> actionList;
if (Dictionary.TryGetValue(typeof (TEvent), out actionList))
{
foreach (var action in actionList)
{
action(@event);
}
}
}
}
EventBus内部通過一個類型為ConcurrentDictionary<Type,List<Action<object>>> 的字典來存儲主題和觀察者清單。寫個測試試試:
[Test]
public void Should_handle_registered_action()
{
var eventBus = new SimpleEventBus();
var number = 0;
eventBus.Register<MessageA>(m=>number=m.Number);
eventBus.Publish(new MessageA(2));
number.Should().Be(2);
}
internal class MessageA
{
public MessageA(int number)
{
Number = number;
}
public int Number { get; private set; }
}
我們自己寫的這個simpleEventBus已經能夠應付大部分情況了,使用起來也比事件模型簡單很多。但是仍然沒有解決記憶體洩漏的問題。
二、MVVM Light中Messenger的實作
Messenger的實作是我們這個簡單eventBus的更新版,首先他抽象了概念,從事件到消息是思維的一個轉變,Messenger認為所有注冊的主題都是消息。其次采用WeakReference來關聯觀察者和主題。
public void Register<TMessage>(object recipient, Action<TMessage> action)
{
lock (_registerLock)
{
var messageType = typeof(TMessage);
Dictionary<Type, List<WeakAction>> recipients;
if (_recipientsStrictAction == null)
{
_recipientsStrictAction = new Dictionary<Type, List<WeakAction>>();
}
recipients = _recipientsStrictAction;
lock (recipients)
{
List<WeakAction> list;
if (!recipients.ContainsKey(messageType))
{
list = new List<WeakAction>();
recipients.Add(messageType, list);
}
else
{
list = recipients[messageType];
}
var weakAction = new WeakAction<TMessage>(recipient, action);
list.Add(weakAction);
}
}
RequestCleanup();
}
這個實作跟我們實作的EventBus大同小異,不同之處是dictionary類型為Dictionary<Type, List<WeakAction>>。
WeakAction的構造函數:
public WeakAction(object target, Action<T> action)
{
if (action.Method.IsStatic)
{
_staticAction = action;
if (target != null)
{
Reference = new WeakReference(target);
}
return;
}
Method = action.Method;
ActionReference = new WeakReference(action.Target);
Reference = new WeakReference(target);
}
ActionReference = new WeakReference(action.Target); 這句話将一個object包裝進了WeakReference。
發送消息的代碼也跟我們自己實作的EventBus大同小異,大家可以直接看代碼對比。
寫一個測試看看如何使用Messenger:
[Test]
public void Should_handle_registered_actions()
{
int number = 0;
Messenger.Default.Register<MessageA>(this,m=>number=m.Number);
Messenger.Default.Send(new MessageA(2));
number.Should().Be(2);
}
internal class MessageA
{
public MessageA(int number)
{
Number = number;
}
public int Number { get; private set; }
}
我們注意到Messenger采用了消息的概念,是以釋出主題也将方法名從publish改為了send。一般我們都說釋出一個事件,發送一個消息。Messenger所提到的概念已經快要接近ServiceBus了。
作者:Richie Zhang
來源:http://www.cnblogs.com/richieyang/
聲明:本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。