爪哇多重继承

为了充分理解如何解决 Java 的多重继承问题,我有一个经典的问题需要澄清。

假设我有一个类 Animal,它有一个子类 BirdHorse,我需要创建一个从 BirdHorse延伸出来的类 Pegasus,因为 Pegasus既是一只鸟又是一匹马。

我觉得这是典型的钻石问题。据我所知,解决这个问题的经典方法是使 AnimalBirdHorse类接口,并从它们实现 Pegasus

我想知道是否还有其他方法可以解决这个问题,即我仍然可以为鸟类和马创建对象。如果有一种方法能够创造动物也将是伟大的,但不是必要的。

145272 次浏览

您可以为动物类(生物学意义上的类)创建接口,比如为马创建 public interface Equidae,为鸟创建 public interface Avialae(我不是生物学家,所以术语可能是错误的)。

那么您仍然可以创建一个

public class Bird implements Avialae {
}

还有

public class Horse implements Equidae {}

还有

public class Pegasus implements Avialae, Equidae {}

评论中补充道:

为了减少重复的代码,您可以创建一个抽象类,其中包含您想要实现的动物的大部分公共代码。

public abstract class AbstractHorse implements Equidae {}


public class Horse extends AbstractHorse {}


public class Pegasus extends AbstractHorse implements Avialae {}

更新

我想补充一个更多的细节。作为 布莱恩评论道,这是 OP 已经知道的东西。

然而,我想强调的是,我建议用接口绕过“多重继承”问题,我不建议使用已经表示具体类型的接口(比如 Bird) ,而是更多地表示一种行为(其他人指的是鸭子类型,这也很好,但我的意思只是: 鸟类的生物类,Avialae)。我也不推荐使用以大写字母“ I”开头的接口名称,比如 IBird,它只是告诉你为什么需要一个接口。这就是问题的不同之处: 使用接口构造继承层次结构,有用时使用抽象类,在需要时实现具体类,并在适当时使用委托。

从技术上来说,您一次只能扩展一个类并实现多个接口,但是当涉及到软件工程时,我宁愿建议使用一个通常无法回答的特定于问题的解决方案。顺便说一下,没有扩展具体类/只扩展抽象类来防止不必要的继承行为是很好的面向对象实践——没有所谓的“动物”,也没有使用动物对象,只有具体的动物。

接口不能模拟多重继承,Java 创建者认为多重继承是错误的,所以在 Java 中没有这样的东西。

如果您想将两个类的功能组合为一次性使用的对象组合,即。

public class Main {
private Component1 component1 = new Component1();
private Component2 component2 = new Component2();
}

如果要公开某些方法,请定义它们,并让它们将调用委托给相应的控制器。

这里的接口可能很方便-如果 Component1实现接口 Interface1,而 Component2实现 Interface2,您可以定义

class Main implements Interface1, Interface2

因此,您可以在上下文允许的情况下交换使用对象。

所以在我看来,你不能陷入钻石问题。

Ehm,您的类可以是其他类的子类,但是仍然可以实现任意多个接口。

飞马实际上是一匹马(它是一匹特殊的马) ,它能够飞翔(这是这匹特殊的马的“技能”)。另一方面,你可以说,飞马是一种鸟,它可以行走,有四条腿——这完全取决于你如何更容易地编写代码。

就像你可以说:

abstract class Animal {
private Integer hp = 0;
public void eat() {
hp++; 
}
}
interface AirCompatible {
public void fly();
}
class Bird extends Animal implements AirCompatible {
@Override
public void fly() {
//Do something useful
}
}
class Horse extends Animal {
@Override
public void eat() {
hp+=2; 
}


}
class Pegasus extends Horse implements AirCompatible {
//now every time when your Pegasus eats, will receive +2 hp
@Override
public void fly() {
//Do something useful
}
}

您可以有一个接口层次结构,然后从选定的接口扩展您的类:

public interface IAnimal {
}


public interface IBird implements IAnimal {
}


public  interface IHorse implements IAnimal {
}


public interface IPegasus implements IBird,IHorse{
}

然后根据需要定义类,方法是扩展特定的接口:

public class Bird implements IBird {
}


public class Horse implements IHorse{
}


public class Pegasus implements IPegasus {
}

我有个蠢主意:

public class Pegasus {
private Horse horseFeatures;
private Bird birdFeatures;


public Pegasus(Horse horse, Bird bird) {
this.horseFeatures = horse;
this.birdFeatures = bird;
}


public void jump() {
horseFeatures.jump();
}


public void fly() {
birdFeatures.fly();
}
}

我可以介绍一下 鸭子打字的概念吗?

最有可能的情况是,您希望 Pegasus 扩展 Bird 和 Horse 接口,但是 Duck 类型实际上表明您应该继承 行为。正如评论中已经说过的,飞马不是鸟,但它可以飞。所以您的 Pegasus 应该继承一个 Flyable-接口,比如一个 Gallopable-接口。

