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

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

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


当前回答

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

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

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

其他回答

资料来源:http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/

C#是一种奇妙的语言,在过去的14年中不断成熟和发展。这对我们开发人员来说很好,因为一种成熟的语言为我们提供了大量的语言特性,供我们使用。

然而,权力大了,责任大了。其中一些功能可能被误用,或者有时很难理解为什么您会选择使用一个功能而不是另一个功能。多年来,我看到许多开发人员都在纠结于一个特性,那就是什么时候选择使用接口或者选择使用抽象类。两者都有各自的优点和缺点,以及使用它们的正确时间和地点。但我们如何决定???

两者都提供了类型之间公共功能的重用。最明显的区别是,接口不提供其功能的实现,而抽象类允许您实现一些“基本”或“默认”行为,然后可以在必要时使用类派生类型“覆盖”此默认行为。

这一切都很好,并且提供了代码的重用,并且遵循了软件开发的DRY(不要重复自己)原则。当你有一种“是”的关系时,抽象类很好用。

例如:金毛寻回犬是一种狗。贵宾犬也是。它们都会吠叫,就像所有的狗一样。然而,您可能需要指出,贵宾犬公园与“默认”狗叫有明显不同。因此,您可以执行以下操作:

public abstract class Dog
{
      public virtual void Bark()
      {
        Console.WriteLine("Base Class implementation of Bark");
      }
}

public class GoldenRetriever : Dog
{
   // the Bark method is inherited from the Dog class
}

public class Poodle : Dog
{
  // here we are overriding the base functionality of Bark with our new implementation
  // specific to the Poodle class
  public override void Bark()
  {
     Console.WriteLine("Poodle's implementation of Bark");
  }
}

// Add a list of dogs to a collection and call the bark method.

void Main()
{
    var poodle = new Poodle();
    var goldenRetriever = new GoldenRetriever();

    var dogs = new List<Dog>();
    dogs.Add(poodle);
    dogs.Add(goldenRetriever);

    foreach (var dog in dogs)
    {
       dog.Bark();
    }
}

// Output will be:
// Poodle's implementation of Bark
// Base Class implementation of Bark

// 

正如您所看到的,这将是一种保持代码干燥的好方法,并允许在任何类型只能依赖默认Bark而不是特殊情况实现时调用基类实现。GoldenRetriever、Boxer、Lab等类都可以免费继承“默认”(低音类)Bark,因为它们实现了Dog抽象类。

但我相信你已经知道了。

您来到这里是因为您想了解为什么您可能希望选择一个接口而不是抽象类,反之亦然。嗯,您可能希望选择接口而不是抽象类的一个原因是,当您没有或希望阻止默认实现时。这通常是因为实现接口的类型与“是”关系无关。事实上,除了每种类型“能够”或“有能力”做某事或拥有某事这一事实之外,它们根本不必是相关的。

那到底是什么意思?嗯,举个例子:人不是鸭子……鸭子也不是人。很明显。然而,鸭子和人类都有游泳的“能力”(考虑到人类在一年级通过了游泳课程:)。此外,由于鸭子不是人,反之亦然,这不是“是一种”关系,而是“能够”关系,我们可以使用一个界面来说明:

// Create ISwimable interface
public interface ISwimable
{
      public void Swim();
}

// Have Human implement ISwimable Interface
public class Human : ISwimable

     public void Swim()
     {
        //Human's implementation of Swim
        Console.WriteLine("I'm a human swimming!");
     }

// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
     public void Swim()
     {
          // Duck's implementation of Swim
          Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
     }
}

//Now they can both be used in places where you just need an object that has the ability "to swim"

public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
{
     somethingThatCanSwim.Swim();
}

public void Main()
{
      var human = new Human();
      var duck = new Duck();

      var listOfThingsThatCanSwim = new List<ISwimable>();

      listOfThingsThatCanSwim.Add(duck);
      listOfThingsThatCanSwim.Add(human);

      foreach (var something in listOfThingsThatCanSwim)
      {
           ShowHowYouSwim(something);
      }
}

 // So at runtime the correct implementation of something.Swim() will be called
 // Output:
 // Quack! Quack! I'm a Duck swimming!
 // I'm a human swimming!

使用上面的代码这样的接口将允许您将对象传递给“能够”执行某些操作的方法。代码不在乎它是如何做到的……它只知道它可以对该对象调用Swim方法,并且该对象将根据其类型知道在运行时采取的行为。

再次,这有助于代码保持干燥,这样您就不必编写多个调用对象的方法来预成型相同的核心函数(ShowHowHumanSwims(人类)、ShowHowDuckSwims(鸭子)等)

在这里使用接口允许调用方法不必担心行为是什么类型或如何实现的。它只是知道,给定接口,每个对象都必须实现Swim方法,因此在自己的代码中调用它是安全的,并允许在自己的类中处理Swim方法的行为。

摘要:

因此,我的主要经验法则是,当你想为一个类层次结构或/和你正在处理的类或类型实现一个“默认”功能时,使用一个抽象类。

另一方面,如果你没有“是一种”关系,但类型共享“能力”来做某事或拥有某事(例如,鸭子“不是”人类“。然而,鸭子和人类共享“游泳能力”),则使用界面。

