我应该何时使用接口,何时使用基类?

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

如果我有狗和猫的课。为什么我要实现IPet而不是PetBase?我可以理解为ISheds或IBarks(IMakesNoise?)提供接口,因为它们可以逐个宠物放置,但我不知道该为普通宠物使用哪个接口。


当前回答

另一个要记住的选项是使用“has-a”关系,也就是“以”或“组合的方式实现”。有时这是一种比使用“is-a”继承更干净、更灵活的结构方式。

从逻辑上说,狗和猫都“拥有”一只宠物可能不太合理,但它避免了常见的多重继承陷阱:

public class Pet
{
    void Bathe();
    void Train(Trick t);
}

public class Dog
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

public class Cat
{
    private Pet pet;

    public void Bathe() { pet.Bathe(); }
    public void Train(Trick t) { pet.Train(t); }
}

是的,这个例子表明,在以这种方式做事时存在大量的代码重复和缺乏优雅。但我们也应该意识到,这有助于保持狗和猫与宠物类的分离(因为狗和猫不能访问宠物的私人成员),并为狗和猫从其他东西(可能是哺乳动物类)继承留下了空间。

当不需要私人访问并且您不需要使用通用的宠物参考/指针来参考狗和猫时,合成是最好的。接口为您提供了通用的引用功能,可以帮助减少代码的冗长,但当组织不好时,它们也会使事情变得模糊。当您需要私人成员访问时,继承是有用的,在使用它时,您将致力于将您的狗和猫类与您的宠物类高度耦合,这是一个巨大的成本。

在继承、组合和接口之间,没有一种方法总是正确的,考虑如何协调地使用这三种选项是有帮助的。在这三个选项中,继承通常是最不常用的选项。

其他回答

接口有一个明显的优点,即对类来说有点“热交换”。将一个类从一个父类更改为另一个父级通常会导致大量工作,但通常可以删除和更改接口,而不会对实现类产生很大影响。这在您有几个“可能”希望类实现的窄行为集的情况下尤其有用。

这在我的领域尤其适用:游戏编程。基类可能会因继承对象“可能”需要的大量行为而变得臃肿。通过接口,可以轻松地向对象添加或删除不同的行为。例如,如果我为想要反映伤害的对象创建了“IDamageEffects”界面,那么我可以很容易地将其应用于各种游戏对象,并在以后很容易地改变主意。假设我设计了一个要用于“静态”装饰对象的初始类,我最初决定它们是不可破坏的。稍后,我可能会决定,如果它们可以爆炸,那会更有趣,所以我修改了类以实现“IDamageEffects”接口。这比切换基类或创建新的对象层次结构要容易得多。

还请记住,不要在OO中被卷走(请参见博客),并始终基于所需的行为来建模对象,如果您设计的应用程序中唯一需要的行为是动物的通用名称和物种,那么您只需要一个类animal,而不是世界上每种可能的动物都需要数百万个类。

让我们以Dog和Cat类为例,使用C#进行演示:

狗和猫都是动物,特别是四足哺乳动物(动物太普通了)。让我们假设您有一个抽象类Mammal,用于这两个类:

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()功能的核心仍然相同。

然而,长颈鹿、犀牛和河马并不是你可以用来做宠物的动物。这就是界面将有用的地方:

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

上述合同的执行在猫和狗之间是不同的;将它们的实现放在抽象类中继承将是一个坏主意。

狗和猫的定义现在应该如下:

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

理论上,您可以从更高的基类重写它们,但本质上,接口允许您只将所需的内容添加到类中,而不需要继承。

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

使用接口在不相关类的ACROSS家族中强制执行契约。例如,对于表示集合但包含完全不同数据的类,您可能有通用的访问方法,即一个类可能表示查询的结果集,而另一个类则可能表示库中的图像。此外,您可以实现多个接口,从而允许您混合(并表示)类的功能。

当类具有共同关系,因此具有相似的结构和行为特征时,使用继承,即汽车、摩托车、卡车和SUV都是可能包含多个车轮、最高速度的道路车辆

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

继承(基类)表示“is-a”关系。例如,狗或猫是宠物。这种关系始终代表着班级的(单一)目的(结合“单一责任原则”)。

另一方面,接口表示类的其他特性。我将其称为“是”关系,如“Foo是一次性的”,因此C#中的IDisposable接口。