.NET中把「事件」看做一個基本的編程概念,並提供了很是優美的語法支持,對好比下C#和Java代碼能夠看出兩種語言設計思想之間的差別。node
// C#
someButton.Click += OnSomeButtonClick;
// Java
someButton.addActionListener( new ActionListener(){ public void actionPerformed(){ ... } });
在咱們的軟件中就大量使用事件來對監聽者與發佈者解耦,但也遇到了一些侷限,在這裏跟你們分享一二。一是沒法保證監聽者的調用順序;二是當監聽者不少時的監聽、解除監聽的效率問題。編程
.NET的事件監聽機制對監聽者的調用順序沒有明確的保證,但有時咱們卻要求保證不一樣組件之間的處理順序。好比,在咱們的軟件中使用相似解釋器模式的方式來實現用戶交互操做,一個稱做交互源的組件負責將UI控件上的事件分派給一組稱爲交互器的組件,這些組件依照事先肯定的優先級依次得到事件處理的機會,只有當具備高優先級的交互器沒有處理事件時,低優先級的組件才能執行進一步的處理。這樣,咱們就能在不一樣業務功能的實現中經過以不一樣的順序組織交互器來重用它們。好比,重用一些基本的視圖縮放、平移、菜單處理等功能。數組
在上述場景下,如何保證交互器間事件處理的順序就變得很重要了。固然若是你看一下MulticastDelegate的源代碼的話,能夠知道在當前的實現中其實各個監聽者仍是有必定的調用順序的。但一來這屬於實現細節,在未來徹底可能改變;二來若是不一樣的監聽器位於不一樣的模塊中時,要依賴於這一實現而保證它們之間的調用順序也是很困難的。數據結構
在這裏咱們借鑑了Java中以接口進行事件處理的方式,並在添加監聽器的同時接收一個表示優先級的參數,這樣就能夠明確的維護各個監聽器的順序了,以下面的代碼所示。咱們在交互器(IInteractor)接口中爲每個UI事件定義了相應的方法,而且讓InteractSource負責將控件上的事件轉化爲對接口中相應方法的調用。spa
public class InteractSource { public void AddInteractor(int priority, IInteractor interactor) { } } public interface IInteractor { public void OnMouseDown(MouseEventArgs e) { } ... ... }
MulticastDelegate是咱們日常使用的事件(event)機制背後的實現,經過其源代碼能夠看到,它在內部使用數組保存了對各個監聽器的引用。這就會形成一個問題——當對一個事件的監聽器數目不少時,添加和移除監聽器的效率將會變得很是低。以移除爲例,對於有N個監聽器的事件來講,平均要進行N/2次比較才能肯定監聽器的位置,並且還要有額外的數組整理操做。爲了解決這一狀況,咱們先是嘗試自行定義事件的添加、移除邏輯,並在內部嘗試使用字典、哈希表等多種方式進行存儲,但事實證實,雖然兩者在時間複雜度上有優點,不過其實際效率仍是達不到要求。設計
最好狀態下是要有一種能在常數時間內添加和移除監聽器的數據結構,也許你也想到了——雙向鏈表。code
也許你又想到了——在雙向鏈表中添加和刪除是常數時間,但查找卻仍然是O(n)的複雜度。orm
使用接口形式的設計方式再次展示了其靈活性,咱們能夠將事件發佈者的設計爲以下形式(示意代碼):對象
public class EventSource { private LinkedList list = new LinkedList(); public Tocken AddListener(IEventListener listener) { LinkedListNode n = new LinkedListNode(listener); list.AddLast(n); return new Tocken(node); } public void RemoveListener(Tocken tocken) { list.Remoe(tocken.node); } public class Tocken { internal LinkedListNode node; } }
在此類中使用雙向鏈表存儲已經添加的監聽器,而在AddListener方法每次調用時都將所添加的鏈表節點保存到一個令牌(Token)中返回。監聽者須要保存這個令牌,並使用它來解除監聽。固然,監聽者徹底能夠忽略令牌是個什麼東西,就像地鐵票歷來就是隻是一張票而已,咱們未曾關心它包含着什麼信息。不過對於發佈者來講卻能夠將一些定位信息保存在其中,從而在解除監聽時充分利用,在上面的代碼中我就保存了鏈表節點的引用,從而達到監聽者的添加、定位、移除都在常數時間內完成。blog
固然,還能夠在Tocken中保存發佈者的引用,這樣就能夠發現」取消對一個歷來沒有監聽過的對象的監聽「這樣的BUG。或者,還有其它信息。