接口与基类

时间:2020-03-05 18:51:34  来源:igfitidea点击:

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

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

如果我有猫狗班。为什么我要实现IPet而不是PetBase?我可以理解具有用于ISheds或者IBarks(IMakesNoise?)的接口,因为可以将它们逐个放置在每个宠物上,但是我不知道该为通用Pet使用哪个接口。

解决方案

回答

现代风格是定义IPet和PetBase。

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

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

回答

通常,与抽象类相比,我们应该更喜欢接口。使用抽象类的原因之一是具体类之间是否具有通用实现。当然,我们仍然应该声明一个接口(IPet)并使用一个抽象类(PetBase)实现该接口。使用小的,不同的接口,我们可以使用倍数来进一步提高灵活性。接口可以最大程度地实现跨边界类型的灵活性和可移植性。跨边界传递引用时,请始终传递接口,而不传递具体类型。这使接收端可以确定具体的实现方式并提供最大的灵活性。当以TDD / BDD方式编程时,这绝对是正确的。

四人帮在他们的书中说:"因为继承将子类公开给其父代的实现细节,所以人们常说'继承破坏封装'。我相信这是真的。

回答

一个重要的区别是我们只能继承一个基类,但是可以实现许多接口。因此,仅在绝对确定不需要继承其他基类的情况下,才希望使用基类。另外,如果我们发现接口越来越大,则应该开始将其分解为几个定义独立功能的逻辑部分,因为没有规则规定类不能全部实现(或者我们可以定义一个不同的接口)。只继承它们以对它们进行分组的接口)。

回答

这取决于要求。如果IPet很简单,我宁愿实现它。否则,如果PetBase实现了我们不想复制的大量功能,那么就可以使用它。

实现基类的不利之处是要求"覆盖"(或者"新")现有方法。这使它们成为虚拟方法,这意味着我们必须谨慎使用对象实例。

最后,.NET的单一继承使我丧命。一个简单的例子:假设我们要创建用户控件,所以我们继承了UserControl。但是,现在我们不可以继承PetBase了。这迫使我们进行重组,例如改成PetBase类成员。

回答

@Joel:某些语言(例如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.

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

PS .:买书。它要详细得多。

回答

除了提及IPet / PetBase实现的注释之外,在某些情况下,提供访问器帮助程序类可能非常有价值。

IPet / PetBase样式假定我们具有多个实现,因此可以简化实现,因此可以增加PetBase的价值。但是,如果我们有多个客户端,那么如果我们是反向客户端或者两者兼而有之,则提供类帮助来帮助使用该接口可以通过简化使用接口的方式来降低成本。

回答

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

抽象类是快捷方式。是否有PetBase的所有派生产品共享的东西,我们只需编写一次即可完成?如果是,那么该是抽象类的时候了。

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

抽象类可能包含多个接口。PetBase抽象类可以实现IPet(宠物具有所有者)和IDigestion(宠物吃,或者至少应该吃)。但是,PetBase可能不会实现IMammal,因为不是所有的宠物都是哺乳动物,也不是所有的哺乳动物都是宠物。我们可以添加扩展PetBase的MammalPetBase并添加IMammal。 FishBase可以具有PetBase并添加IFish。 IFish将ISwim和IUnderwaterBreather用作接口。

是的,对于简单的示例,我的示例过于复杂。但这是接口和抽象类如何协同工作的重要内容。

回答

让我们以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 ++),但是能够实现多个接口,因此它允许我们严格根据需要构造对象。

回答

介面

  • 定义2个模块之间的合同。无法执行。
  • 大多数语言允许我们实现多个接口
  • 修改接口是一项重大更改。所有实现都需要重新编译/修改。
  • 所有成员都是公开的。实现必须实现所有成员。
  • 接口有助于解耦。我们可以使用模拟框架来模拟接口背后的任何内容
  • 接口通常表示一种行为
  • 接口实现相互分离/隔离

基类

  • 允许我们添加一些默认的实现,我们可以通过派生免费获取这些默认实现
  • 除C ++外,我们只能派生自一个类。即使可以来自多个类,这通常也不是一个好主意。
  • 更改基类相对容易。派生不需要做任何特殊的事情
  • 基类可以声明可以通过派生访问的受保护的公共函数
  • 抽象基类不能像接口一样容易地被模拟
  • 基类通常指示类型层次结构(IS A)
  • 类派生可能取决于某些基本行为(对父类实现有复杂的了解)。如果我们更改一个人的基本实现并破坏其他人,事情可能会很混乱。

回答

这是.NET特有的,但是《框架设计指南》一书认为,一般而言,类在不断发展的框架中具有更大的灵活性。交付接口后,我们将无法在不破坏使用该接口的代码的情况下对其进行更改。但是,使用一个类,我们可以修改它,而不会破坏链接到它的代码。只要我们进行了正确的修改(包括添加新功能),就可以扩展和发展代码。

Krzysztof 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.

话虽这么说,接口肯定存在。作为一般准则,如果没有其他作为实现接口方法的示例,请始终提供接口的抽象基类实现。在最好的情况下,基类将节省大量工作。

