扩展接口模式

问题描述:

.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?

我认为扩展方法的明智使用会将接口放在与(抽象)基类更加可等同的位置上。


版本控制。基类超过接口的一个优点是,您可以轻松地在新版本中添加新的虚拟成员,而向接口添加成员会打破针对旧版本库构建的实现者。相反,需要创建与新成员的接口的新版本,并且库必须解决或限制只能实现原始接口的传统对象的访问。

举一个具体的例子,一个库的第一个版本可能定义一个接口,像这样:

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

我能看到的一个问题是,在一家大公司,这种模式可能会让代码变得难以理解和使用(如果不是不可能的话)。如果多个开发人员不断地将自己的方法添加到现有的类中,那么与这些类分离(以及上帝帮助我们所有人甚至是BCL类),我可以看到一个代码库很快就失去了控制。

即使在我自己的工作中,我也可以看到发生这种情况,我的PM希望将我们工作的所有代码添加到UI或数据访问层,我完全可以看到他坚持20或30被添加到System.String中的方法,这些方法只与字符串处理切线相关。

我认为扩展方法替代的最好的东西就是您在每个项目中找到的所有实用类。

至少现在,我觉得任何其他扩展方法的使用都会导致工作场所的混乱。

我的两位。

扩展方法应该被用作扩展。任何关键的结构/设计相关的代码或非平凡的操作都应该放在一个对象中,该对象构成/从类或接口继承。

一旦另一个对象试图使用扩展的对象,它们将不会看到扩展,并且可能不得不重新实现/重新引用它们。

传统智慧是,扩展方法只能用于:

  • 实用工具类,如Vaibhav的提到
  • 延长密封第三方的API

哎哟。请不要扩展接口。
接口是一个类应该实现的干净契约,并且您所使用的类必须必须限制在核心接口中,以使其正常工作。

这就是为什么你总是声明接口为类型而不是实际类。

IInterface variable = new ImplementingClass(); 

对不对?

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

扩展接口没有任何问题,实际上LINQ是如何将扩展方法添加到集合类的。

这就是说,你应该只在需要在所有实现该接口的类中提供相同功能并且该功能不是(也可能不应该是)“官方”实现任何派生类。如果为需要新功能的每种可能的派生类型编写扩展方法是不切实际的,那么扩展接口也是很好的。

我看到使用扩展方法分离域/模型和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

我看到很多人提倡使用基类来共享通用功能。小心这一点 - 你应该赞成组合而不是继承。从模型的角度来看,继承只能用于多态。它不是代码重用的好工具。

至于这样的问题:在做这件事情时要注意限制 - 例如在显示的代码中,使用扩展方法来有效地实现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(); 
    } 
} 

Rob Connery(Subsonic和MVC店面)在他的Storefront应用程序中实现了一个类似于IRepository的模式。这不是上述模式,但它有一些相似之处。

数据层返回IQueryable,它允许消费层在其上应用过滤和排序表达式。例如,奖金可以指定一个单一的GetProducts方法,然后在消费层中适当地决定如何进行排序,过滤或甚至只是特定范围的结果。

不是传统的方法,但非常酷,绝对是一个干案。

我需要解决类似的东西: 我想有一个列表<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"); 
     } 

多一点点。

如果多个接口具有相同的扩展方法签名,则需要将调用方显式转换为一种接口类型,然后调用该方法。例如。

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

同意,但如果你必须这样做,我建议接口设计不好或者你的班级不应该实现它们。 – Keith 2010-01-08 08:09:58