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

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

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


当前回答

我通常在需要之前都不会实现。与抽象类相比,我更喜欢接口,因为这提供了更多的灵活性。如果在某些继承类中有共同的行为,我会将其上移并创建一个抽象基类。我不认为两者都有必要,因为它们本质上服务于相同的目的,而且两者都有一种糟糕的代码气味(imho),解决方案被过度设计了。

其他回答

接口应较小。真的很小。如果你真的要分解你的对象,那么你的接口可能只包含一些非常具体的方法和财产。

抽象类是快捷方式。PetBase的所有衍生产品都有你可以编写一次代码并完成的东西吗?如果是的话,那么是抽象类的时候了。

抽象类也是有限的。虽然它们为您提供了生成子对象的绝佳捷径,但任何给定对象只能实现一个抽象类。很多时候,我发现这是抽象类的局限性,这也是我使用大量接口的原因。

抽象类可以包含多个接口。您的PetBase抽象类可以实现IPet(宠物有主人)和IDigestion(宠物吃,或者至少它们应该吃)。然而,PetBase可能不会实施IMammal,因为并非所有宠物都是哺乳动物,也并非所有哺乳动物都是宠物。您可以添加一个扩展PetBase的MammalPetBase并添加IMammal。FishBase可以有PetBase并添加IFish。IFish将使用ISwim和IUnderwaterBreaker作为接口。

是的,对于简单的示例来说,我的示例非常复杂,但这是接口和抽象类如何协同工作的一部分。

这取决于您的要求。如果IPet足够简单,我更愿意实现它。否则,如果PetBase实现了大量您不想复制的功能,那么就试试看。

实现基类的缺点是需要重写(或新的)现有方法。这使它们成为虚拟方法,这意味着您必须小心如何使用对象实例。

最后,.NET的单一继承让我很头疼。一个天真的例子:假设你正在创建一个用户控件,那么你就继承了UserControl。但是,现在你被禁止继承PetBase。这迫使您重新组织,例如创建PetBase类成员。

不要使用基类,除非您知道它的含义,并且它适用于这种情况。如果适用,使用它,否则使用接口。但请注意关于小接口的答案。

公共继承在OOD中被过度使用,表达的内容比大多数开发人员意识到或愿意实现的要多得多

简而言之,如果A“是一个”B,那么对于它公开的每个方法,A需要的不超过B,交付的不少于B。

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

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

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

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

我建议尽可能使用组合而不是继承。使用接口,但对基本实现使用成员对象。这样,您可以定义一个工厂,该工厂构造对象以某种方式运行。如果要更改行为,则创建一个新的工厂方法(或抽象工厂),以创建不同类型的子对象。

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

因此,您可能会得到一个带有IFurBehavior参数的宠物,而不是IPet或PetBase。IFurBehavior参数由PetFactory的CreateDog()方法设置。shed()方法调用的就是这个参数。

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

即使在多种继承语言中,我也推荐这种模式。