什么时候在C#中使用抽象类代替具有扩展方法的接口?


70

“抽象类”和“接口”是相似的概念,其中接口是两者中比较抽象的。一个区别因素是抽象类在需要时为派生类提供方法实现。但是,在C#中,最近引入的扩展方法已减小了这种差异因素,该扩展方法使得可以为接口方法提供实现。另一个区别因素是,一个类只能继承一个抽象类(即没有多重继承),但可以实现多个接口。这使接口的限制更少,更加灵活。因此,在C#中,什么时候应该使用抽象类而不是具有扩展方法的接口?

接口+扩展方法模型的一个著名示例是LINQ,其中为IEnumerable通过多种扩展方法实现的任何类型提供了查询功能。


2
而且我们也可以在界面中使用“属性”。
Gulshan

1
听起来像是C#特定的问题,而不是一般的OOD问题。(其他大多数OO语言都没有扩展方法或属性。)
PéterTörök


4
扩展方法不能完全解决抽象类所解决的问题,因此其原因与Java或其他类似的单一继承语言中的原因没有什么不同。
工作

3
扩展方法并没有减少对抽象类方法实现的需求。扩展方法不能访问私有成员字段,也不能将它们声明为虚拟成员并在派生类中被覆盖。
zooone9243 2012年

Answers:


63

当您需要实现类的一部分时。我使用的最好的示例是模板方法模式。

public abstract class SomethingDoer
{
    public void Do()
    {
        this.DoThis();
        this.DoThat();
    }

    protected abstract void DoThis();
    protected abstract void DoThat();
}

因此,您可以定义调用Do()时将要采取的步骤,而无需了解如何实现这些细节。派生类必须实现抽象方法,而不是Do()方法。

扩展方法不一定满足方程式的“必须是类的一部分”部分。此外,iirc扩展方法在范围上不能(似乎)是公共的。

编辑

这个问题比我最初认为的要有趣。经过进一步检查,乔恩·斯基特(Jon Skeet)在SO上回答了这样的问题,赞成使用接口+扩展方法。同样,潜在的缺点是对这种设计的对象层次结构使用了反射。

就我个人而言,我很难看到改变当前普遍做法的好处,但是这样做的缺点很少甚至没有。

应该注意的是,可以通过Utility类以多种语言进行这种编程。扩展仅提供语法糖,以使方法看起来像它们属于该类。


击败我吧
。– Giorgi

1
但是,使用接口和扩展方法可以完成相同的操作。在抽象基类中定义的方法在那里Do()将被定义为扩展方法。剩下的用于派生类定义的方法DoThis()将是主接口的成员。借助接口,您可以支持多种实现/继承。并参阅this-odetocode.com/blogs/scott/archive/2009/10/05/…–
Gulshan

@Gulshan:因为可以通过多种方式完成某件事,但这不会使所选方式无效。使用接口没有优势。抽象类本身就是接口。您不需要接口来支持多种实现。
ThomasX 2011年

同样,接口+扩展模型也可能有缺点。以Linq库为例。很好,但是如果您只需要在该代码文件中使用一次,则完整的Linq库将在代码文件中每个IEnumerable的IntelliSense中可用。这会大大降低IDE性能。
KeithS 2012年

-1,因为您还没有展示出比接口更适合抽象方法的抽象类的方法
Benjamin Hodgson 2015年

25

这完全取决于您要建模的内容:

抽象类通过继承来工作。作为特殊的基类,它们对某种关系建模。

例如,狗动物,因此我们有

class Dog : Animal { ... }

因为实际上创建通用的所有动物没有意义(看起来像什么?),所以我们创建了这个Animal基类abstract-但它仍然是基类。如果Dog没有一个班级,这个班级就没有意义了Animal

另一方面,接口是另一回事。它们不使用继承,而是提供多态性(也可以通过继承来实现)。他们不对is-a关系进行建模,但更多的确支持

例如IComparable- 支持与另一个对象进行比较的对象。

类的功能不取决于其实现的接口,该接口仅提供访问该功能的通用方法。我们仍然可以DisposeGraphicsFileStream无需他们实施IDisposable。该接口仅将方法关联在一起。

