2008-12-05 57 views
4

「頂級」類將一個事件附加到一個可能在callstack中「5+層以下」的類的最佳設計決策是什麼?附加到C#中的調用堆棧中的事件的最佳方法?

例如,也許MainForm產生了一個對象,並且該對象產生了其他幾個對象調用的調用堆棧。最明顯的方法是將事件鏈接到對象層次結構,但這看起來很混亂,需要很多工作。

另一個解決方案是使用觀察者模式,通過創建一個可公開訪問的靜態對象來暴露事件,並充當底層對象和頂層「表單」之間的代理。

有什麼建議嗎?

這是一個僞代碼示例。在這個例子中,MainForm實例化'SomeObject',並附加到一個事件。 'SomeObject'附加到它實例化的對象上,以便將事件傳遞給MainForm偵聽器。

class Mainform 
{ 
    public void OnLoad() 
    { 
     SomeObject someObject = new SomeObject(); 
     someObject.OnSomeEvent += MyHandler; 
     someObject.DoStuff(); 
    } 

    public void MyHandler() 
    { 
    } 
} 



class SomeObject 
{ 
    public void DoStuff() 
    { 
     SomeOtherObject otherObject = new SomeOtherObject(); 
     otherObject.OnSomeEvent += MyHandler; 
     otherObject.DoStuff(); 
    } 


    public void MyHandler() 
    { 
     if(OnSomeEvent != null) 
      OnSomeEvent(); 
    } 


    public event Action OnSomeEvent; 
} 

回答

4

如果您的應用程序不是基於複合UI應用程序塊,最簡單的解決方案是在Main窗體和您的其他組件之間放置一個「偵聽器」類,這兩個類都可以輕鬆訪問。從概念上講,類佈局如下:

 
    ----------   ---------------- 
    | MainForm |  | Some Component | 
     ---------   ---------------- 
      |     | 
     Hooks onto   Notifies 
      |     | 
      \    /
      ----------------- 
      | Proxy Notifier | 
      ----------------- 

下面是一些示例代碼:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 

namespace ConsoleApplication1 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      FakeMainForm form = new FakeMainForm(); 
      form.CreateComponentAndListenForMessage(); 
      Console.ReadKey(true); 
     } 
    } 

    class FakeMainForm 
    { 
     public FakeMainForm() 
     { 
      Listener.AddListener(MessageRecieved); 
     } 

     void MessageRecieved(string msg) 
     { 
      Console.WriteLine("FakeMainForm.MessageRecieved: {0}", msg); 
     } 

     public void CreateComponentAndListenForMessage() 
     { 
      ComponentClass component = new ComponentClass(); 
      component.PretendToProcessData(); 
     } 
    } 

    class Listener 
    { 
     private static event Action<string> Notify; 

     public static void AddListener(Action<string> handler) 
     { 
      Notify += handler; 
     } 

     public static void InvokeListener(string msg) 
     { 
      if (Notify != null) { Notify(msg); } 
     } 
    } 

    class ComponentClass 
    { 
     public void PretendToProcessData() 
     { 
      Listener.InvokeListener("ComponentClass.PretendToProcessData() was called"); 
     } 
    } 
} 

這個程序的輸出如下:

FakeMainForm.MessageRecieved: ComponentClass.PretendToProcessData() was called

此代碼,您可以直接調用方法對任何聽衆來說,無論他們在調用堆棧中有多遠。

它很容易重寫你的Listener類,使它更通用一些,適用於不同類型,但你應該明白。

+0

很好的答案!謝謝。 – user43823 2008-12-06 20:14:57

0

我最初的意圖是試圖避免這種情況,以便一個對象的範圍有明顯的界限。在表格的特殊情況下,我會嘗試讓孩子的父母表單管理與其祖先進行的所有必需的通信。你能更具體地瞭解你的情況嗎?

0

