为什么在多态中使用基类和接口?

时间:2022-09-24 23:41:41

In the C# example of polymorphism, there is a Cat class which inherits a class called AnimalBase and an interface called IAnimal.

在多态性的C#示例中,有一个Cat类,它继承了一个名为AnimalBase的类和一个名为IAnimal的接口。

The link in question is: http://en.wikipedia.org/wiki/Polymorphism_in_object-oriented_programming

有问题的链接是:http://en.wikipedia.org/wiki/Polymorphism_in_object-oriented_programming

My question is, why is both a base class and an interface used? Why not one or the other? I was of the school of thought that only an abstract class would be required for implementing polymorphism.

我的问题是,为什么要使用基类和接口?为什么不是一个或另一个?我认为实现多态性只需要一个抽象类。

Thanks

7 个解决方案

#1


The statement that "inheriting from a base class allows you to inherit BEHAVIOR, whereas implementing an interface only lets you specify INTERACTION" is absolutely true.

“从基类继承允许继承BEHAVIOR,而实现接口只允许您指定INTERACTION”的语句绝对正确。

But more importantly, interfaces allow statically typed languages to continue to support polymorphism. An Object Oriented purist would insist that a language should provide inheritance, encapsulation, modularity and polymorphism in order to be a fully-featured Object Oriented language. In dynamically-typed - or duck typed - languages (like Smalltalk,) polymorphism is trivial; however, in statically typed languages (like Java or C#,) polymorphism is far from trivial (in fact, on the surface it seems to be at odds with the notion of strong typing.)

但更重要的是,接口允许静态类型语言继续支持多态。面向对象的纯粹主义者会坚持认为语言应该提供继承,封装,模块化和多态,以便成为一个功能齐全的面向对象语言。在动态类型 - 或鸭类型 - 语言(如Smalltalk)中,多态性是微不足道的;然而,在静态类型语言(如Java或C#)中,多态性远非微不足道(事实上,从表面上看,它似乎与强类型的概念不一致。)

Let me demonstrate:

让我来证明一下:

In a dynamically-typed (or duck typed) language (like Smalltalk), all variables are references to objects (nothing less and nothing more.) So, in Smalltalk, I can do this:

在动态类型(或鸭子类型)语言(如Smalltalk)中,所有变量都是对象的引用(没有更多,仅此而已。)因此,在Smalltalk中,我可以这样做:

|anAnimal|    
anAnimal := Pig new.
anAnimal makeNoise.

anAnimal := Cow new.
anAnimal makeNoise.

That code:

  1. Declares a local variable called anAnimal (note that we DO NOT specify the TYPE of the variable - all variables are references to an object, no more and no less.)
  2. 声明一个名为anAnimal的局部变量(请注意,我们不指定变量的TYPE - 所有变量都是对象的引用,不多也不少。)

  3. Creates a new instance of the class named "Pig"
  4. 创建名为“Pig”的类的新实例

  5. Assigns that new instance of Duck to the variable anAnimal.
  6. 将Duck的新实例分配给变量anAnimal。

  7. Sends the message makeNoise to the pig.
  8. 将消息makeNoise发送给猪。

  9. Repeats the whole thing using a cow, but assigning it to the same exact variable as the Pig.
  10. 使用牛重复整个事情,但将其分配给与Pig相同的确切变量。

The same Java code would look something like this (making the assumption that Duck and Cow are subclasses of Animal:

相同的Java代码看起来像这样(假设Duck和Cow是Animal的子类:

Animal anAnimal = new Pig();
duck.makeNoise();

anAnimal = new Cow();
cow.makeNoise();

That's all well and good, until we introduce class Vegetable. Vegetables have some of the same behavior as Animal, but not all. For example, both Animal and Vegetable might be able to grow, but clearly vegetables don't make noise and animals cannot be harvested.

这一切都很好,直到我们介绍类蔬菜。蔬菜与动物有一些相同的行为,但不是全部。例如,动物和蔬菜都可能生长,但显然蔬菜不会产生噪音,动物也无法收获。

In Smalltalk, we can write this:

在Smalltalk中,我们可以这样写:

|aFarmObject|
aFarmObject := Cow new.
aFarmObject grow.
aFarmObject makeNoise.

aFarmObject := Corn new.
aFarmObject grow.
aFarmObject harvest.

This works perfectly well in Smalltalk because it is duck-typed (if it walks like a duck, and quacks like a duck - it is a duck.) In this case, when a message is sent to an object, a lookup is performed on the receiver's method list, and if a matching method is found, it is called. If not, some kind of NoSuchMethodError exception is thrown - but it's all done at runtime.

这在Smalltalk中运行得非常好,因为它是鸭子类型(如果它像鸭子一样行走,像鸭子一样呱呱叫它 - 它是一只鸭子。)在这种情况下,当一条消息被发送到一个对象时,就会执行查找。接收者的方法列表,如果找到匹配的方法,则调用它。如果没有,则会抛出某种NoSuchMethodError异常 - 但它都是在运行时完成的。

But in Java, a statically typed language, what type can we assign to our variable? Corn needs to inherit from Vegetable, to support grow, but cannot inherit from Animal, because it does not make noise. Cow needs to inherit from Animal to support makeNoise, but cannot inherit from Vegetable because it should not implement harvest. It looks like we need multiple inheritance - the ability to inherit from more than one class. But that turns out to be a pretty difficult language feature because of all the edge cases that pop up (what happens when more than one parallel superclass implement the same method?, etc.)

但在Java中,一种静态类型语言,我们可以为变量分配什么类型?玉米需要从蔬菜中继承,以支持生长,但不能继承动物,因为它不会产生噪音。 Cow需要继承Animal以支持makeNoise,但不能继承VEG,因为它不应该实现收获。看起来我们需要多重继承 - 从多个类继承的能力。但事实证明这是一个非常困难的语言特性,因为弹出的所有边缘情况(当多个并行超类实现相同的方法时会发生什么?等)

Along come interfaces...

接下来的接口......

If we make Animal and Vegetable classes, with each implementing Growable, we can declare that our Cow is Animal and our Corn is Vegetable. We can also declare that both Animal and Vegetable are Growable. That lets us write this to grow everything:

如果我们制作动物和蔬菜课程,每个实施Growable,我们可以宣布我们的牛是动物,我们的玉米是蔬菜。我们还可以宣布动物和蔬菜都是可以生长的。这让我们写这个来增长一切:

List<Growable> list = new ArrayList<Growable>();
list.add(new Cow());
list.add(new Corn());
list.add(new Pig());

for(Growable g : list) {
   g.grow();
}

And it lets us do this, to make animal noises:

它让我们这样做,发出动物的声音:

List<Animal> list = new ArrayList<Animal>();
list.add(new Cow());
list.add(new Pig());
for(Animal a : list) {
  a.makeNoise();
}

The biggest advantage to the duck-typed language is that you get really nice polymorphism: all a class has to do to provide behavior is provide the method (there are other tradeoffs, but this is the big one when discussing typing.) As long as everyone plays nice, and only sends messages that match defined methods, all is good. The downside is that the kind of error below isn't caught until runtime:

duck-typed语言的最大优点是你得到了非常好的多态性:所有类都必须提供行为提供方法(还有其他权衡,但这是讨论打字时的大问题。)只要每个人都玩得很好,只发送符合定义方法的消息,一切都很好。缺点是下面的错误类型直到运行时才被捕获:

|aFarmObject|
aFarmObject := Corn new.
aFarmObject makeNoise. // No compiler error - not checked until runtime.

Statically-typed languages provide much better "programming by contract," because they will catch the two kinds of error below at compile-time:

静态类型语言提供了更好的“按合同编程”,因为它们将在编译时捕获下面的两种错误:

Animal farmObject = new Corn();  // Compiler error: Corn cannot be cast to Animal.
farmObject makeNoise();

--

Animal farmObject = new Cow();
farmObject.harvest(); // Compiler error: Animal doesn't have the harvest message.

So....to summarize:

  1. Interface implementation allows you to specify what kinds of things objects can do (interaction) and Class inheritance lets you specify how things should be done (implementation).

    接口实现允许您指定对象可以执行的操作(交互),而类继承允许您指定应该如何完成(实现)。

  2. Interfaces give us many of the benefits of "true" polymorphism, without sacrificing compiler type checking.

    接口为我们提供了许多“真正的”多态性的好处,而不会牺牲编译器类型检查。

#2


Base Classes are used when you want to reuse BEHAVIOR

如果要重用BEHAVIOR,则使用基类

Interfaces are used when you want to control how the class INTERACTS with other objects. It defines the interaction in a precis manner.

当您想要控制类与其他对象的INTERACTS时,使用接口。它以精确的方式定义了交互。

In my experience the amount of times you want to control how classes interact dwarfs the times when you want to reuse behavior.

根据我的经验,您希望控制类交互方式的次数使您希望重用行为的次数相形见绌。

#3


Having an abstract class lets you implement some/most of the members in a common way. Having an interface means you aren't restricted to only using that abstract base class when you want to use it for polymorphism.

使用抽象类可以以通用方式实现某些/大多数成员。拥有一个接口意味着当你想将它用于多态时,你不仅限于使用那个抽象基类。

I don't see any contradiction in having both.

我认为两者并不矛盾。

#4


Interfaces provide you the ability to have polymorphic behaviour across class heirachy's. The downside is that you can't inherit a default implementation (directly). With class polymorphism you can only get that polymorphic behaviour within your class heirachy, but you can inherit common/default behaviour. By inherting and providing an interface you provide a contract (the interface) but get the easy implementation benefits of inheritance while still allowing others to support the "contract" outside of the baseclass limitations.

接口为您提供跨类heirachy的多态行为的能力。缺点是您不能(直接)继承默认实现。使用类多态性,您只能在类heirachy中获得该多态行为,但您可以继承常见/默认行为。通过提供和提供接口,您可以提供合同(接口),但获得继承的简单实现优势,同时仍允许其他人支持基类限制之外的“合同”。

#5


Interfaces enforce "Behavior". Any class that is declared to implement a specified interface MUST implement members with the signatures declared in the interface.. i.e, they must publically that specified behavior... They don't necessarly have to implement the behavior in the same way, but they musrt be capable of the same behavior... i.e. both a bird and a worm "CanMove", so they both must implement the behavior" of being able "to Move", Specifying that they both must impement interface ICanMove does this... How they do it is the function of implementation.

接口强制执行“行为”。声明为实现指定接口的任何类必须实现具有在接口中声明的签名的成员。即,它们必须公开地指定行为......它们不一定必须以相同的方式实现行为,但它们musrt能够有相同的行为......即鸟和蠕虫“CanMove”,所以他们都必须实现“能够”移动的行为“,指定他们都必须阻止界面ICanMove这样做...他们如何做到这一点是实施的功能。

Base classes are for reuse of "Implementation"...

基类用于重用“实现”......

That's why naming conventions for interfaces suggest using "I[Verb/Adverb]" as in IEnumerable, ICanMove, ICanLog, etc.

这就是为什么接口的命名约定建议在IEnumerable,ICanMove,ICanLog等中使用“I [Verb / Adverb]”。

You use base classes to put common implementation in a single place. If an abstract base class has no implementation, in any member, then it functions identically to an interface, which cannot have implementation

您可以使用基类将常见实现放在一个位置。如果抽象基类在任何成员中都没有实现,那么它的功能与接口相同,后者无法实现

#6


Base classes and interfaces really have mostly unrelated purposes. The main purpose of the base class is for your inheriting class to be able to import some common functionality. The main purpose of the interface is for other classes to be able to ask the question, "does this object support interface X"?

基类和接口确实具有大多数不相关的目的。基类的主要目的是让继承类能够导入一些常用功能。接口的主要目的是让其他类能够提出问题,“这个对象是否支持接口X”?

#7


So I guess I could inherit an animal class for common behaviour, like walk, sleep, etc, and then have a specialised interface (for a lion, perhaps), which would contain specific behaviour and properties - like the animal is dangerous, eats humans, etc.

所以我想我可以继承动物类的常见行为,比如走路,睡觉等,然后有一个专门的界面(对于狮子,也许),它将包含特定的行为和属性 - 就像动物是危险的,吃人类等

#1


The statement that "inheriting from a base class allows you to inherit BEHAVIOR, whereas implementing an interface only lets you specify INTERACTION" is absolutely true.

“从基类继承允许继承BEHAVIOR,而实现接口只允许您指定INTERACTION”的语句绝对正确。

But more importantly, interfaces allow statically typed languages to continue to support polymorphism. An Object Oriented purist would insist that a language should provide inheritance, encapsulation, modularity and polymorphism in order to be a fully-featured Object Oriented language. In dynamically-typed - or duck typed - languages (like Smalltalk,) polymorphism is trivial; however, in statically typed languages (like Java or C#,) polymorphism is far from trivial (in fact, on the surface it seems to be at odds with the notion of strong typing.)

但更重要的是,接口允许静态类型语言继续支持多态。面向对象的纯粹主义者会坚持认为语言应该提供继承,封装,模块化和多态,以便成为一个功能齐全的面向对象语言。在动态类型 - 或鸭类型 - 语言(如Smalltalk)中,多态性是微不足道的;然而,在静态类型语言(如Java或C#)中,多态性远非微不足道(事实上,从表面上看,它似乎与强类型的概念不一致。)

Let me demonstrate:

让我来证明一下:

In a dynamically-typed (or duck typed) language (like Smalltalk), all variables are references to objects (nothing less and nothing more.) So, in Smalltalk, I can do this:

在动态类型(或鸭子类型)语言(如Smalltalk)中,所有变量都是对象的引用(没有更多,仅此而已。)因此,在Smalltalk中,我可以这样做:

|anAnimal|    
anAnimal := Pig new.
anAnimal makeNoise.

anAnimal := Cow new.
anAnimal makeNoise.

That code:

  1. Declares a local variable called anAnimal (note that we DO NOT specify the TYPE of the variable - all variables are references to an object, no more and no less.)
  2. 声明一个名为anAnimal的局部变量(请注意,我们不指定变量的TYPE - 所有变量都是对象的引用,不多也不少。)

  3. Creates a new instance of the class named "Pig"
  4. 创建名为“Pig”的类的新实例

  5. Assigns that new instance of Duck to the variable anAnimal.
  6. 将Duck的新实例分配给变量anAnimal。

  7. Sends the message makeNoise to the pig.
  8. 将消息makeNoise发送给猪。

  9. Repeats the whole thing using a cow, but assigning it to the same exact variable as the Pig.
  10. 使用牛重复整个事情,但将其分配给与Pig相同的确切变量。

The same Java code would look something like this (making the assumption that Duck and Cow are subclasses of Animal:

相同的Java代码看起来像这样(假设Duck和Cow是Animal的子类:

Animal anAnimal = new Pig();
duck.makeNoise();

anAnimal = new Cow();
cow.makeNoise();

That's all well and good, until we introduce class Vegetable. Vegetables have some of the same behavior as Animal, but not all. For example, both Animal and Vegetable might be able to grow, but clearly vegetables don't make noise and animals cannot be harvested.

这一切都很好,直到我们介绍类蔬菜。蔬菜与动物有一些相同的行为,但不是全部。例如,动物和蔬菜都可能生长,但显然蔬菜不会产生噪音,动物也无法收获。

In Smalltalk, we can write this:

在Smalltalk中,我们可以这样写:

|aFarmObject|
aFarmObject := Cow new.
aFarmObject grow.
aFarmObject makeNoise.

aFarmObject := Corn new.
aFarmObject grow.
aFarmObject harvest.

This works perfectly well in Smalltalk because it is duck-typed (if it walks like a duck, and quacks like a duck - it is a duck.) In this case, when a message is sent to an object, a lookup is performed on the receiver's method list, and if a matching method is found, it is called. If not, some kind of NoSuchMethodError exception is thrown - but it's all done at runtime.

这在Smalltalk中运行得非常好,因为它是鸭子类型(如果它像鸭子一样行走,像鸭子一样呱呱叫它 - 它是一只鸭子。)在这种情况下,当一条消息被发送到一个对象时,就会执行查找。接收者的方法列表,如果找到匹配的方法,则调用它。如果没有,则会抛出某种NoSuchMethodError异常 - 但它都是在运行时完成的。

But in Java, a statically typed language, what type can we assign to our variable? Corn needs to inherit from Vegetable, to support grow, but cannot inherit from Animal, because it does not make noise. Cow needs to inherit from Animal to support makeNoise, but cannot inherit from Vegetable because it should not implement harvest. It looks like we need multiple inheritance - the ability to inherit from more than one class. But that turns out to be a pretty difficult language feature because of all the edge cases that pop up (what happens when more than one parallel superclass implement the same method?, etc.)

但在Java中,一种静态类型语言,我们可以为变量分配什么类型?玉米需要从蔬菜中继承,以支持生长,但不能继承动物,因为它不会产生噪音。 Cow需要继承Animal以支持makeNoise,但不能继承VEG,因为它不应该实现收获。看起来我们需要多重继承 - 从多个类继承的能力。但事实证明这是一个非常困难的语言特性,因为弹出的所有边缘情况(当多个并行超类实现相同的方法时会发生什么?等)

Along come interfaces...

接下来的接口......

If we make Animal and Vegetable classes, with each implementing Growable, we can declare that our Cow is Animal and our Corn is Vegetable. We can also declare that both Animal and Vegetable are Growable. That lets us write this to grow everything:

如果我们制作动物和蔬菜课程,每个实施Growable,我们可以宣布我们的牛是动物,我们的玉米是蔬菜。我们还可以宣布动物和蔬菜都是可以生长的。这让我们写这个来增长一切:

List<Growable> list = new ArrayList<Growable>();
list.add(new Cow());
list.add(new Corn());
list.add(new Pig());

for(Growable g : list) {
   g.grow();
}

And it lets us do this, to make animal noises:

它让我们这样做,发出动物的声音:

List<Animal> list = new ArrayList<Animal>();
list.add(new Cow());
list.add(new Pig());
for(Animal a : list) {
  a.makeNoise();
}

The biggest advantage to the duck-typed language is that you get really nice polymorphism: all a class has to do to provide behavior is provide the method (there are other tradeoffs, but this is the big one when discussing typing.) As long as everyone plays nice, and only sends messages that match defined methods, all is good. The downside is that the kind of error below isn't caught until runtime:

duck-typed语言的最大优点是你得到了非常好的多态性:所有类都必须提供行为提供方法(还有其他权衡,但这是讨论打字时的大问题。)只要每个人都玩得很好,只发送符合定义方法的消息,一切都很好。缺点是下面的错误类型直到运行时才被捕获:

|aFarmObject|
aFarmObject := Corn new.
aFarmObject makeNoise. // No compiler error - not checked until runtime.

Statically-typed languages provide much better "programming by contract," because they will catch the two kinds of error below at compile-time:

静态类型语言提供了更好的“按合同编程”,因为它们将在编译时捕获下面的两种错误:

Animal farmObject = new Corn();  // Compiler error: Corn cannot be cast to Animal.
farmObject makeNoise();

--

Animal farmObject = new Cow();
farmObject.harvest(); // Compiler error: Animal doesn't have the harvest message.

So....to summarize:

  1. Interface implementation allows you to specify what kinds of things objects can do (interaction) and Class inheritance lets you specify how things should be done (implementation).

    接口实现允许您指定对象可以执行的操作(交互),而类继承允许您指定应该如何完成(实现)。

  2. Interfaces give us many of the benefits of "true" polymorphism, without sacrificing compiler type checking.

    接口为我们提供了许多“真正的”多态性的好处,而不会牺牲编译器类型检查。

#2


Base Classes are used when you want to reuse BEHAVIOR

如果要重用BEHAVIOR,则使用基类

Interfaces are used when you want to control how the class INTERACTS with other objects. It defines the interaction in a precis manner.

当您想要控制类与其他对象的INTERACTS时,使用接口。它以精确的方式定义了交互。

In my experience the amount of times you want to control how classes interact dwarfs the times when you want to reuse behavior.

根据我的经验,您希望控制类交互方式的次数使您希望重用行为的次数相形见绌。

#3


Having an abstract class lets you implement some/most of the members in a common way. Having an interface means you aren't restricted to only using that abstract base class when you want to use it for polymorphism.

使用抽象类可以以通用方式实现某些/大多数成员。拥有一个接口意味着当你想将它用于多态时,你不仅限于使用那个抽象基类。

I don't see any contradiction in having both.

我认为两者并不矛盾。

#4


Interfaces provide you the ability to have polymorphic behaviour across class heirachy's. The downside is that you can't inherit a default implementation (directly). With class polymorphism you can only get that polymorphic behaviour within your class heirachy, but you can inherit common/default behaviour. By inherting and providing an interface you provide a contract (the interface) but get the easy implementation benefits of inheritance while still allowing others to support the "contract" outside of the baseclass limitations.

接口为您提供跨类heirachy的多态行为的能力。缺点是您不能(直接)继承默认实现。使用类多态性,您只能在类heirachy中获得该多态行为,但您可以继承常见/默认行为。通过提供和提供接口,您可以提供合同(接口),但获得继承的简单实现优势,同时仍允许其他人支持基类限制之外的“合同”。

#5


Interfaces enforce "Behavior". Any class that is declared to implement a specified interface MUST implement members with the signatures declared in the interface.. i.e, they must publically that specified behavior... They don't necessarly have to implement the behavior in the same way, but they musrt be capable of the same behavior... i.e. both a bird and a worm "CanMove", so they both must implement the behavior" of being able "to Move", Specifying that they both must impement interface ICanMove does this... How they do it is the function of implementation.

接口强制执行“行为”。声明为实现指定接口的任何类必须实现具有在接口中声明的签名的成员。即,它们必须公开地指定行为......它们不一定必须以相同的方式实现行为,但它们musrt能够有相同的行为......即鸟和蠕虫“CanMove”,所以他们都必须实现“能够”移动的行为“,指定他们都必须阻止界面ICanMove这样做...他们如何做到这一点是实施的功能。

Base classes are for reuse of "Implementation"...

基类用于重用“实现”......

That's why naming conventions for interfaces suggest using "I[Verb/Adverb]" as in IEnumerable, ICanMove, ICanLog, etc.

这就是为什么接口的命名约定建议在IEnumerable,ICanMove,ICanLog等中使用“I [Verb / Adverb]”。

You use base classes to put common implementation in a single place. If an abstract base class has no implementation, in any member, then it functions identically to an interface, which cannot have implementation

您可以使用基类将常见实现放在一个位置。如果抽象基类在任何成员中都没有实现,那么它的功能与接口相同,后者无法实现

#6


Base classes and interfaces really have mostly unrelated purposes. The main purpose of the base class is for your inheriting class to be able to import some common functionality. The main purpose of the interface is for other classes to be able to ask the question, "does this object support interface X"?

基类和接口确实具有大多数不相关的目的。基类的主要目的是让继承类能够导入一些常用功能。接口的主要目的是让其他类能够提出问题,“这个对象是否支持接口X”?

#7


So I guess I could inherit an animal class for common behaviour, like walk, sleep, etc, and then have a specialised interface (for a lion, perhaps), which would contain specific behaviour and properties - like the animal is dangerous, eats humans, etc.

所以我想我可以继承动物类的常见行为,比如走路,睡觉等,然后有一个专门的界面(对于狮子,也许),它将包含特定的行为和属性 - 就像动物是危险的,吃人类等

相关文章