回答

基类的继承者应具有"是"关系。接口表示一个"实现"关系。
因此,仅当继承者将维护is关系时才使用基类。

回答

运用自己的判断力,保持聪明。不要总是做别人(像我)所说的话。我们会听到"首选接口而不是抽象类",但这确实取决于。这取决于班级。

在上面提到的具有对象层次结构的情况下,接口是一个好主意。接口有助于处理这些对象的集合,并且在实现与层次结构中的任何对象一起使用的服务时也有帮助。我们只需定义用于处理层次结构中对象的合同即可。

另一方面,当我们实现一堆共享通用功能的服务时,我们可以将通用功能分成一个完整的单独类,也可以将其上移到一个通用基类中并使其抽象化,从而没人可以实例化该基类。班级。

还要考虑一下如何随着时间的推移来支持抽象。接口是固定的:我们将接口发布为任何类型均可实现的一组功能的合同。基类可以随时间扩展。这些扩展成为每个派生类的一部分。

回答

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

在某些情况下,如果在助手对象中定义了所有可变行为,我们可能会发现主要对象根本不需要接口。

因此,代替IPet或者PetBase,我们可能最终得到带有IFurBehavior参数的Pet。 IFurBehavior参数由PetFactory的CreateDog()方法设置。 shed()方法将调用此参数。

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

即使在多继承语言中,我也建议使用此模式。

回答

接口具有明显的优势,即类可以"热插拔"。将一个类从一个父类更改为另一个父类通常会导致大量工作,但是接口通常可以删除和更改,而对实现类没有很大影响。在我们可能希望某个类实现的行为有几种狭窄的集合的情况下,这特别有用。

这在我的领域特别有效:游戏编程。基类可能会因大量继承对象"可能"需要的行为而肿。使用界面,可以轻松方便地将不同的行为添加或者删除到对象。例如,如果我为要反映伤害的对象创建" IDamageEffects"接口,则可以轻松地将其应用于各种游戏对象,并在以后轻松地改变主意。假设我设计了一个初始类,该类要用于"静态"装饰对象,并且最初确定它们是不可破坏的。稍后,我可能会决定是否将它们炸开会更好玩,因此我更改了类以实现" IDamageEffects"接口。这比切换基类或者创建新的对象层次结构要容易得多。

回答

继承还有其他优点,例如,变量能够保存父类或者继承类的对象(而不必将其声明为通用对象,例如" Object")。

例如,在.NET WinForms中,大多数UI组件都派生自System.Windows.Forms.Control,因此声明为该变量的变量可以"保留"几乎所有UI元素,无论是按钮,ListView还是我们拥有的东西。现在,我们将无权访问该项目的所有属性或者方法,但是我们将拥有所有基本内容,这将很有用。

回答

胡安

我喜欢将接口视为表征类的一种方式。特定的犬种类别(例如YorkshireTerrier)可能是父犬类别的后代,但它也实现了IFurry,IStubby和IYippieDog。因此,类定义了什么是类,但是接口告诉我们有关它的信息。

这样做的好处是,例如,它使我可以收集所有IYippieDog,并将它们扔到我的Ocean系列中。因此,现在我可以遍历一组特定的对象并找到符合我正在查看的标准的对象,而不必仔细检查类。

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

这种想法与每个类都应该有一个接口,而我们应该对该接口进行编码的想法背道而驰。很好,但是最终会给类提供很多一对一的接口,这会使事情变得混乱。我了解这个想法是,它实际上并不需要花费任何时间,现在我们可以轻松地进行进出交换。但是,我发现我很少这样做。大多数时候,我只是修改现有的类,并且遇到与我完全相同的问题,如果需要更改该类的公共接口,我总​​是会做同样的事情,除了我现在必须在两个地方进行更改。

因此,如果我们像我一样认为,我们肯定会说猫和狗是IPettable。这是将两者匹配的特征。

另一个问题是它们是否应该具有相同的基类?问题是,是否需要将它们广泛地视为同一件事。当然它们都是动物,但是这与我们将它们一起使用的方式相适应。

假设我想收集所有动物类并将它们放入我的方舟容器中。

还是他们需要成为哺乳动物?也许我们需要某种跨动物挤奶工厂?

它们甚至根本不需要链接在一起吗?仅知道它们都是IPetable就足够了吗?

当我真的只需要一个班级时,我常常感到渴望获得整个班级的层次结构。我有一天会做我可能需要它的事情,通常我从不这样做。即使我这样做了,我通常也不得不做很多工作来修复它。那是因为我正在创建的头等舱不是狗,我不是那么幸运,而是鸭嘴兽。现在,我的整个类层次结构都是基于奇怪的情况,而且我有很多浪费的代码。

在某些时候,我们可能还会发现并非所有的猫都是IPetable的(就像那只无毛的猫一样)。现在,我们可以将该接口移动到所有适合的派生类。我们会发现,突然的变化要小得多,突然之间Cats不再是从PettableBase派生的。

回答

