关于OOP:接口与基类

Interface vs Base class

什么时候应该使用接口,什么时候应该使用基类?

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

如果我上的是猫狗课。为什么我要实现IPET而不是PetBase?我可以理解为ISHED或IBarks(imakesNoise?)因为它们可以放在一个宠物一个宠物的基础上,但我不知道用哪一个普通的宠物。


让我们以你的狗和猫类为例,用C来举例说明:

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

1
public abstract class Mammal

这个基类可能有如下默认方法:

  • 饲料
  • 配偶

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

1
2
public class Dog : Mammal
public class Cat : Mammal

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

1
2
3
public class Giraffe : Mammal
public class Rhinoceros : Mammal
public class Hippopotamus : Mammal

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

然而,长颈鹿、犀牛和河马并不是你可以用来养宠物的动物。这就是界面的用处所在:

1
2
3
4
5
6
public interface IPettable
{
    IList<Trick> Tricks{get; set;}
    void Bathe();
    void Train(Trick t);
}

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

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

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

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

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


嗯,Josh Bloch在有效的Java 2D中说了自己:

比抽象类更喜欢接口

一些要点:

  • Existing classes can be easily retrofitted to implement a new
    interface. All you have to do is add
    the required methods if they don’t yet
    exist and add an implements clause to
    the class declaration.

  • Interfaces are ideal for defining mixins. Loosely speaking, a
    mixin is a type that a class can
    implement in addition to its"primary
    type" to declare that it provides
    some optional behavior. For example,
    Comparable is a mixin interface that
    allows a class to declare that its
    instances are ordered with respect to
    other mutually comparable objects.

  • Interfaces allow the construction of nonhierarchical type
    frameworks. Type hierarchies are
    great for organizing some things, but
    other things don’t fall neatly into a
    rigid hierarchy.

  • Interfaces enable safe, powerful functionality enhancements via the
    wrap- per class idiom. If you use
    abstract classes to define types, you
    leave the programmer who wants to add
    functionality with no alternative but
    to use inheritance.

Moreover, you can combine the virtues
of interfaces and abstract classes by
providing an abstract skeletal
implementation class to go with each
nontrivial interface that you export.

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

附:买这本书。更详细。


现代风格是定义ipet和petbase。

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

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


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

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

另一方面,接口表示类的附加特性。我称之为"是"关系,就像"Foo是可弃的",因此c中的IDisposable接口。


界面

  • 定义两个模块之间的合同。不能有任何实现。
  • 大多数语言允许您实现多个接口
  • 修改一个接口是一个突破性的改变。所有实现都需要重新编译/修改。
  • 所有成员都是公开的。实现必须实现所有成员。
  • 接口有助于分离。您可以使用模拟框架来模拟接口后面的任何内容。
  • 接口通常表示一种行为
  • 接口实现相互分离。

基类

  • 允许您添加一些通过派生免费获得的默认实现
  • 除了C++之外,只能从一个类派生。即使可以从多个类中获得,这通常也是一个坏主意。
  • 更改基类相对容易。派生不需要做任何特殊的事情
  • 基类可以声明可由派生访问的受保护函数和公共函数
  • 抽象基类不能像接口那样容易被模拟
  • 基类通常表示类型层次结构(is a)
  • 类派生可能依赖于一些基本行为(对父实现有复杂的知识)。如果您对一个人的基本实现进行更改,并打破其他人的基础实现,事情可能会很混乱。


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

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


这是一个漂亮的.NET框架设计指南的特异性,但一般认为这类图书给更多的灵活性在不断变化的框架。一次是在shipped接口,你不会得到机会改变它没有打破代码,使用这样的接口。然而,一个类,你可以修改它的代码,链接和不打破它。只要你做正确的修改,其中包括添加新的功能,你可以想你的代码的扩展和进化。

克日什托夫说:在cwalina 81页