抽象类和接口之间需要注意的另一个区别是,一个类可以实现一对多接口,但一个类只能从一个抽象类(或任何类)继承。是的,您可以嵌套类并具有继承层次结构(许多程序都这样做,也应该这样做),但不能在一个派生类定义中继承两个类(这一规则适用于C#。在其他一些语言中,您可以这样做,通常是因为这些语言中缺少接口)。

还请记住,使用接口时要遵守接口隔离原则(ISP)。ISP表示,不应强迫任何客户端依赖它不使用的方法。因此,接口应该专注于特定的任务,并且通常非常小(例如IDisposable、IComparable)。

另一个提示是,如果您正在开发小而简洁的功能,请使用接口。如果要设计大型功能单元,请使用抽象类。

希望这能为一些人扫清障碍!

此外,如果你能想出任何更好的例子或想指出什么,请在下面的评论中这样做!

Josh Bloch在《有效的Java 2d》中说道:

首选接口而非抽象类

一些要点:

可以很容易地对现有类进行修改,以实现新的界面你所要做的就是添加所需的方法(如果还没有)存在并将implements子句添加到类声明。接口是定义混合的理想选择。粗略地说mixin是类可以使用的类型除了“主要”键入“”以声明它提供一些可选行为。例如Comparable是一个mixin接口允许类声明其实例的排序依据其他相互可比较的对象。接口允许构造非分层类型框架。类型层次结构为很适合组织一些事情,但是其他的事情并不是很简单刚性层次。接口通过每类包装的习惯用法。如果您使用抽象类来定义类型留下想要添加的程序员功能,别无选择使用继承。此外,你可以结合这些优点接口和抽象类提供抽象骨架实现类导出的非平凡接口。

另一方面,接口很难发展。如果您向接口添加一个方法,它将破坏它的所有实现。

PS:买这本书。它要详细得多。

在这篇Java World文章中对其进行了很好的解释。

就我个人而言,我倾向于使用接口来定义接口,即系统设计中指定如何访问某些内容的部分。

我会有一个类实现一个或多个接口,这并不罕见。

抽象类是我用来做其他事情的基础。

以下是上述文章JavaWorld.com文章的摘录,作者Tony Sintes,2001年4月20日


接口与抽象类选择接口和抽象类不是一个非此即彼的命题。如果你需要改变你的设计,让它成为一个界面。然而,您可能有提供一些默认行为的抽象类。抽象类是应用程序框架中的优秀候选者。抽象类允许您定义一些行为;它们迫使您的子类提供其他子类。例如,如果您有一个应用程序框架,抽象类可以提供默认服务,如事件和消息处理。这些服务允许应用程序插入应用程序框架。但是,有些特定于应用程序的功能只有您的应用程序才能执行。此类功能可能包括启动和关闭任务,这些任务通常依赖于应用程序。因此,抽象基类可以声明抽象的关闭和启动方法,而不是试图定义该行为本身。基类知道它需要这些方法,但抽象类让您的类承认它不知道如何执行这些操作;它只知道它必须发起行动。当需要启动时,抽象类可以调用启动方法。当基类调用此方法时,Java调用子类定义的方法。许多开发人员忘记了定义抽象方法的类也可以调用该方法。抽象类是创建计划继承层次结构的极好方法。对于类层次结构中的非叶类,它们也是一个很好的选择。类与接口

有人说你应该用接口来定义所有的类,但我认为这个建议有点极端。当我看到设计中的某些内容会频繁更改时,我会使用接口。

例如,Strategy模式允许您在程序中交换新的算法和过程,而不改变使用它们的对象。媒体播放器可能知道如何播放CD、MP3和wav文件。当然,您不希望将这些回放算法硬编码到播放器中;这将使添加像AVI这样的新格式变得困难。此外,您的代码将充斥着无用的case语句。为了增加伤害,您需要在每次添加新算法时更新这些案例陈述。总而言之,这不是一种非常面向对象的编程方式。使用Strategy模式,您可以简单地将算法封装在对象后面。如果这样做,您可以随时提供新的媒体插件。让我们将插件类称为MediaStrategy。该对象将有一个方法:playStream(流)。所以为了添加一个新的算法,我们只需扩展我们的算法类。现在,当程序遇到新的媒体类型时,它只是将流的播放委托给我们的媒体策略。当然,您需要一些管道来正确实例化所需的算法策略。这是一个使用界面的好地方。我们使用了Strategy模式,它清楚地表明设计中的一个位置将发生变化。因此,您应该将策略定义为一个接口。当您希望某个对象具有某种类型时,通常应该支持接口而不是继承;在本例中为MediaStrategy。依赖类型标识的继承是危险的;它将您锁定到特定的继承层次结构中。Java不允许多重继承,因此您不能扩展能够为您提供有用实现或更多类型标识的内容。

通过def,接口提供了一个与其他代码通信的层。默认情况下,类的所有公共财产和方法都实现隐式接口。我们也可以将接口定义为一个角色,当任何类都需要扮演这个角色时,它必须实现它,根据实现它的类,给它不同的实现形式。oops的两个概念!!!

现代风格是定义IPet和PetBase。

该接口的优点是其他代码可以使用它,而与其他可执行代码没有任何联系。完全“干净”。界面也可以混合。

但是基类对于简单的实现和通用实用程序很有用。因此,还提供一个抽象基类,以节省时间和代码。