2008-08-11 138 views
23

.Net 3.5中的新擴展允許將功能從接口中分離出來。擴展接口模式

例如.NET 2.0中

public interface IHaveChildren { 
    string ParentType { get; } 
    int ParentId { get; } 

    List<IChild> GetChildren() 
} 

能(3.5)變爲:

public interface IHaveChildren { 
    string ParentType { get; } 
    int ParentId { get; } 
} 

public static class HaveChildrenExtension { 
    public static List<IChild> GetChildren(this IHaveChildren) { 
     //logic to get children by parent type and id 
     //shared for all classes implementing IHaveChildren 
    } 
} 

在我看來這是許多接口,一個更好的機制。他們不再需要抽象基礎來分享這些代碼,而且功能上代碼的工作原理是一樣的。這可以使代碼更易於維護並且更易於測試。

唯一的缺點是,一個抽象的基地實現可以是虛擬的,但可以是圍繞工作(將一個實例方法隱藏具有相同名稱的擴展方法是什麼?這將是混亂的代碼,這樣做?)

不定期使用此模式的其他原因?


澄清:

是的,我看到的擴展方法的傾向是無處不在與他們就結了。我會特別小心有任何.Net的值類型沒有經過同行評審的一個很大的(我認爲我們必須對字符串的唯一一個是.SplitToDictionary() - 類似.Split()但考慮鍵值分隔符太)

我覺得有一個整體的最佳實踐辯論有;-)

(順便說一句:DannySmurf,你下午聽起來很嚇人。)

我專門詢問這裏有關使用擴展方法,而在以前,我們有接口的方法。


我想避免很多級別的抽象基類 - 實現這些模型的類大多已經有了基類。我認爲這個模型比添加更多的對象層次結構可以更容易維護,並且不會過度耦合。

這是MS已經做了什麼IEnumerable和IQueryable的Linq?

回答

8

我認爲擴展方法的明智使用會將接口放在與(抽象)基類更加可等同的位置上。


版本控制。基類超過接口的一個優點是,您可以輕鬆地在新版本中添加新的虛擬成員,而向接口添加成員會打破針對舊版本庫構建的實現者。相反,需要創建與新成員的接口的新版本,並且庫必須解決或限制只能實現原始接口的傳統對象的訪問。

舉一個具體的例子,一個庫的第一個版本可能定義一個接口,像這樣:

public interface INode { 
    INode Root { get; } 
    List<INode> GetChildren(); 
} 

一旦庫已經發布,我們不能修改界面不破壞現有用戶。取而代之的是,在未來的版本中,我們需要定義一個新的接口添加額外functionalty:

public interface IChildNode : INode { 
    INode Parent { get; } 
} 

然而,只有新庫的用戶將能夠實現新的接口。爲了與舊代碼工作,我們需要適應舊的實現,其中一個擴展方法可以很好地處理了:

public static class NodeExtensions { 
    public INode GetParent(this INode node) { 
    // If the node implements the new interface, call it directly. 
    var childNode = node as IChildNode; 
    if(!object.ReferenceEquals(childNode, null)) 
     return childNode.Parent; 

    // Otherwise, fall back on a default implementation. 
    return FindParent(node, node.Root); 
    } 
} 

現在,新圖書館的所有用戶都可以同樣對待傳統和現代化的實現。


過載。擴展方法可能有用的另一個領域是爲接口方法提供重載。你可能有一個有幾個參數的方法來控制它的行爲,其中只有第一個或兩個參數在90%的情況下很重要。由於C#不允許爲參數設置默認值,因此用戶必須每次都調用完全參數化的方法,否則每個實現都必須實現核心方法的平凡重載。

而是擴展方法可以用來提供瑣碎的過載實現:

public interface ILongMethod { 
    public bool LongMethod(string s, double d, int i, object o, ...); 
} 

... 
public static LongMethodExtensions { 
    public bool LongMethod(this ILongMethod lm, string s, double d) { 
    lm.LongMethod(s, d, 0, null); 
    } 
    ... 
} 


請注意,這兩種情況都寫在接口所提供的操作而言,涉及瑣碎或好已知的默認實現。這就是說,你只能從一個類繼承一次,並有針對性地採用擴展方法可以提供處理一些通過接口缺乏:)


編輯基類提供的細微的有價值的方法: Joe Duffy的一篇相關文章:Extension methods as default interface method implementations

0

我能看到的一個問題是,在一家大公司,這種模式可能會讓代碼變得難以理解和使用(如果不是不可能的話)。如果多個開發人員不斷地將自己的方法添加到現有的類中,那麼與這些類分離(以及上帝幫助我們所有人甚至是BCL類),我可以看到一個代碼庫很快就失去了控制。

即使在我自己的工作中,我也可以看到發生這種情況,我的PM希望將我們工作的所有代碼添加到UI或數據訪問層,我完全可以看到他堅持20或30被添加到System.String中的方法,這些方法只與字符串處理切線相關。

6

我認爲擴展方法替代的最好的東西就是您在每個項目中找到的所有實用類。

至少現在,我覺得任何其他擴展方法的使用都會導致工作場所的混亂。

我的兩位。

11