Over the course of the three versions of the .NET Framework, I have talked about this guideline with quite a few developers on our team. Many of them, including those who initially disagreed with the guidelines, have said that they regret having shipped some API as an interface. I have not heard of even one case in which somebody regretted that they shipped a class.

有人说这确实是一个地方的接口。作为一般指引,提供抽象类可以实现接口库为例,如果没有其他的方式来实现一个接口。在最好的这类案例库要节省很多的时间。


胡安

我喜欢把接口看作一种描述类的方法。一个特定的狗品种类,比如约克郡人,可能是父狗类的后代,但它也实现了ifurry、istubby和iyippiedog。所以类定义了类是什么,但是接口告诉了我们关于它的事情。

这样做的好处是,我可以收集所有的Iyippiedog,然后把它们扔到我的海洋收藏中。因此,现在我可以跨越一组特定的对象,找到符合我正在查看的标准的对象,而不必过于仔细地检查类。

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

不过,这种想法与这样的想法背道而驰:每个类都应该有一个接口,并且您应该对接口进行编码。这很好,但是你最终会得到很多类的一对一接口,这会让事情变得混乱。我知道这个想法是,它并不真正需要做任何事情,现在你可以轻松地交换东西。然而,我发现我很少这样做。大多数时候,我只是在适当的地方修改现有的类,如果该类的公共接口需要更改,我总是会遇到同样的问题,但现在我必须在两个地方更改它。

所以如果你像我一样想的话,你肯定会说猫和狗是可以吃的。这是一个符合两者的特征。

不过,另一个问题是,它们应该具有相同的基类吗?问题是,他们是否需要被广泛地视为同一件事?当然,它们都是动物,但这是否符合我们将如何一起使用它们。

说我想把所有的动物类都收集起来放在我的方舟容器里。

或者他们需要成为哺乳动物?也许我们需要一个跨动物挤奶厂?

它们甚至需要连接在一起吗?仅仅知道它们都是可攻击的就足够了吗?

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

你可能也会发现,并非所有的猫都能被捕杀(就像那只没毛的猫)。现在,您可以将该接口移动到所有适合的派生类。你会发现,一个更不破坏性的变化,所有突然的猫不再源自pettablebase。


下面是接口和基类的基本和简单定义:

  • 基类=对象继承。
  • 接口=功能继承。

干杯


我建议使用组合而inheritence每当可能的。使用接口,但使用的基地成员对象的实现。这样,你可以定义你的对象是A厂是构建在一定的行为方式。如果你想改变你的行为,然后创建一个新的工厂方法(或抽象工厂),创建不同类型的子对象。

在某些情况下,你可能发现你的主要对象不需要在所有的接口,如果所有的行为是mutable助手对象中定义的。

这不是petbase IPET或,你可能最终有一个宠物是ifurbehavior参数。这是ifurbehavior参数集的《petfactory createdog()方法。它是这个参数是所谓的棚()方法。

如果你这样做,你会发现你的代码是一个更灵活的和最简单的对象,你的交易系统有很宽的基本行为。

我推荐这个产品inheritence模式多语言。


在这个Java世界文章中解释得很好

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

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

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

以下是上述文章javaworld.com文章的摘录,作者Tony Sintes,04/20/01

Interface vs. abstract class

Choosing interfaces and abstract classes is not an either/or proposition. If you need to change your design, make it an interface. However, you may have abstract classes that provide some default behavior. Abstract classes are excellent candidates inside of application frameworks.

Abstract classes let you define some behaviors; they force your subclasses to provide others. For example, if you have an application framework, an abstract class may provide default services such as event and message handling. Those services allow your application to plug in to your application framework. However, there is some application-specific functionality that only your application can perform. Such functionality might include startup and shutdown tasks, which are often application-dependent. So instead of trying to define that behavior itself, the abstract base class can declare abstract shutdown and startup methods. The base class knows that it needs those methods, but an abstract class lets your class admit that it doesn't know how to perform those actions; it only knows that it must initiate the actions. When it is time to start up, the abstract class can call the startup method. When the base class calls this method, Java calls the method defined by the child class.