我的第一個想法是,從你的MainForm的角度來看,它應該不知道5層下來會發生什麼。它應該只知道它與它產生的對象的相互作用。因此,如果主窗體想要異步執行某些操作,則應該可以通過異步調用生成的對象上的方法來實現該功能。如果你允許調用者異步執行某種方法,則不需要再向下推事件模型......只需將方法直接調用到堆棧中即可。你已經在另一個線程上。

希望這會有所幫助。只要記住你的應用程序的級別應該只知道它們下面的級別發生了什麼。

+0

我不希望我的MainForm'異步執行動作',我希望它附加到它實例化的對象的更新,而不需要它實例化的對象不得不形成對象層次結構'事件鏈'。 – user43823 2008-12-05 22:50:06

0

WPF使用routed events。這些是靜態的,可以冒泡或向下挖掘元素樹。我不知道你是否使用WPF,但靜態事件的想法可能會幫助你。

0

我不會說這是一個設計錯誤,主表單需要傾聽某個對象正在做什麼的正確理由。我遇到的一種情況是向用戶顯示狀態消息,以指示後臺進程在做什麼,或者多線程應用程序中有多個控件正在做什麼,這樣可以讓多個屏幕/「頁面」一次打開。

在複合用戶界面應用程序塊中,當其實例化對象在同一工作項目(工作項目只是一組相關用戶控件的對象容器)中時,依賴項注入容器的基本等價物將事件連接起來。它通過掃描特殊屬性來完成此操作,如事件[EventPublication("StatusChanged")]和公共方法[EventSubscription("StatusChanged")]。我的一個應用程序使用此功能,以便在應用程序內部實例化的用戶控件可以廣播狀態信息(例如「加載客戶數據... 45%」),而不知道該數據最終會以主窗體的狀態欄。

所以一個用戶控件可以做這樣的事情:


public void DoSomethingInTheBackground() 
{ 
    using (StatusNotification sn = new StatusNotification(this.WorkItem)) 
    { 
     sn.Message("Loading customer data...", 33); 
     // Block while loading the customer data.... 
     sn.Message("Loading order history...", 66); 
     // Block while loading the order history... 
     sn.Message("Done!", 100); 
    } 
} 

...其中StatusNotification類有一個event與像


[EventPublication("StatusChanged")] 
public event EventHandler<StatusEventArgs> StatusChanged; 

的簽名......和上述Message()Dispose()該類上的方法適當地調用該事件。但是這個班並沒有明確地把這個事件與任何事情聯繫起來。對象實例化器將自動將事件連接到具有相同名稱的訂閱屬性的任何人。

所以MainForm中有一個事件處理程序,它看起來是這樣的:


[EventSubscription("StatusChanged", ThreadOption=ThreadOption.UserInterface)] 
public void OnStatusChanged(object sender, StatusEventArgs e) 
{ 
    this.statusLabel.Text = e.Text; 
    if (e.ProgressPercentage != -1) 
    { 
     this.progressBar.Visible = true; 
     this.progressBar.Value = e.ProgressPercentage; 
    } 
} 

...或一些這樣的。它比這更復雜,因爲多個用戶控件可以在同一時間廣播狀態消息,因此它將通過多個狀態通知輪換給定的秒數。所以要重新創建這種行爲,而不實際切換到CAB(說實話,這比我想的真的要複雜得多),你可以有一個MessageNotificationService對象,你可以通過你的應用程序或你變成一個靜態/單一對象(我通常會避免這種方法,因爲它很難測試),或者你可以讓你的子控件由一個工廠類實例化,這個工廠類可以爲你的事件接線。對象可以通過自己創建的屬性向工廠註冊,也可以通過顯式調用「嘿,任何時候您創建帶有此簽名事件的對象,我想知道它」的方法來註冊。

只要注意讓任何你實現的類在一個對象被釋放的時候解開事件,因爲在這種情況下它很愚蠢,最終得到的東西不會被垃圾收集。

希望這會有所幫助!

+0

您有一個優雅的IoC解決方案。也許比我期望的更高級一個答案,但一個很酷的想法。感謝您的貢獻! =) – user43823 2008-12-06 20:14:23