擴展方法應該被用作擴展。任何關鍵的結構/設計相關的代碼或非平凡的操作都應該放在一個對象中,該對象構成/從類或接口繼承。

一旦另一個對象試圖使用擴展的對象,它們將不會看到擴展,並且可能不得不重新實現/重新引用它們。

傳統智慧是,擴展方法只能用於:

  • 實用工具類,如Vaibhav的提到
  • 延長密封第三方的API
1

哎喲。請不要擴展接口。
接口是一個類應該實現的乾淨契約,並且您所使用的類必須必須限制在覈心接口中,以使其正常工作。

這就是爲什麼你總是聲明接口爲類型而不是實際類。

IInterface variable = new ImplementingClass(); 

對不對?

如果你真的需要一些附加功能的合約,抽象類是你的朋友。

2

擴展接口沒有任何問題,實際上LINQ是如何將擴展方法添加到集合類的。

這就是說,你應該只在需要在所有實現該接口的類中提供相同功能並且該功能不是(也可能不應該是)「官方」實現任何派生類。如果爲需要新功能的每種可能的派生類型編寫擴展方法是不切實際的,那麼擴展接口也是很好的。

4

我看到使用擴展方法分離域/模型和UI /視圖功能是一件好事,尤其是因爲它們可以駐留在單獨的名稱空間中。

例如:

namespace Model 
{ 
    class Person 
    { 
     public string Title { get; set; } 
     public string FirstName { get; set; } 
     public string Surname { get; set; } 
    } 
} 

namespace View 
{ 
    static class PersonExtensions 
    { 
     public static string FullName(this Model.Person p) 
     { 
      return p.Title + " " + p.FirstName + " " + p.Surname; 
     } 

     public static string FormalName(this Model.Person p) 
     { 
      return p.Title + " " + p.FirstName[0] + ". " + p.Surname; 
     } 
    } 
} 

這種方式擴展方法可類似地用於XAML數據模板。您不能訪問該類的私有/受保護成員,但它可以保持數據抽象,而不會在整個應用程序中出現過多的代碼重複。

+0

我也喜歡這種方法zooba,但我沿着部分類的路線,其中部分舉行了所有的「擴展」。 – 2013-01-02 09:42:57

2

我看到很多人提倡使用基類來共享通用功能。小心這一點 - 你應該贊成組合而不是繼承。從模型的角度來看,繼承只能用於多態。它不是代碼重用的好工具。

至於這樣的問題:在做這件事情時要注意限制 - 例如在顯示的代碼中,使用擴展方法來有效地實現GetChildren'密封'此實現,並且不允許任何IHaveChildren impl提供它自己的如果需要的話。如果這是好的,那麼我不介意擴展方法的方法。它不是一成不變的,而且通常可以在以後需要更多靈活性時輕鬆重構。

爲了獲得更大的靈活性,使用策略模式可能更可取。例如:

public interface IHaveChildren 
{ 
    string ParentType { get; } 
    int ParentId { get; } 
} 

public interface IChildIterator 
{ 
    IEnumerable<IChild> GetChildren(); 
} 

public void DefaultChildIterator : IChildIterator 
{ 
    private readonly IHaveChildren _parent; 

    public DefaultChildIterator(IHaveChildren parent) 
    { 
     _parent = parent; 
    } 

    public IEnumerable<IChild> GetChildren() 
    { 
     // default child iterator impl 
    } 
} 

public class Node : IHaveChildren, IChildIterator 
{ 
    // *snip* 

    public IEnumerable<IChild> GetChildren() 
    { 
     return new DefaultChildIterator(this).GetChildren(); 
    } 
} 
1

Rob Connery(Subsonic和MVC店面)在他的Storefront應用程序中實現了一個類似於IRepository的模式。這不是上述模式,但它有一些相似之處。

數據層返回IQueryable,它允許消費層在其上應用過濾和排序表達式。例如,獎金可以指定一個單一的GetProducts方法,然後在消費層中適當地決定如何進行排序,過濾或甚至只是特定範圍的結果。

不是傳統的方法,但非常酷,絕對是一個幹案。

0

我需要解決類似的東西: 我想有一個列表<IIDable>傳遞給擴展函數,其中IIDable是具有長的getId()函數的接口。 我試過使用GetIds(這個List <IIDable> bla),但編譯器不允許我這樣做。 我用模板代替,然後在函數內部輸入casted到接口類型。我需要這個函數用於一些linq to sql生成的類。

我希望這有助於:)

public static List<long> GetIds<T>(this List<T> original){ 
     List<long> ret = new List<long>(); 
     if (original == null) 
      return ret; 

     try 
     { 
      foreach (T t in original) 
      { 
       IIDable idable = (IIDable)t; 
       ret.Add(idable.getId()); 
      } 
      return ret; 
     } 
     catch (Exception) 
     { 
      throw new Exception("Class calling this extension must implement IIDable interface"); 
     } 
2

多一點點。

如果多個接口具有相同的擴展方法簽名,則需要將調用方顯式轉換爲一種接口類型,然後調用該方法。例如。

((IFirst)this).AmbigousMethod() 
+0

同意,但如果你必須這樣做,我建議接口設計不好或者你的班級不應該實現它們。 – Keith 2010-01-08 08:09:58