Many developers forget that a class that defines an abstract method can call that method as well. Abstract classes are an excellent way to create planned inheritance hierarchies. They're also a good choice for nonleaf classes in class hierarchies.

Class vs. interface

Some say you should define all classes in terms of interfaces, but I think recommendation seems a bit extreme. I use interfaces when I see that something in my design will change frequently.

For example, the Strategy pattern lets you swap new algorithms and processes into your program without altering the objects that use them. A media player might know how to play CDs, MP3s, and wav files. Of course, you don't want to hardcode those playback algorithms into the player; that will make it difficult to add a new format like AVI. Furthermore, your code will be littered with useless case statements. And to add insult to injury, you will need to update those case statements each time you add a new algorithm. All in all, this is not a very object-oriented way to program.

With the Strategy pattern, you can simply encapsulate the algorithm behind an object. If you do that, you can provide new media plug-ins at any time. Let's call the plug-in class MediaStrategy. That object would have one method: playStream(Stream s). So to add a new algorithm, we simply extend our algorithm class. Now, when the program encounters the new media type, it simply delegates the playing of the stream to our media strategy. Of course, you'll need some plumbing to properly instantiate the algorithm strategies you will need.

This is an excellent place to use an interface. We've used the Strategy pattern, which clearly indicates a place in the design that will change. Thus, you should define the strategy as an interface. You should generally favor interfaces over inheritance when you want an object to have a certain type; in this case, MediaStrategy. Relying on inheritance for type identity is dangerous; it locks you into a particular inheritance hierarchy. Java doesn't allow multiple inheritance, so you can't extend something that gives you a useful implementation or more type identity.


还要记住不要在OO(见博客)中被扫地出门,并且总是根据所需的行为来建模对象,如果你正在设计一个应用程序,其中你所需要的唯一行为是一个动物的通用名称和物种,那么你只需要一个类动物的名称,而不是每个可能的动物的数百万个类。在世界上。


我有一个粗略的经验法则

功能:可能在所有部分都不同:界面。

数据和功能,部件基本上是相同的,部件不同:抽象类。

实际工作的数据和功能,如果只进行细微的扩展:普通(具体)类

数据和功能,没有计划更改:带有最终修改器的普通(具体)类。

数据,可能还有功能:只读:枚举成员。

这是非常粗糙和准备好的,根本没有严格的定义,但是有一个从接口到所有内容都要更改为枚举的范围,其中所有内容都像一个只读文件一样被固定。


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

抽象类是快捷方式。有没有所有Petbase共享的衍生产品都可以一次性编码并完成?如果是的话,那么现在是抽象类的时候了。

抽象类也是有限的。虽然它们为生成子对象提供了一个很好的快捷方式,但是任何给定的对象只能实现一个抽象类。很多时候,我发现这是抽象类的一个限制,这就是为什么我使用很多接口的原因。

抽象类可以包含多个接口。petbase抽象类可以实现ipet(pets有所有者)和idigestion(pets吃东西,或者至少应该这样做)。然而,petbase可能不会实现imammal,因为不是所有的宠物都是哺乳动物,也不是所有的哺乳动物都是宠物。您可以添加哺乳动物petbase来扩展petbase并添加imammal。Fishbase可以有petbase和添加ifish。ifish将iswim和iunderwaterbeather作为接口。

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


在submin.net编码指南中很好地解释了接口上的基类的情况:

Base Classes vs. Interfaces
An interface type is a partial
description of a value, potentially
supported by many object types. Use
base classes instead of interfaces
whenever possible. From a versioning
perspective, classes are more flexible
than interfaces. With a class, you can
ship Version 1.0 and then in Version
2.0 add a new method to the class. As long as the method is not abstract,
any existing derived classes continue
to function unchanged.

