2009-12-16 88 views
9

我目前正在編寫一個C#庫以簡化實現小物理模擬/實驗。C#中的虛擬方法或事件#

主要組件是SimulationForm,它在內部運行定時器循環並隱藏用戶的樣板代碼。

  1. Init()(初始化一切)
  2. Render(Graphics g)(渲染當前的仿真狀態)
  3. Move(double dt)(移動實驗dt秒)

我:實驗本身只是通過三種方法來定義只是想知道讓用戶實現這些功能有什麼更好的選擇:

1)虛擬方法,由繼承的形式覆蓋

protected virtual void Init() {} 
... 

2)活動

public event EventHandler<MoveSimulationEventArgs> Move = ... 
... 

編輯:注意方法應該是不抽象反正。實際上,還有更多,其中沒有一個已經實現了。因爲許多模擬不需要它們,所以將它們排除在外通常很方便。

很酷的事情有關這是一個「範式」是,你可以寫

partial class frmMyExperiment : SimulationForm { 
} 

,你就完全能夠與設計師和所有繼承的控制和設置/特性交互。我不想通過完全不同的方法失去這個功能。

回答

17

我更喜歡在這種情況下使用虛擬方法。

此外,如果方法是要求爲了功能,您可以使您的類爲abstract class。這在可用性方面是最好的,因爲它會導致編譯器強制使用。如果用戶試圖在不實現方法的情況下使用你的類/表單,編譯器會發出抱怨。

事件在這裏有點不合適,因爲這實際上不是你想要的不僅僅是單個實現(事件允許多個訂閱者),而且它在概念上更多地是對象的功能,而且更少由對象引起的通知。

+2

對所需方法的「抽象」+1。 – 2009-12-16 20:29:58

+1

IMO,摘要FTW。 – Yoopergeek 2009-12-16 20:30:32

+0

摘要不適合,但「功能但notifcation」是一個好點+1 – Dario 2009-12-16 20:44:30

3

還有另一種選擇,從函數式編程中借用:將擴展點公開爲類型爲Func或Action的屬性,並讓您的實驗因此提供委託。

例如,在你的模擬亞軍,你必須:

public class SimulationForm 
{ 
    public Action Init { get; set; } 
    public Action<Graphics> Render { get; set; } 
    public Action<double> Move { get; set; } 

    //... 
} 

而且在你的模擬你必須:

public class Simulation1 
{ 
    private SimulationForm form; 

    public void Simulation1() 
    { 
     form = new SimulationForm(); 
     form.Init = Init; 
     form.Render = Render; 
     form.Move = Move; 
    } 

    private void Init() 
    { 
     // Do Init code here 
    } 

    private void Render(Graphics g) 
    { 
     // Do Rendering code here 
    } 

    private void Move(double dt) 
    { 
     // Do Move code here 
    } 
} 

編輯:對於一件很無聊的例子正是我在一些面向私人的代碼中使用的這種技術(用於私人遊戲項目),請查看my delegate-powered-collection blog post

+0

+1爲一個新穎的想法。我不確定這是否是「最佳」方法。 – ScottS 2009-12-16 20:48:04

+0

我不知道這是否是最好的方法 - 我只是想指出另一種選擇。 – 2009-12-16 20:59:22

+1

@Erik:將我的評論與「過去我對某些特定問題使用此策略」進行了評論:請記住,這不是人們想要擴展程序的最常見方式之一,這可能會導致一些混淆打算使用API​​。衡量這種優勢與其他開發人員不得不花時間去了解它的可能性相重要。 :) – 2009-12-17 06:29:34

5

的決定一點幫助:

是否要通知其他(多個)對象?然後使用事件。

你想讓不同的實現可能/使用多態嗎?然後使用虛擬/抽象方法。

在某些情況下,即使組合兩種方式也是一個很好的解決方案。

13

有趣的是,我不得不在最近的一個設計中做出類似的選擇。一種方法並不嚴格優於另一種方法。

在你的例子中,如果沒有額外的信息,我可能會選擇虛擬方法 - 特別是因爲我的直覺讓我相信你會用繼承來模擬不同類型的實驗,而且你不需要具有多重訂戶(如事件允許)。