这种概念在 策略模式中得到了应用。这个例子实际上向你展示了一只鸭子如何继承了 FlyBehaviourQuackBehaviour,但仍然可以有鸭子,例如 RubberDuck,它不能飞。他们也可以使 Duck延长一个 Bird级,但他们会放弃一些灵活性,因为每个 Duck将能够飞行,即使是可怜的 RubberDuck

我认为这在很大程度上取决于您的需求,以及如何在代码中使用动物类。

如果您希望能够在 Pegasus 类中使用 Horse 和 Bird 实现的方法和特性,那么您可以将 Pegasus 实现为 Bird 和 Horse 的 作文:

public class Animals {


public interface Animal{
public int getNumberOfLegs();
public boolean canFly();
public boolean canBeRidden();
}


public interface Bird extends Animal{
public void doSomeBirdThing();
}
public interface Horse extends Animal{
public void doSomeHorseThing();
}
public interface Pegasus extends Bird,Horse{


}


public abstract class AnimalImpl implements Animal{
private final int numberOfLegs;


public AnimalImpl(int numberOfLegs) {
super();
this.numberOfLegs = numberOfLegs;
}


@Override
public int getNumberOfLegs() {
return numberOfLegs;
}
}


public class BirdImpl extends AnimalImpl implements Bird{


public BirdImpl() {
super(2);
}


@Override
public boolean canFly() {
return true;
}


@Override
public boolean canBeRidden() {
return false;
}


@Override
public void doSomeBirdThing() {
System.out.println("doing some bird thing...");
}


}


public class HorseImpl extends AnimalImpl implements Horse{


public HorseImpl() {
super(4);
}


@Override
public boolean canFly() {
return false;
}


@Override
public boolean canBeRidden() {
return true;
}


@Override
public void doSomeHorseThing() {
System.out.println("doing some horse thing...");
}


}


public class PegasusImpl implements Pegasus{


private final Horse horse = new HorseImpl();
private final Bird bird = new BirdImpl();




@Override
public void doSomeBirdThing() {
bird.doSomeBirdThing();
}


@Override
public int getNumberOfLegs() {
return horse.getNumberOfLegs();
}


@Override
public void doSomeHorseThing() {
horse.doSomeHorseThing();
}




@Override
public boolean canFly() {
return true;
}


@Override
public boolean canBeRidden() {
return true;
}
}
}

另一种可能性是使用 实体-组件-系统方法而不是继承来定义动物。当然,这意味着您不会有动物的单独 Java 类,而是它们只由其组件定义。

实体-组件-系统方法的一些伪代码可能如下所示:

public void createHorse(Entity entity){
entity.setComponent(NUMER_OF_LEGS, 4);
entity.setComponent(CAN_FLY, false);
entity.setComponent(CAN_BE_RIDDEN, true);
entity.setComponent(SOME_HORSE_FUNCTIONALITY, new HorseFunction());
}


public void createBird(Entity entity){
entity.setComponent(NUMER_OF_LEGS, 2);
entity.setComponent(CAN_FLY, true);
entity.setComponent(CAN_BE_RIDDEN, false);
entity.setComponent(SOME_BIRD_FUNCTIONALITY, new BirdFunction());
}


public void createPegasus(Entity entity){
createHorse(entity);
createBird(entity);
entity.setComponent(CAN_BE_RIDDEN, true);
}

将物体结合在一起有两种基本方法:

  • 第一个是 遗产。因为您已经确定了继承的局限性,这意味着您不能在这里做您需要的事情。
  • 第二个是 作曲。由于继承失败,你需要使用组合。

它的工作方式是您有一个 Animal 对象。然后在该对象中添加进一步的对象,以提供所需的属性和行为。

例如:

  • Bird 扩展 禽兽实现 < strong > IFlier
  • Horse 扩展 禽兽的工具 < strong > IHerbivore,IQuadrupe
  • Pegasus 扩展 禽兽工具 < strong > IHerbivore,IQuadrupe,IFlier

现在 IFlier看起来就像这样:

 interface IFlier {
Flier getFlier();
}

Bird是这样的:

 class Bird extends Animal implements IFlier {
Flier flier = new Flier();
public Flier getFlier() { return flier; }
}

现在你拥有了继承的所有优势。您可以重用代码。您可以拥有一个 IFliers 集合,并且可以使用多态性的所有其他优点,等等。

然而,您也可以从组合中获得所有的灵活性。您可以对每种类型的 Animal应用任意多种不同的接口和复合支持类,并对每个位的设置方式进行所需的控制。

战略模式组合的替代方法

