关于C#:抽象类与接口


Abstract classes vs Interfaces

我对抽象类在C中的用法有点困惑。在C++中,定义继承继承抽象类类的模板是有意义的。但是,在C语言中,接口是否具有相同的用途?

确实,抽象类可以有接口不提供的默认实现。所以,如果实现不需要包括在基类中,那么最好是使用接口吗?


我仍然喜欢提供一个接口的默认抽象实现,假设它是一个实质性的接口(而且它是有意义的)。您永远不知道什么时候可以向接口添加一些东西,这些东西有一个简单的默认实现,可以"免费"地包含到继承抽象基类的任何人身上。


这个代码项目文章有很多关于这两者之间的区别的信息,包括一个表,比较和对比每一个特性。

接口定义类之间的契约——类相互调用的方式。一个类可以实现多个接口,但只能从一个抽象类继承。


True that abstract classes can have default implementation which is not provided by Interfaces. So if implementation doesn't need to be included in base class, is it better to go for Interfaces?

是的:如果在基类中实现某些方法是有意义的,而这些方法对于所有固有类都是通用的,那么应该使用抽象类。如果基类只用于定义接口,但继承的类之间没有公共逻辑,请使用接口。


对于你的第一个问题,是的。

对于你的第二个答案,我会给你一些我已经遵循的提示。

  • 结合使用抽象类和接口来优化设计权衡。

使用抽象类

  • 当创建一个类库时,它将被广泛地分布或重用,特别是对客户机来说,使用抽象类优先于接口;因为它简化了版本控制。

  • 使用抽象类为一系列类型定义通用的基类。

  • 使用抽象类提供默认行为。

  • 在逻辑上类所属的层次结构中,只对基类进行子类化。

使用接口

  • 在创建可以随意更改的独立项目时,请优先使用抽象类的接口;因为它提供了更大的设计灵活性。

  • 使用接口引入多态行为而不进行子类化,并对多个继承进行建模,从而允许特定类型支持多种行为。

  • 使用接口为值类型设计多态层次结构。

  • 当一个不变的契约确实是有意的时候,使用一个接口。

  • 设计良好的接口定义了非常具体的功能范围。拆分包含不相关功能的接口。


接口和抽象类服务于不同的目标。接口用于声明类的协定,而抽象类用于共享公共实现。

如果只使用抽象类,则类不能从其他类继承,因为C不支持多重继承。如果只使用接口,则类不能共享公共代码。

1
2
3
4
5
6
7
8
9
10
11
12
public interface IFoo
{
    void Bar();
}

public abstract class FooBase : IFoo
{
    public abstract void Bar()
    {
        // Do some stuff usually required for IFoo.
    }
}

现在我们可以在各种情况下使用接口和基础实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class FooOne : FooBase
{
    public override void Bar()
    {
        base.Bar(); // Use base implementation.

        // Do specialized stuff.
    }
}

public class FooTwo : FooBase
{
    public override void Bar()
    {
        // Do other specialized stuff.

        base.Bar(); // Use base implementation.

        // Do more specialized stuff.
    }
}

// This class cannot use the base implementation from FooBase because
// of inheriting from OtherClass but it can still implement IFoo.
public class FooThree : OtherClass, IFoo
{
    public virtual void Bar()
    {
        // Do stuff.
    }
}

可以实现任意数量的接口,但只能继承一个类。因此,类和接口在C中是完全不同的,您不能互换使用它们。在C中,抽象类仍然是类,而不是接口。


如果您没有任何默认/通用代码,那么使用一个接口。

抽象类也可以用作模板,在模板中定义某些算法的步骤和调用顺序,派生类提供这些步骤的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class Processor
{
  // this is the only public method
  // implements the order of the separate steps
  public void Process()
  {
    Step1();
    Step2();
    //...
  }
  // implementation is provided by derived classes
  protected abstract void Step1();
  protected abstract void Step2();
}

这在一些答案中有所暗示,但没有明确说明。

事实上,您可以实现多个接口,并且只能从一个基类继承,就像它们是同一硬币的两面一样,这不是一个好的方法来看待它。

不要将接口视为对象层次结构的一部分。它们通常只是您的真实对象继承权声明为实现的功能的一小部分(或者至少是特定的,如果不是很小的话)。以IDisposable为例。如果是你写的,你会问自己它应该是一个抽象类还是一个接口?很明显,在这种情况下,它们是两个完全不同的东西。我想要一次性的。认为我是可克隆的和可数的。您可以在类中实现这些类,而不必尝试使类从一些不相关的类(如列表或数组)派生。或者取IEnumerator。只需为对象提供moveNext类型的视图。我的类可以提供这种功能,而不必尴尬地从其他与类无关的顺序收集数据类型派生。


建模时遵循的规则是:类(包括抽象的)和结构模型实体。接口模型行为。实现接口的实体可以被视为显示接口(契约)公开的行为。


注意,使用C 3,您可以通过使用扩展方法为接口提供默认行为。不过,也有一些限制,抽象类仍然有它们的位置。


在C中,使用抽象类的一个很大的障碍是您只能使用一个。通过接口,您可以不限制实现的基类。为此,即使我创建了一个抽象基类来帮助实现,我也总是使用一个接口。

通常,基本抽象类的另一个烦恼是它们倾向于依赖模板参数。这会使其他代码很难使用。解决这个问题的简单方法是提供一个接口,在不知道模板类的类型参数的情况下与抽象类进行对话。

其他人似乎打字更快,但请允许我总结一下…

Use an interface. If you need to share implementation, you can also create an abstract base class that provides common implementation details.


虽然没有实现的抽象类等价于接口,但接口和抽象类用于不同的事物。

接口可以在最一般的意义上用于多态性。例如,ICollection用于定义所有集合的接口(有很多集合)。在这里,它定义了您想要对某种类型执行的操作。还有许多其他用途(如可测试性、依赖注入等)。此外,接口可以是混合的,这在概念上和技术上都有效。

抽象类更多地与可模板化的行为有关,其中虚拟方法是"填补空白"的地方。显然,您不能混合抽象类(至少在C中不能)。


您应该总是喜欢编程而不是接口,而不是具体的类。

如果您还希望有一个默认的实现,您仍然可以创建一个实现接口的基类。


我总是喜欢接口,只要基类没有一些真正"繁重"的实现,这将为实现者节省大量时间。给予.NET只允许一个基类继承,强制您的用户继承是一个巨大的限制。