language-agnostic - 接口VS 基类

  显示原文与译文双语对照的内容

什么时候应使用接口以及什么时候应使用基类?

如果我不想实际定义方法的基实现,那么它应该是一个接口?

如果我有狗和 cat 类。 为什么我想要实现IPet而不是 PetBase? 我能理解接口ISheds或者 IBarks ( IMakesNoise), 因为这些可以放置在宠物,宠物的基础上,但我不懂,用于一般的宠物。

时间:

让我们来举一个狗和 cat 类的例子,让我们用 C# 来说明:

狗和 cat 都是动物,特别是四足动物( 动物太一般了) 。 假设你有一个抽象类哺乳动物,它们都是:


public abstract class Mammal

这里基类可能有默认方法,例如:

  • 提要
  • 配对

所有这些行为都或多或少都是在一个物种之间实现的。 要定义这个,你需要:


public class Dog : Mammal
public class Cat : Mammal

现在假设还有其他哺乳动物,我们通常会在动物园看到它们:


public class Giraffe : Mammal
public class Rhinoceros : Mammal
public class Hippopotamus : Mammal

这仍然是有效的,因为在功能 Feed()Mate()的核心仍然是相同的。

然而,giraffes,犀牛和hippos并不是你能让宠物从。 这就是接口的用处:


public interface IPettable
{
 IList<Trick> Tricks{get; set;}
 void Bathe();
 void Train(Trick t);
}

上述契约的实现在 cat 和dog之间不一样;将它们的实现放在抽象类中继承是一个不好的主意。

你的狗和 cat的定义应该如下所示:


public class Dog : Mammal, IPettable
public class Cat : Mammal, IPettable

从理论上讲,你可以从更高的基类覆盖它们,但本质上一个接口允许你只在类中添加需要的东西,而无需继承。

因此,因为你通常只能从一个抽象类继承( 在大多数静态类型的面向对象语言中,即。 例外包括 C++ ),但能够实现多个接口,它允许你根据需要在严格的中构造对象。

好吧,Josh说他自己在 Effective Java 2:

在抽象类上首选接口

一些要点:

  • 现有类可以轻松改造实现一个新的接口。 你所要做的就是添加必要的方法,如果它们还不存在,并将一个implements子句添加到类声明。

  • 接口是定义 mixin的理想方法。 松散地说,mixin是类除了它的"主要类型"之外可以实现的类型,它声明它提供了一些可选的行为。 例如可以比较是一个mixin接口,它允许类声明它的实例与其他相互比较的对象排序。

  • 的接口允许构建nonhierarchical类型框架 。 类型层次对于组织某些事物是非常有用的,但是其他东西并不能整齐地归入一个严格的层次结构。

  • 接口启用安全、强大的功能增强通过wrap-每个类习语。 如果你使用抽象类定义类型,那么你就会离开想要添加功能的程序员,除了使用继承。

此外,通过提供抽象的骨架实现类和你导出的每个重要接口,可以结合接口和抽象类的优点。

另一方面,接口很难进化。 如果向接口添加一个方法,它将中断它的所有实现。

买这本书,它更详细。

现代风格是定义 IPet 和 PetBase 。

接口的优点是其他代码可以在没有任何可以执行代码的情况下使用它。 完全"清理。"也可以混合接口。

但是基类对于简单的实现和公共实用程序很有用。 所以提供一个抽象基类来节省时间和代码。

接口和基类表示两种不同的关系形式。

继承 ( 基类) 代表"is-a"关系。 比如 狗或者 cat"is-a"宠物。 这里关系始终代表类( 与一起使用"单一职责原则(Single Responsibility Principle)"栏)的( 单) 英镑目的

接口,另一方面,代表一个类的其他功能。 我将它称为"是"关系,比如" Foo 是一次性的",因此 IDisposable 接口在 C# 中。

通常,你应该优先于抽象类的接口。 使用抽象类的一个原因是在具体类之间有通用的实现。 当然,你仍然应该声明一个接口( IPet ) 并拥有一个抽象的类( PetBase ) 实现 interface.Using 小,独特的接口,你可以使用倍数来进一步提高灵活性。 接口允许跨边界的类型的最大灵活性和可移植性。 当跨越边界传递引用时,总是传递接口而不是具体类型。 这允许接收端确定具体实现并提供最大的灵活性。 在 tdd/bdd模式下编程时这绝对是真的。

在他们的书"因为继承公开了子类实现的细节的子类,所以通常说'继承中声明的四人帮打破了封装"。 我相信这是真的。

接口

  • 定义 2模块之间的协定。 不能有任何实现。
  • 大多数语言允许你实现多个接口
  • 修改接口是一个中断更改。 所有实现都需要重新编译/修改。
  • 所有成员都是公共的。实现必须实现所有成员。
  • 接口有助于解耦。你可以使用模拟框架模拟接口背后的任何东西
  • 接口通常表示一种行为
  • 接口实现相互分离/隔离

