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

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

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


当前回答

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

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

其他回答

现代风格是定义IPet和PetBase。

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

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

资料来源: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)。

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

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

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

通常,您应该喜欢接口而不是抽象类。使用抽象类的一个原因是,如果在具体类之间有共同的实现。当然,您仍然应该声明一个接口(IPet),并让一个抽象类(PetBase)实现该接口。使用小的、不同的接口,您可以使用多个来进一步提高灵活性。接口允许最大限度的灵活性和跨边界类型的可移植性。当跨越边界传递引用时,始终传递接口而不是具体类型。这允许接收端确定具体实施,并提供最大的灵活性。当以TDD/BDD方式编程时,这是绝对正确的。

“四人帮”在他们的书中说:“因为继承将子类暴露于其父类实现的细节,所以人们常说‘继承破坏了封装’。”。我相信这是真的。

如果除了您的类型成员之外,其他开发人员确实没有任何理由希望使用自己的基类,并且您预见到版本控制问题,那么您应该使用基类(请参见http://haacked.com/archive/2008/02/21/versioning-issues-with-abstract-base-classes-and-interfaces.aspx).

如果继承开发人员有任何理由使用自己的基类来实现类型的接口,并且您没有看到接口发生变化,那么就使用接口。在这种情况下,为了方便起见,您仍然可以加入实现接口的默认基类。

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

首选接口而非抽象类

一些要点:

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

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

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