Because interfaces do not support
implementation inheritance, the
pattern that applies to classes does
not apply to interfaces. Adding a
method to an interface is equivalent
to adding an abstract method to a base
class; any class that implements the
interface will break because the class
does not implement the new method.
Interfaces are appropriate in the
following situations:

  • Several unrelated classes want to support the protocol.
  • These classes already have established base classes (for
    example,
    some are user interface (UI) controls,
    and some are XML Web services).
  • Aggregation is not appropriate or practicable. In all other
    situations,
    class inheritance is a better model.

  • 来源:http:////12/jasonroell.com 2014年09 /接口和抽象类,什么如果你使用/

    C语言是一个很有#成熟和过去14年来的。这是伟大的,因为美国的开发者提供一个成熟的语言(美国1994:14语言特征是在我们的处置。

    然而,在多大的功率有很大的责任。我的论文的特点可以misused,有时它是难理解为什么你会选择一个使用过的另一个特征。多年来,我看到很多特征与开发商斗争的时候选择使用一个接口或抽象类在选择使用。都有优点和缺陷和在正确的时间和使用的每个地方。但我们如何决定???????????????

    无论是普通的功能之间提供的重用)的类型。最明显的区别是,马上接口提供的功能没有实现或实现,而抽象类允许你一些"基地"或"违约"行为,然后有能力"覆盖"这一类的默认行为,如果必要的源类型。

    这是一个很好的提供所有的伟大的代码重用和adheres到干(不要重复自己)的软件开发原则。使用抽象类是伟大的,当你有一个"A"的关系。

    例如:A"是一个"金毛寻回犬型的狗。这是一个卷毛狗。他们是为所有的狗树皮的CAN,CAN。然而,你可能想州立公园,这是显着不同的卷毛狗比狗树皮"默认"。therefor意义,它可以为你的东西是:为实施

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    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

    //

    你可以看到,这将是一个伟大的方式让您的代码库,允许干和执行的类的类型可以在任何位置上的依赖默认的树皮而特殊用例的实现。一类goldenretriever,实验室所有的战士,是可以"默认"(低音级),因为他们只是在没有树皮狗批实现抽象类。

    但我知道你已经知道。

    你在这里是因为你想明白为什么你会想选择过抽象类在接口或反之亦然。一个好的原因,你可能想选择过抽象类接口是在当你没有时间或想一个默认的实现。这是因为通常的类型是不相关的接口,在实施"is a"关系。实际上,他们是不相关的,在所有的事实,除了为每个类型是"可以"或"能力",安切洛蒂做什么或有什么。

    现在我是一尾均值?好的,例如:A A A人力是鸭和鸭不是一个人类。很明显。然而,这两个A和一个鸭子的"能力"的人力(或游泳,他游泳课人力传递在第一级:))。因此,自A鸭不是一个人类或反之亦然,这不是在"是一个"好"的关系,但是我们可以使用"关系到CAN接口说明:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    // 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!

    像上面的代码使用的界面,让你的护照在一个对象的方法是"能做什么"。代码演练不在乎它如何知道它是…它是呼叫"游泳对象和对象的方法上想知道需要在运行时的行为基于其类型。

    再次,这是由您的代码,你会干这么多没有写的方法是调用对象到预制件相同的核心功能(showhowhumanswims(人力),showhowduckswims(鸭),等等)

    这里使用的方法可以调用一个接口没有担心什么类型的行为或是这是如何实现的。它只是似乎知道每个对象的接口,要必须让它是这样实现的游泳方法调用它自己的安全代码和允许的行为在《游法是处理自己的类。

    总结:

    因此,我的主要经验法则是,当您想要为类层次结构或/和您正在使用的类或类型实现"默认"功能时,使用抽象类共享"是"关系(例如,poodle是"狗的类型")。好的。

    另一方面,当你没有"是"的关系,但有类型共享"能力"做某件事或有某件事时,使用界面(例如,duck"不是"人。然而,鸭子和人类有着共同的"游泳能力")。好的。

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

    还请记住,当使用接口时,要遵循接口隔离原则(ISP)。ISP声明,不应强制任何客户端依赖于它不使用的方法。因此,接口应该集中在特定的任务上,并且通常非常小(例如,IDisposable、IComparable)。好的。

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

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

    如果你能想出更好的例子或者想指出一些事情,请在下面的评论中这样做!好的。好啊。


    一个重要的区别是,只能继承一个基类,但可以实现多个接口。因此,如果您完全确定不需要同时继承不同的基类,则只希望使用基类。另外,如果你发现你的接口变得越来越大,那么你应该开始把它分成几个逻辑部分来定义独立的功能,因为没有规则规定你的类不能全部实现它们(或者你可以定义一个不同的接口,它只继承所有的接口来对它们进行分组)。


    比抽象类更喜欢接口

    理论基础,需要考虑的要点是:

    • 接口更灵活,因为一个类可以实现多个接口。由于Java没有多重继承,所以使用抽象类阻止用户使用任何其他类等级制度。一般来说,在没有默认值的情况下首选接口实现或状态。Java集合提供了很好的例子这个(地图、集合等)。
    • 抽象类具有允许更好地向前推进的优势兼容性。一旦客户机使用了接口,就不能更改它;如果他们使用抽象类,您仍然可以添加行为而不正在破坏现有代码。如果需要考虑兼容性,请考虑使用抽象类。
    • 即使您有默认的实现或内部状态,考虑提供一个接口和它的抽象实现。这将帮助客户,但如果期望的〔1〕。当然,这个问题已经详细讨论过了其他地方[2,3]。

    (1)当然,它增加了更多的代码,但是如果简洁是你的首要关注点,你可能应该首先避免Java。

    〔2〕Joshua Bloch,有效Java,项目16-18。

    [3]http://www.codeproject.com/kb/ar…


    当我第一次开始学习面向对象编程时,我犯了一个简单的、可能也是常见的错误,即使用继承来共享公共行为——即使在这种行为对对象的本质不重要的情况下也是如此。

    为了进一步建立一个在这个特定问题中被广泛使用的例子,有很多东西是可宠爱的——女朋友、汽车、模糊的毯子……-所以我可能有一个petable类,它提供了这种常见的行为,以及继承它的各种类。

    然而,可宠物并不是这些物体本质的一部分。有很多更重要的概念,对他们的本质至关重要-女朋友是一个人,汽车是陆上交通工具,猫是哺乳动物…

    行为应该首先分配给接口(包括类的默认接口),并且只有在以下情况下才提升为基类:(a)行为对于属于较大类的子集的一大组类是通用的,在这个意义上,"cat"和"person"是"哺乳动物"的子集。

    关键是,在你比我一开始更了解面向对象的设计之后,你通常会自动地做这件事,而不必考虑它。因此,"代码到接口,而不是抽象类"这句话的真实性变得如此明显,以至于你很难相信有人会费心去说它,并且开始尝试在它里面解读其他含义。

    我要补充的另一件事是,如果一个类是纯抽象的——没有向子级、父级或客户端公开的非抽象、非继承成员或方法——那么为什么它是一个类?它可以被替换,在某些情况下可以被接口替换,在其他情况下可以被空值替换。


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

    公共继承在OOD中被过度使用,并且表达的内容比大多数开发人员意识到的或愿意实现的要多得多。见Liskov可替代性原理

    简而言之,如果一个"是"B,那么对于它公开的每一个方法,A不需要超过B,并且提供不少于B。


    概念上,接口用于正式和半正式地定义对象将提供的一组方法。形式上是指一组方法名称和签名,半形式上是指与这些方法相关的人类可读文档。接口只是一个API的描述(毕竟,API代表应用程序程序员接口),它们不能包含任何实现,并且不可能使用或运行接口。它们只明确说明了应该如何与对象交互的契约。

    类提供了一个实现,它们可以声明它们实现了零个、一个或多个接口。如果一个类打算被继承,约定是在类名前面加上"base"。

    基类和抽象基类(abc)之间有区别。ABCS将接口和实现混合在一起。计算机程序设计外的抽象表示"摘要",即"抽象==接口"。然后,抽象基类既可以描述接口,也可以描述打算继承的空的、部分的或完整的实现。

    关于什么时候使用接口,抽象基类,什么时候只使用类的观点,将因开发内容和开发语言的不同而大不相同。接口通常只与静态类型的语言(如Java或C语言)关联,但动态类型的语言也可以具有接口和抽象基类。例如,在Python中,明确了类(声明它实现了接口)和对象(表示类的实例)之间的区别,并说它们提供了接口。在动态语言中,两个对象都是同一类的实例,可能会声明它们提供了完全不同的接口。在Python中,这仅适用于对象属性,而方法是类中所有对象之间的共享状态。然而,在Ruby中,对象可以有每个实例的方法,因此同一类的两个对象之间的接口可能会根据程序员的需要而变化(然而,Ruby没有任何显式的接口声明方法)。

    在动态语言中,对象的接口通常是隐式假定的,要么是通过内省对象并询问它提供了什么方法(在跳跃之前查看),或者最好是简单地尝试在对象上使用所需的接口,如果对象没有提供该接口,则捕获异常(更容易请求对对象的原谅,因为它可以通过n允许)。这可能导致"误报",其中两个接口具有相同的方法名,但语义上不同,但是权衡是,您的代码更灵活,因为您不需要预先过度指定,以预测代码的所有可能用途。


    另一个要记住的选择是使用"has-a"关系,也就是"composition"来实现"has-a"关系,有时这是一种比使用"is-a"继承更干净、更灵活的方法来构造事物。

    从逻辑上讲,狗和猫"都有"宠物可能没有那么合理,但它避免了常见的多重继承陷阱:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    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); }
    }

    是的,这个例子表明,用这种方式做事情有很多代码重复和缺乏优雅。但是,我们也应该理解,这有助于使狗和猫与宠物阶级脱钩(因为狗和猫无法接触到宠物的私人成员),并且为狗和猫从其他东西(可能是哺乳动物阶级)继承留下了空间。

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

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


    以前关于使用抽象类来实现公共实现的意见是明确的。我还没有提到的一个好处是,接口的使用使得为了单元测试而实现模拟对象更加容易。将ipet和petbase定义为jason-cohen所描述的类型,可以让您轻松模拟不同的数据条件,而无需物理数据库的开销(直到您决定要测试实际情况为止)。


    在某些意义上,接口和抽象类可以互换。但是,不同之处在于:i)接口不能实现代码;i i)因此,接口不能进一步调用堆栈以生成子类;i i i)抽象类只能在一个类上继承,而多个接口可以在一个类上实现。


    我发现,在以下用例中,界面>抽象>具体的模式起作用:

    1
    2
    3
    1.  You have a general interface (eg IPet)
    2.  You have a implementation that is less general (eg Mammal)
    3.  You have many concrete members (eg Cat, Dog, Ape)

    抽象类定义具体类的默认共享属性,但强制使用接口。例如:

    1
    2
    3
    4
    5
    6
    7
    8
    public interface IPet{

        public boolean hasHair();

        public boolean walksUprights();

        public boolean hasNipples();
    }

    现在,因为所有的哺乳动物都有头发和乳头(阿法克,我不是动物学家),所以我们可以把它转入抽象的基础类。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public abstract class Mammal() implements IPet{

         @override
         public walksUpright(){
             throw new NotSupportedException("Walks Upright not implemented");
         }

         @override
         public hasNipples(){return true}

         @override
         public hasHair(){return true}

    然后,具体的类仅仅定义了它们直立行走。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Ape extends Mammal(){

        @override
        public walksUpright(return true)
    }

    public class Catextends Mammal(){

        @override
        public walksUpright(return false)
    }

    当有许多具体的类时,这种设计是很好的,并且您不希望维护样板文件只是为了编程到接口。如果将新方法添加到接口中,它将破坏所有生成的类,因此您仍然可以获得接口方法的优势。

    在这种情况下,抽象也可以是具体的;但是,抽象的命名有助于强调正在使用这种模式。


    这取决于你的要求。如果IPET足够简单,我宁愿实现它。否则,如果petbase实现了大量您不想复制的功能,那么就拥有它。

    实现基类的缺点是对现有方法的要求。这使它们成为虚拟方法,这意味着您必须注意如何使用对象实例。

    最后,.NET的单一继承杀死了我。一个天真的例子:假设您正在创建一个用户控件,所以您继承了UserControl。但是,现在你被禁止继承PetBase。这迫使你重新组织,例如改为成为一个PetBase类成员。


    乔尔:一些语言(例如,C++)允许多重继承。


    我通常也不执行,直到我需要一个。我喜欢接口而不是抽象类,因为这样可以提供更多的灵活性。如果在一些继承类中有共同的行为,我会将其上移并创建一个抽象的基类。我不认为两者都需要,因为它们本质上是为相同的目的服务的,并且两者都是解决方案被过度设计的不良代码味道(imho)。


    通过def,接口提供了一个与其他代码通信的层。默认情况下,类的所有公共属性和方法都实现隐式接口。我们还可以将接口定义为一个角色,当任何类需要扮演这个角色时,它必须实现它,根据实现它的类给它不同的实现形式。因此,当您谈论接口时,您谈论的是多态性;当您谈论基类时,您谈论的是继承。两个糟糕的概念!!!!


    When should I use an interface and when should I use a base class?

    如果

  • 你有纯的abstract方法,没有非抽象的方法
  • 您没有默认实现EDCOX1,1个方法(除了Java 8语言,接口方法提供默认实现)
  • 如果您正在使用Java 8,现在接口将提供一些非抽象方法的默认实现。这将使interfaceabstract类更有用。
  • 了解更多详细信息,请查看此SE问题。

    Should it always be an interface if I don't want to actually define a base implementation of the methods?

    对。它更好更干净。即使您有一个带有一些抽象方法的基类,我们还是让基类通过接口扩展abstract方法。以后可以更改接口而不更改基类。

    Java中的示例:

    1
    2
    3
    4
    abstract class PetBase implements IPet {
    // Add all abstract methods in IPet interface and keep base class clean.
       Base class will contain only non abstract methods and static methods.
    }

    If I have a Dog and Cat class. Why would I want to implement IPet instead of PetBase? I can understand having interfaces for ISheds or IBarks (IMakesNoise?), because those can be placed on a pet by pet basis, but I don't understand which to use for a generic Pet.

    我更喜欢让基类实现接口。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     abstract class PetBase implements IPet {
     // Add all abstract methods in IPet
     }

     /*If ISheds,IBarks is common for Pets, your PetBase can implement ISheds,IBarks.
      Respective implementations of PetBase can change the behaviour in their concrete classes*/

     abstract class PetBase implements IPet,ISheds,IBarks {
     // Add all abstract methods in respective interfaces
     }

    优势:

  • 如果我想在现有接口中再添加一个抽象方法,我只需简单地更改接口,而不需要接触抽象基类。如果我想更改合同,我将更改接口和实现类,而不必触及基类。

  • 您可以通过接口为基类提供不可变性。看看这篇文章

  • 有关更多详细信息,请参阅此相关SE问题:

    我应该如何解释接口和抽象类之间的区别?


    列出你的对象必须是、拥有或做的事情,以及你的对象可以是、拥有或做的事情。必须指示基本类型,并且可以指示接口。

    例如,你的脚底必须呼吸,而你的胸口可能会发麻。

    对问题域的分析将帮助您定义精确的层次结构。


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

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


    在inheritor of a库类应该有"A"的关系。代表"实现接口"的关系。所以只有当你使用一个库,保持一流的inheritors想是a的关系。


    除了提到ipet/petbase实现的注释之外,还有一些情况下提供访问器助手类非常有价值。

    ipet/petbase样式假定您有多个实现,从而增加了petbase的价值,因为它简化了实现。但是,如果您有两个客户机,而您有多个客户机,那么提供一个类帮助来帮助使用接口可以通过使接口更容易使用来降低成本。


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

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


    使用您自己的判断和智能。不要总是做的人(像我)是说。"你会听到一个抽象类的接口,选择",但它真的取决于。它取决于到底是一流的。

    上述案例中我们有一个层次对象的接口是一个好主意。该接口由一个工作对象和它的大学论文,所以当执行服务工作的任何对象的层次。你只是一个合同定义的工作对象和层次。

    在其他的手,当你实现A股几乎是公共服务的功能,你可以是一个普通的功能分离的完整的或单独的类,你可以移动它分成A类公共库和使它所以没有人instantiate摘要CAN的基础类。

    因此,考虑如何支持你的abstractions过这个时间。是你释放的固定接口:接口作为一个功能的一组契约可以实现任何类型。基地班可以扩展过的时间。这些扩展的一部分,成为一个来源的一流大学。


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

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


    继承还有其他的好处,比如变量能够保存父类或继承类的对象(而不必将其声明为泛型,比如"object")。

    例如,在.NET WinForms中,大多数UI组件都是从System.Windows.Forms.Control派生的,因此声明为可以"保留"任何UI元素的变量—无论是按钮、列表视图还是您拥有的内容。现在,当然,您将不能访问该项的所有属性或方法,但是您将拥有所有基本的东西——这是有用的。


    感谢JonLimjap的回答,但我想为接口和抽象基类的概念添加一些解释。

    接口类型与抽象基类

    改编自Pro C_5.0和.NET 4.5框架手册。

    接口类型可能与抽象基类非常相似。回忆当一个类被标记为抽象类时,它可以定义任意数量的抽象成员来提供所有派生类型的多态接口。但是,即使类确实定义了一组抽象成员,还可以自由定义任意数量的构造函数、字段数据、非抽象成员(使用等等。另一方面,接口只包含抽象成员定义。抽象父类建立的多态接口有一个主要限制其中,只有派生类型支持抽象父级定义的成员。然而,在更大的软件系统,开发没有公共父类的多个类层次结构是很常见的。超出System.Object。假定抽象基类中的抽象成员仅适用于派生的类型,我们无法在不同的层次结构中配置类型以支持相同的多态性接口。举例来说,假设您定义了以下抽象类:

    1
    2
    3
    4
    5
    6
    7
    8
    public abstract class CloneableType
    {
    // Only derived types can support this
    //"polymorphic interface." Classes in other
    // hierarchies have no access to this abstract
    // member.
       public abstract object Clone();
    }

    根据此定义,只有扩展可克隆类型的成员才能支持克隆()。方法。如果您创建了一组不扩展这个基类的新类,就不能获得这个多态界面。此外,您可能还记得,C不支持类的多重继承。因此,如果您想创建一个小型货车,它是一辆车,是一个可克隆的类型,那么您就不能这样做:

    1
    2
    3
    4
    5
    // Nope! Multiple inheritance is not possible in C#
    // for classes.
    public class MiniVan : Car, CloneableType
    {
    }

    正如您所猜测的那样,接口类型是可以挽救的。定义接口后,它可以由任何类或结构、任何层次结构、任何命名空间或任何程序集内实现(用任何.NET编程语言编写)。如您所见,接口是高度多态的。考虑在系统命名空间中定义的名为ICloneable的标准.NET接口。这个接口定义了一个名为clone()的方法:

    1
    2
    3
    4
    public interface ICloneable
    {
    object Clone();
    }