基类:

  • Allows you to add someyf状态译文:yf default yfstrongr译文:yf implementation that you get for free by derivation
  • 除了 C++ 之外,你只能从一个类派生。 即使可以从多个类,这通常是一个不好的主意。
  • 更改基类相对简单。 派生不需要做任何特殊操作
  • 基类可以声明可以由派生访问的受保护和公共函数
  • 抽象基类不能像接口那样容易地被模仿
  • 基类通常表示类型层次结构( 是一个)
  • 类派生可能依赖于某些基本行为( 具有复杂的父实现知识) 。 如果你对一个人的基本实现做了改动,并打断其他人,事情会变得混乱。

这很. NET 具体,但框架设计指南书认为,一般类提供更多灵活性的发展框架。 一旦一个接口被传送,你就没有机会在不破坏使用该接口的代码的情况下更改它。 但有了类,你可以修改它,而不破坏链接到它的代码。 只要你做出正确的修改,其中包括添加新功能,你将能够扩展和发展你的代码。

Krzysztof Cwalina在第 81页上显示:

在. NET 框架的三个版本,我已经讲过这条指导原则与不少开发商对我们的团队。 他们中的许多人,包括那些最初不同意指导方针,说他们后悔运送一些api接口。 我还没有听说过有一个人后悔他们shipped了一个类。

据说这里肯定有一个接口的地方。 作为一般准则总是提供一个抽象的基类实现一个接口,如果没有其他的方法来实现接口的一个例子。 在基类将保存大量工作的最佳情况下。

胡安.

我喜欢把接口看作一个类的特征。 一个特定的狗类类,比如 YorkshireTerrier,可能是父dog类的后裔,但它也实现了 IFurry,IStubby和 IYippieDog 。 所以类定义了类是什么,但是接口告诉了我们它的内容。

这样做的好处是,它允许我把所有的iyippiedog放到我的海洋集合中。 所以现在我可以接触一组特定的对象,找到满足我需要的条件,而不需要过于仔细地检查这个类。

我发现接口真的应该定义一个类的公共行为的sub-set 。 如果它定义了所有实现的所有类的公共行为,那么它通常不需要存在。 他们没有告诉我任何有用的东西。

这个想法虽然相悖,每个类都应该有一个接口,你应该到接口代码。 这是好,但是你最终的一个接口类和它使一件事令人困惑。 我明白,这个想法并不是真正的花费,现在你可以轻松地交换东西。 然而,我发现我很少这样做。 大部分时间我只是修改现有的类,有同样的问题我总是做这类的公共接口是否需要改变,除了我现在必须改变它在两个地方。

所以如果你像我一样认为 cat 和狗是 IPettable 。 这是一种与它们相匹配的特征。

另一部分是它们是否有相同的基类? 问题是他们是否需要被广泛的对待。 当然它们都是动物,但这符合我们将它们一起使用的方式。

说我想收集所有的动物类并把它们放到我的Ark容器 in 。

或者它们需要是哺乳动物? 也许我们需要一些交叉式挤奶工厂?

他们甚至都需要被链接在一起? 只要知道它们都是IPettable就足够了?

当我真的只需要一个类的时候,我经常觉得需要派生一个完整的类层次结构。 我在期待某一天,我可能需要它,通常我永远也做不到。 即使我这么做了,我总会发现我必须做很多来修复它。 那是因为我创建的第一个类不是狗,我不是幸运的,而是鸭嘴兽。 现在我的整个类层次都基于奇怪的情况,而且我有很多浪费的代码。

你也可能会发现不是所有的猫都是 IPettable ( 像那个无头的那个) 。 现在你可以将该接口移动到所有适合的派生类。 你会发现一个突然的改变,突然所有的猫不再是从PettableBase派生的。

我建议尽可能使用组合而不是 inheritence 。 使用接口,但对基实现使用成员对象。 这样,你就可以定义一个工厂,该工厂构造的对象以某种方式表现。 如果你想更改行为,那么你将创建一个新的工厂方法( 或者抽象工厂),它创建不同类型的sub-objects 。

在某些情况下,你可能会发现主对象根本不需要接口,如果所有的可变行为都在 helper 对象中定义。

所以IPet或者PetBase,你可能会得到一只宠物一个IFurBehavior参数。 IFurBehavior参数设置的CreateDog() PetFactory的方法。 为 shed() 方法调用了这个参数。

如果你这样做,你会发现你的代码更加灵活,你的大多数简单对象都能处理基本的系统行为。

我建议在multiple-inheritence语言中使用这种模式。

下面是接口和基类的基本 definiton:

  • 基类= 对象继承。
  • 接口= 函数继承。

cheers!

...