2012-09-18 32 views
2

假設我有許多使用不同簽名的方法。根據一些外部代碼,所有這些方法可能調用。你可能會認爲它們是一些事件處理程序。如何避免空基抽象類?

現在我應該有兩種實現像這樣:

  • 每個實現真正處理只有所有可能的事件的一部分
  • 每個實現對於它不想/不能處理的所有事件都不做任何事情。

我當然可以爲所有可能的處理程序聲明一個接口,但是接下來我將不得不在每個實現中創建空方法(處理程序)。即使對於那些我不想/不能處理的事件。

我想這樣做類似如下:

abstract class Base 
{ 
    public virtual void First(int i, double d) { /* no implementation */ } 
    public virtual void Second(double d) { /* no implementation */ } 
    public virtual void Third(string s, int i) { /* no implementation */ } 
} 

class Child : Base 
{ 
    public override void First(int i, double d) { /* implementation */ } 
    public override void Second(double d) { /* implementation */ } 
} 

class AnotherChild : Base 
{ 
    public override void Second(double d) { /* implementation */ } 
    public override void Third(string s, int i) { /* implementation */ } 
} 

這種做法迫使我在基地抽象類的所有可能的處理程序創建空的實現。

你能推薦一些更好的東西嗎?一種不需要產生大量空方法的方法?

我正在使用C#2.0,並且無法在此任務中使用較新版本的語言。

+4

我認爲你在那裏做的完美是有道理的。我不認爲空的方法體是任何問題。或者,您可以使用所有派生類訂閱的常規事件。 – usr

+0

@JFFx我需要在另一個類中使用任一實現。這個班會爲處理所有可能的事件。一個實現將處理事件的一部分,其他實現將處理其他部分。這些部分確實重疊。 – Bobrovsky

+0

@leppie:你爲什麼刪除2.0標籤?這個問題僅限於那個平臺,所以例如一些「動態」方法是沒有問題的。 – quetzalcoatl

回答

4

我同意@usr - 我沒有看到空函數的問題。如果你想調用一個函數,那麼它必須存在。如果它在某些情況下什麼也不做,那麼這個功能應該是空的。具有空函數的基類與需要反覆實現相同空函數的接口似乎是一個非常好的主意。

如果您正在尋找替代品,可以考慮Chain of Responsibility設計模式。您可以調用一個通用函數,然後參數化所需的行爲,而不是調用特定的函數。然後,您可以將物體鏈接在一起(不同情況下的不同鏈條)並給予他們一個處理這種行爲的機會。如果他們沒有人處理它,那麼沒有任何反應。

這在某些情況下可以很好地工作,但是實現非常簡單和優雅的基類方法會更加複雜。小心不要過度設計。


這裏的實施指揮鏈的基礎上,你在你的問題給的例子的一個例子:

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

namespace ConsoleApplication2 { 

    interface ICommand { 
     bool Execute(string action, params object[] parameters); 
    } 

    class Program { 
     static void Main(string[] args) { 

      CommandChain l_chain1 = new CommandChain(new FirstCommand(), new SecondCommand()); 
      CommandChain l_chain2 = new CommandChain(new SecondCommand(), new ThirdCommand()); 

      // Chain 1 

      if (l_chain1.Execute("first", (int) 1, (double) 1.1)) 
       Console.WriteLine("Chain 1 executed First"); 
      else 
       Console.WriteLine("Chain 1 did not execute First"); 

      if (l_chain1.Execute("second", (double) 1.2)) 
       Console.WriteLine("Chain 1 executed Second"); 
      else 
       Console.WriteLine("Chain 1 did not execute Second"); 

      if (l_chain1.Execute("third", "4", (int) 3)) 
       Console.WriteLine("Chain 1 executed Third"); 
      else 
       Console.WriteLine("Chain 1 did not execute Third"); 

      // Chain 2 

      if (l_chain2.Execute("first", (int) 1, (double) 1.1)) 
       Console.WriteLine("Chain 2 executed First"); 
      else 
       Console.WriteLine("Chain 2 did not execute First"); 

      if (l_chain2.Execute("second", (double) 1.2)) 
       Console.WriteLine("Chain 2 executed Second"); 
      else 
       Console.WriteLine("Chain 2 did not execute Second"); 

      if (l_chain2.Execute("third", "4", (int) 3)) 
       Console.WriteLine("Chain 2 executed Third"); 
      else 
       Console.WriteLine("Chain 2 did not execute Third"); 

      Console.ReadKey(true); 

     } 
    } 

    class CommandChain { 

     private ICommand[] _commands; 

     public CommandChain(params ICommand[] commands) { 
      _commands = commands; 
     } 

     public bool Execute(string action, params object[] parameters) { 
      foreach (ICommand l_command in _commands) { 
       if (l_command.Execute(action, parameters)) 
        return true; 
      } 
      return false; 
     } 

    } 

    class FirstCommand : ICommand { 
     public bool Execute(string action, params object[] parameters) { 
      if (action == "first" && 
       parameters.Length == 2 && 
       parameters[0].GetType() == typeof(int) && 
       parameters[1].GetType() == typeof(double)) { 

       int i = (int) parameters[0]; 
       double d = (double) parameters[1]; 

       // do something 

       return true; 
      } else 
       return false; 
     } 
    } 

    class SecondCommand : ICommand { 
     public bool Execute(string action, params object[] parameters) { 
      if (action == "second" && 
       parameters.Length == 1 && 
       parameters[0].GetType() == typeof(double)) { 

       double d = (double) parameters[0]; 

       // do something 

       return true; 
      } else 
       return false; 
     } 
    } 

    class ThirdCommand : ICommand { 
     public bool Execute(string action, params object[] parameters) { 
      if (action == "third" && 
       parameters.Length == 2 && 
       parameters[0].GetType() == typeof(string) && 
       parameters[1].GetType() == typeof(int)) { 

       string s = (string) parameters[0]; 
       int i = (int) parameters[1]; 

       // do something 

       return true; 
      } else 
       return false; 
     } 
    } 

} 

(請注意,這個例子並不遵循所有的編程最佳實踐 - 我不建議實現這個代碼,例如action參數可能會比枚舉類型更好,並返回某種CommandResult而不是布爾值,只能用於靈感)。

1

只是一個側面說明:如果這是爲了測試目的,你可以模擬接口,並立即有你的無所作爲的實現幾乎沒有代碼。類似地,您也可以在僅實現部分接口的類中實現所有缺少的方法。不用於測試 - 你也可以在生產中使用它,但..在prod中使用mocks稍微有些味道,並且爲「handler-ish」合約擁有一個空白實現的樣板基本實際上並不是什麼壞事,事實上,將它作爲進一步代碼的基礎是件好事 - 就像Cyborgx37已經提到的一樣。