下面是我的一些一般性意見有關這些模式之間進行選擇:

活動是巨大的:

  1. 當你不需要調用者返回任何信息,而當你希望可擴展性而不需要子類化。
  2. 如果您希望允許調用者擁有多個可以偵聽和響應該事件的訂戶。
  3. 因爲它們自然排列成template method模式 - 這有助於避免引入fragile base class problem

事件的最大問題是管理訂閱者的生命期可能會變得棘手,當訂閱者訂閱的時間超過必要時,您可能會引入泄漏甚至功能缺陷。第二個最大的問題是,允許多個訂閱者可以創建一個令人困惑的實現,其中個別訂閱者互相訪問 - 或展示訂單依賴關係。

虛擬方法工作得很好:

  1. 當你只想繼承人能夠改變類的行爲。
  2. 當你需要從調用返回的信息(這事件的不輕鬆支持)
  3. 時,你只需要一個用戶到特定的擴展點
  4. 當你想衍生品的衍生品能夠覆蓋行爲。

虛擬方法的最大問題是你可以很容易地將脆弱的基類問題引入到你的實現中。虛擬方法本質上是與派生類的契約,您必須清楚地記錄文檔,以便繼承者可以提供有意義的實現。

虛擬方法的第二大問題是,它可以引入深層或廣泛的繼承樹來定製在特定情況下如何定製類的行爲。這可能是好的,但我通常會嘗試避免繼承,如果在問題域中沒有明確的關係is-a

還有另一種解決方案,您可以考慮使用:strategy pattern你的班級是否支持分配一個對象(或委託)來定義Render,Init,Move等的行爲方式。您的基類可以提供默認實現,但允許外部消費者改變行爲。雖然與事件類似,但優點是您可以將值返回給調用代碼,並且只能強制執行一個用戶。

2

都沒有。

你應該使用一個實現了一個接口的類來定義你需要的函數。

1

爲什麼界面不是這個的選擇?

0

使用虛擬方法來允許基類的特殊化 - 內部細節。

從概念上講,除了返回方法之外,您可以使用事件來表示類的「輸出」。

0

我喜歡LBuskin的總結。至於脆弱的基類問題,有一種情況我想保護事件可能是最佳做法的有力競爭者:

  1. 你必須真正用於內部類的使用(而不是策略也許事件就像OP的Render一樣),例如Initializing或EnabledChanging,
  2. 您有可能產生大的繼承層次結構(深層和/或廣泛的),並且需要對事件採取多重行動(多個非公開消費者!)
  3. 你沒有w螞蟻依靠每個派生類記住調用base.OnSomethingHappened()。 (避免脆弱的基類問題。)
  4. 爲了的處理事件不要緊(以其它方式使用base.OnSomethingHappened()的鏈來控制秩序。)
  5. 事件適用於一些一種內部狀態變化,對外部類別不會有意義(否則使用公共事件)。例如,公共初始化事件通常是發生任何內部初始化之前發生的可取消事件。受保護的Initializing事件不應該作爲可取消事件暴露給該類的用戶,因爲該類可能已經部分初始化。

這避免了需要使用「派生類應該調用基方法」來記錄虛擬受保護方法,並希望您或其他派生類的人始終讀取文檔並遵循它。它還避免了不確定的問題,即在重寫的方法開始時還是在中間或結束時調用基方法。

注意:如果你認爲一個基類連接到它自己的事件是愚蠢的,沒有什麼從具有基類的非虛方法阻止你。

在OP的情況下,我想初始化,並可能將事件可能符合上述標準視情況而定。通常情況下,不可能有多於一個Render方法,因此,不需要調用任何基本Render方法的抽象方法(或者虛擬方法,如果此類允許您運行模擬而不繪製任何內容),則不需要調用任何基本Render方法。如果外部組件可能知道如何將該類繪製到圖形目標,那麼可能使用替代的視覺樣式(即2D或3D),Render的策略(公共代理)將會很有用。 Render方法似乎不太可能,但可能適用於未提及的其他功能,尤其是應用算法(趨勢線平均方法等)的功能。