扩展接口模式

.Net 3.5中的新扩展允许从接口拆分function。

例如在.Net 2.0中

public interface IHaveChildren { string ParentType { get; } int ParentId { get; } List GetChildren() } 

可以(在3.5中)成为:

 public interface IHaveChildren { string ParentType { get; } int ParentId { get; } } public static class HaveChildrenExtension { public static List GetChildren( this IHaveChildren ) { //logic to get children by parent type and id //shared for all classes implementing IHaveChildren } } 

在我看来,这对于许多接口来说是更好的机制。 他们不再需要抽象基础来共享此代码,并且function上代码的工作原理相同。 这可以使代码更易于维护和更容易测试。

唯一的缺点是抽象基础实现可以是虚拟的,但可以解决(实例方法是否会隐藏具有相同名称的扩展方法?这会使代码混淆吗?)

没有经常使用这种模式的任何其他原因?


澄清:

是的,我看到扩展方法的趋势是到处都是它们。 我会特别小心在没有大量同行评审的情况下使用.Net值类型(我认为我们在字符串上唯一的一个是.SplitToDictionary() – 类似于.Split()但是采用键值分隔符太)

我认为那里有一个完整的最佳实践辩论;-)

(顺便说一句:DannySmurf,你的PM听起来很可怕。)

我在这里特别询问使用扩展方法,以前我们有接口方法。


我试图避免许多级别的抽象基类 – 实现这些模型的类大多已经有基类。 我认为这个模型可以比添加更多的对象层次结构更易于维护和更少过度耦合。

这是MS对Linq的IEnumerable和IQueryable做了什么?

我认为明智地使用扩展方法将接口置于与(抽象)基类更加相等的位置。

版本。 基类具有过度接口的一个优点是,您可以在更高版本中轻松添加新虚拟成员,而向接口添加成员将破坏针对旧版本库构建的实现者。 相反,需要创建具有新成员的新版本的接口,并且库必须解决或限制对仅实现原始接口的旧对象的访问。

作为一个具体的例子,库的第一个版本可能会定义一个接口,如下所示:

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

库已发布后,我们无法在不破坏当前用户的情况下修改界面。 相反,在下一个版本中,我们需要定义一个新接口来添加其他function:

 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 ); } ... } 

请注意,这两种情况都是根据接口提供的操作编写的,并且涉及琐碎或众所周知的默认实现。 也就是说,你只能从一个类inheritance一次,并且有针对性地使用扩展方法可以提供一种有价值的方法来处理接口缺少的基类提供的一些细节:)


编辑: Joe Duffy的相关post: 扩展方法作为默认接口方法实现

扩展方法应该只用作:扩展。 任何关键的结构/设计相关代码或非平凡操作都应该放在一个由类或接口组成/inheritance的对象中。

一旦另一个对象尝试使用扩展的对象,他们将看不到扩展,并且可能必须再次重新实现/重新引用它们。

传统观点认为,扩展方法只应用于:

  • 实用class,正如Vaibhav所说
  • 扩展密封的第三方API

我认为扩展方法取代的最好的东西是你在每个项目中找到的那些实用程序类。

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

我的两位。

我认为使用扩展方法将域/模型和UI /视图function分离是一件好事,特别是因为它们可以驻留在不同的命名空间中。

例如:

 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数据模板使用扩展方法。 您无法访问该类的私有/受保护成员,但它允许维护数据抽象,而不会在整个应用程序中进行过多的代码重复。

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

话虽这么说,你真的应该只在需要在实现该接口的所有类中提供相同function的情况下执行此操作,并且该function不是(并且可能不应该)任何派生的“官方”实现的一部分类。 如果为每个可能需要新function的派生类型编写扩展方法是不切实际的,那么扩展接口也是很好的。

我看到很多人主张使用基类来共享通用function。 小心这一点 – 你应该赞成合成而不是inheritance。 从建模的角度来看,inheritance只应用于多态。 它不是代码重用的好工具。

至于问题:在执行此操作时要了解限制 – 例如在显示的代码中,使用扩展方法有效地实现GetChildren’密封’此实现,并且不允许任何IHaveChildren impl在需要时提供它自己的。 如果这没关系,那么我不介意扩展方法的方法那么多。 它不是一成不变的,通常可以在以后需要更大的灵活性时轻松重构。

为了获得更大的灵活性,使用策略模式可能更为可取。 就像是:

 public interface IHaveChildren { string ParentType { get; } int ParentId { get; } } public interface IChildIterator { IEnumerable GetChildren(); } public void DefaultChildIterator : IChildIterator { private readonly IHaveChildren _parent; public DefaultChildIterator(IHaveChildren parent) { _parent = parent; } public IEnumerable GetChildren() { // default child iterator impl } } public class Node : IHaveChildren, IChildIterator { // *snip* public IEnumerable GetChildren() { return new DefaultChildIterator(this).GetChildren(); } } 

多一点点。

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

 ((IFirst)this).AmbigousMethod() 

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

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

 IInterface variable = new ImplementingClass(); 

对?

如果你真的需要一个带有一些附加function的合同,抽象类就是你的朋友。

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

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

不是传统的方法,但非常酷,绝对是DRY的情况。

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

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

我需要解决类似的问题:我希望将List 传递给extensions函数,其中IIDable是一个具有long getId()函数的接口。 我尝试使用GetIds(此List bla),但编译器不允许我这样做。 我使用模板代替然后在函数内部输入类型到接口类型。 我需要这个函数用于一些linq到sql生成的类。

我希望这有帮助 :)

  public static List GetIds(this List original){ List ret = new List(); 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"); }