以前有关将抽象类用于常见实现的评论肯定是正确的。我尚未看到的一个好处是,使用接口可以更轻松地实现模拟对象以进行单元测试。如Jason Cohen所述定义IPet和PetBase,使我们可以轻松模拟不同的数据条件,而无需物理数据库的开销(直到我们决定是时候测试真实的东西了)。

回答

除非我们知道基类的含义,否则不要使用基类,并且该基类在这种情况下适用。如果适用,请使用它,否则,请使用接口。但是请注意有关小型接口的答案。

OOD中过度使用了公共继承,并且公开表达的内容超出了大多数开发人员意识到或者愿意接受的范围。参见李斯科夫可替代性原则

简而言之,如果A"是a" B,则对于它公开的每种方法,A所要求的不超过B且所传递的不少于B。

回答

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

如果继承的开发人员有任何理由使用自己的基类来实现类型的接口,而我们看不到接口发生变化,则可以选择接口。在这种情况下,为了方便起见,我们仍然可以抛出实现该接口的默认基类。

回答

另外请记住,不要被OO所淹没(请参阅博客),并始终根据所需的行为对对象进行建模,如果我们正在设计的应用程序中,所需的唯一行为是动物的通用名称和种类,那么我们只需要一类具有名称属性的动物,而不是世界上每种可能的动物的数百万个类。

回答

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

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

另一方面,接口表示类的其他功能。我将其称为"是"关系,就像" Foo是可抛弃的"那样,因此C#中的IDisposable接口。

回答

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

类提供了一种实现,它们可以声明自己实现了零个,一个或者多个接口。如果要继承一个Class,则约定是在Class名称前加上" Base"作为前缀。

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

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

在动态语言中,通常是通过隐式假定对象的接口,方法是对对象进行内省并询问其提供了哪些方法(请先了解一下),或者最好是简单地尝试在对象上使用所需的接口,并在对象存在时捕获异常不提供该界面(比许可权更容易获得宽恕)。这可能会导致"误报",其中两个接口具有相同的方法名称,但在语义上有所不同,但是要权衡的是,代码更加灵活,因为我们无需过度指定预先就可以预期所有可能的用途代码。

回答

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

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

干杯

回答

在Java World这篇文章中很好地解释了

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

我会有一个实现1个或者多个接口的类并不少见。

我将抽象类用作其他内容的基础。

以下摘录自上述文章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.

回答

要记住的另一种选择是使用"具有"关系,也就是"根据"或者"组成"实现。有时,与使用"是"继承相比,这是一种结构更清晰,更灵活的方法。

从逻辑上讲,"狗"和"猫"都"拥有"一只宠物可能在逻辑上没有多大意义,但它避免了常见的多重继承陷阱:

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); }
}

是的,此示例表明以这种方式执行代码涉及很多代码重复并且缺乏优雅。但是,还应该意识到,这有助于使Dog and Cat与Pet类脱钩(因为Dog and Cat无法访问Pet的私人成员),并且为Dog and Cat继承从其他事物继承的空间- -可能是哺乳动物类。

当不需要私人访问并且我们不需要使用通用Pet引用/指针来引用Dog和Cat时,最好使用组合。接口为我们提供了通用的参考功能,可以帮助减少代码的冗长性,但是当它们组织得不好时,它们也可以使事情变得模糊。当我们需要私有成员访问时,继承很有用,并且在使用继承时,我们将致力于将Dog和Cat类与Pet类高度结合,这是一笔高昂的费用。

在继承,组合和接口之间,没有一种永远都是正确的方法,它有助于考虑如何将所有三个选项和谐地使用。在这三者中,继承通常是应该最少使用的选项。

回答

Submain .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.

回答

当我刚开始学习面向对象的编程时,我犯了一个简单的并且可能是常见的错误,即使用继承来共享常见的行为,即使该行为对于对象的本质不是必需的。

为了进一步建立在这个特定问题上经常使用的示例的基础上,有很多东西可以用来宠爱女朋友,汽车,毛毯……所以我可能有一个Petable类提供了这种常见的行为,以及从中继承的各种类。

但是,成为宠物不是这些对象中任何一个的本质的一部分。对于他们的本性来说,还有更重要的更重要的概念:女友是一个人,汽车是陆地车辆,猫是哺乳动物...

行为应首先分配给接口(包括类的默认接口),并且仅当行为(a)是属于较大类的子集的一大类类所共有时,才将其提升为基类,其含义与"猫"和"人"是"哺乳动物"的子集。

问题是,在我们比起初我对面向对象的设计有了更好的理解之后,通常我们会自动进行此操作而无需考虑它。因此,"编码到接口,而不是抽象类"语句的基本事实变得如此显而易见,以至于我们很难相信任何人都愿意去说它并开始尝试将其他含义读入其中。

我要补充的另一件事是,如果一个类是纯抽象的,而没有暴露于子级,父级或者客户端的非抽象,非继承成员或者方法,那为什么它是一个类呢?在某些情况下,可以将其替换为接口,而在其他情况下,可以将其替换为Null。