原则上,可以像扩展一样添加和删除接口,而无需更改类的行为,而只是丰富了对它的访问。但是,您不能删除基类,因为该对象将变得毫无意义!


您何时选择保留基类抽象?
Gulshan

1
@Gulshan:如果这样做(出于某种原因),实际上创建基类的实例是没有意义的。您可以“创造”吉娃娃或白鲨,但是如果没有进一步的专业化就不能创造动物 -因此您将做出Animal抽象。这使您可以编写abstract派生类专用的功能。或更多的编程术语来说-创建a可能没有任何意义UserControl,但由于a Button a UserControl,因此我们具有相同的类/基类关系。
Dario

如果您有抽象方法,那么您将有抽象类。还有没有实现价值的抽象方法(例如对动物使用“ go”)。当然,有时候您不希望允许某些通用类的对象,而是将其抽象化。
Dainius

没有规则说如果说“ B是一个A”在语法和语义上有意义,那么A必须是抽象类。您应该首选接口,直到您真的无法避免使用该类为止。共享代码可以通过接口等的组成来完成。这使得避免使用怪异的继承树来避免陷入困境变得更加容易。
萨拉

3

我刚刚意识到,多年来我没有使用抽象类(至少没有创建过)。

抽象类是接口和实现之间有点奇怪的野兽。抽象类中有些东西使我的蜘蛛感觉有些刺痛。我认为,不应以任何方式(或进行任何搭配)“暴露”接口背后的实现。

即使在实现接口的类之间需要通用的行为,我也将使用独立的帮助器类,而不是抽象类。

我会去寻找接口。


2
-1:我不确定您的年龄,但是您可能想学习设计模式,尤其是模板方法模式。设计模式将使您成为更好的程序员,并且将结束您对抽象类的无知。
Jim G.

刺痛感也一样。类是C#和Java的第一个也是最重要的功能,它是一种用于创建类型并将其立即永久链接到一个实现的功能。
杰西·米利坎

2

恕我直言,我认为这里混合了一些概念。

类使用某种继承,而接口使用另一种继承。

两种继承都很重要和有用,每种继承都有其优缺点。

让我们从头开始。

关于接口的故事早在C ++时代就开始了。在1980年代中期,当开发C ++时,尚未意识到接口作为另一种类型的想法(它会及时出现)。

C ++只有一种继承,即类使用的继承,称为实现继承。

为了能够应用GoF Book的建议:“为接口编程,而不是为实现编程”,C ++支持抽象类和多个实现继承,以便能够执行C#中的接口能够(并且非常有用!) 。

C#引入了一种新的类型,接口以及一种新的继承(即接口继承),以至少两种不同的方式来支持“为接口编程,而不是为实现编程”,其中有一个重要的警告:仅C#通过接口继承(而不是实现继承)支持多重继承。

因此,GoF建议使用C#中的接口背后的体系结构原因。

希望对您有所帮助。

亲切的问候,加斯顿


1

将扩展方法应用于接口对于在可能仅共享一个公共接口的类之间应用公共行为很有用。这样的类可能已经存在,并且通过其他方式无法扩展。

对于新设计,我倾向于在接口上使用扩展方法。对于类型的层次结构,扩展方法提供了一种扩展行为的方法,而无需打开整个层次结构进行修改。

但是,扩展方法无法访问私有实现,因此在层次结构的顶部,如果我需要将私有实现封装在公共接口之后,那么抽象类将是唯一的方法。


不,这不是唯一安全的方法。还有另一种更安全的方法。那就是扩展方法。客户端完全不会被感染。这就是为什么我提到接口+扩展方法。
Gulshan

@Ed James我认为“功能更弱” =“更灵活”什么时候您会选择功能更强大的抽象类而不是更灵活的接口?
Gulshan

@Ed James在asp.mvc中,从一开始就使用扩展方法。您对此有何看法?
Gulshan

0

我认为在C#中不支持多重继承,这就是为什么在C#中引入接口的概念。

在抽象类的情况下,也不支持多重继承。因此,很容易决定是否要使用抽象类或接口。


确切地说,它是抽象类,在C#中称为“接口”。
JohanBoulé2014年

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.