另一种方法是让 Animal基类包含一个内部集合来保存不同行为的列表,这取决于您正在做什么以及如何做。在这种情况下,您最终会使用一些更接近于策略模式的东西。这在简化代码方面确实带来了优势(例如,Horse不需要知道任何关于 QuadrupedHerbivore的信息) ,但是如果不使用接口方法,就会失去多态性的许多优势,等等。

Java 没有多重继承问题,因为它没有多重继承。这是为了解决真正的多重继承问题(钻石问题)而设计的。

缓解这个问题有不同的策略。最容易实现的是 Pavel 提出的 Composite 对象(本质上是 C + + 处理它的方式)。我不知道通过 C3线性化的多重继承(或类似的继承)是否会成为 Java 的未来,但我对此表示怀疑。

如果你的问题是学术性的,那么正确的解决办法是鸟和马是更具体的,假设飞马仅仅是鸟和马的结合是错误的。更正确的说法是,飞马与鸟类和马有某些共同的内在属性(也就是说它们可能有共同的祖先)。正如莫里茨的回答所指出的那样,这个问题可以得到充分的模拟。

在 Java8和更高版本中,你可以使用 默认方法来实现一种类似于 c + + 的多重继承。 您还可以查看 本教程,它显示了一些比正式文档更容易开始使用的示例。

正如你已经意识到的那样,在 Java 中多重继承类是不可能的,但是在接口中却是可能的。您可能还需要考虑使用组合设计模式。

几年前我写了一篇非常全面的关于作文的文章。

Https://codereview.stackexchange.com/questions/14542/multiple-inheritance-and-composition-with-java-and-c-updated

把马放在有半扇门的马厩里是安全的,因为马不能走过半扇门。因此,我设置了一个马房服务,接受任何类型的马项目,并把它放在一个半门的马厩。

那么马也能像动物一样飞翔吗?

我过去常常思考关于多重继承的问题,但是现在我已经从事编程超过15年了,我不再关心多重继承的实现。

通常情况下,当我试图处理一个指向多重继承的设计时,我后来发布了我错过了理解问题域的版本。

或者

如果它看起来像鸭子,叫起来像鸭子,但它需要 电池,你可能有错误的抽象

为了解决 Java & rarr; 接口中的多重继承问题,使用

J2EE (core JAVA)注释作者: K.V.R 先生第51页

第27天

  1. 接口基本上用于开发用户定义的数据类型。
  2. 关于接口,我们可以实现多重继承的概念。
  3. 通过接口,我们可以实现多态的概念,动态绑定,因此我们可以提高 JAVA 程序的性能 内存空间和执行时间的轮换。

接口是一个构造,其中包含纯 未定义的方法或接口是纯粹抽象的集合 方法。

[...]

第28天:

语法 -1用于将接口的特性重用到类:

[abstract] class <clsname> implements <intf 1>,<intf 2>.........<intf n>
{
variable declaration;
method definition or declaration;
};

在上面的语法中,clsname 表示类的名称,即 从 n 个接口中继承特性 关键字,用于将接口的特性继承到 派生类。

[...]

语法 -2继承“ n”个接口到另一个接口:

interface <intf 0 name> extends <intf 1>,<intf 2>.........<intf n>
{
variable declaration cum initialization;
method declaration;
};

[...]

语法 -3:

[abstract] class <derived class name> extends <base class name> implements <intf 1>,<intf 2>.........<intf n>
{
variable declaration;
method definition or declaration;
};

为了降低复杂性和简化语言,java 不支持多重继承。

考虑一个场景,其中 A、 B 和 C 是三个类。C 类继承 A 类和 B 类。如果 A 类和 B 类具有相同的方法,并且您从子类对象调用它,那么调用 A 类或 B 类的方法将会有歧义。

因为编译时错误比运行时错误好,所以如果你继承了2个类,java 会呈现编译时错误。因此,无论你有相同的方法或不同,现在会有编译时间错误。

class A {
void msg() {
System.out.println("From A");
}
}


class B {
void msg() {
System.out.println("From B");
}
}


class C extends A,B { // suppose if this was possible
public static void main(String[] args) {
C obj = new C();
obj.msg(); // which msg() method would be invoked?
}
}
  1. 定义 接口以定义功能。您可以为多个功能定义多个接口。这些功能可以由特定的 禽兽实现。
  2. 通过共享非静态和非公共数据/方法,使用 继承遗产在类之间建立关系。
  3. 使用 装饰者 _ 模式动态添加功能。这将允许您减少继承类和组合的数量。

为了更好地理解,请看下面的例子

何时使用修饰模式?

问题还没解决。为了充分模拟这种情况,并防止代码复制,需要使用多重继承或 Mixin。使用默认函数的接口是不够的,因为不能在接口中保存成员。 接口建模导致在子类或静态中进行代码复制,这两者都是有害的。

你所能做的就是使用一个自定义的结构,把它分解成更多的组件,然后把它们组合在一起..。

玩具语言