有关克服Java缺乏多重继承的既定做法?

时间:2015-03-21 06:16:12

标签: java multiple-inheritance diamond-problem

我有一个经典的钻石继承问题

   A
 /   \
B     C
 \   /
   D

是所有接口,我有

AImpl(A)
|       \
|        \
BImpl(B)  CImpl(C)
|          \
|           \
DImpl(B,C)   \
|             F(C)
|
E(B,C)

其中,类E实现了接口BC,但F仅实现了接口C

由于缺少多重继承,我目前在DImplCImpl中有重复的功能。

我刚刚修复了CImpl中的错误,但忘了对DImpl执行相同操作。显然,记住始终将代码从CImpl复制到DImpl,反之亦然,因为代码库不断增长,因此不可持续。是否存在将两者的共享代码放在一个地方的既定最佳实践,尽管不允许多重继承?

编辑 - 具有多重继承的解决方案应该DImpl继承CImpl.cFunction(),而不是将DImpl.cFunction重新定义为CImpl.cFunction的副本

编辑2 - 示例代码:

public interface Animal {
  public void eat();
}

public interface FlyingAnimal extends Animal {
  public void fly();
}

public interface RunningAnimal extends Animal {
  public void run();
}

public interface Monster extends FlyingAnimal, RunningAnimal {
  public void roar();
}

public class AnimalImpl implements Animal {
  @Override
  public void eat() {
    ...
  }
}

public class FlyingAnimalImpl extends AnimalImpl implements FlyingAnimal {
  @Override
  public void fly() {
    ...
  }
}

public class RunningAnimalImpl extends AnimalImpl implements RunningAnimal {
  @Override
  public void run() {
    ...
  }
}

public class MonsterImpl extends FlyingAnimalImpl implements Monster {
  @Override
  public void run() {
    ...
  }

  @Override
  public void roar() {
    ...
  }
}

public class ScaryMonster extends MonsterImpl implements Monster {
  public void sneakAround() {
    ...
  }
}

public class Human extends RunningAnimalImpl implements RunningAnimal {
  public void scream() {
    ...
  }
}

现在,如果我在RunningAnimalImpl.run()中发现错误并修复了错误,我必须记住将修复程序复制到MonsterImpl.run()

4 个答案:

答案 0 :(得分:2)

在Java 8中,您可以在接口中实现默认方法,因此如果您有一个具有常见实现的接口,只需在接口内部定义它们,并在需要稍微更改它时覆盖它们。当然,假设您使用的是Java 8。

例如:

public interface A {
    default void cFunction(){
       System.out.println("Calling A.cFunction");
    }
}

public class DImpl implements A {
}

DImpl可以调用cFunction,默认调用接口实现。

如果2个接口具有相同签名的方法,则可以通过引用接口名称以及A.super.cFunction()

等方法来调用它们。

更有意义的例子:

public interface Driveable {
    default void start(Vehicle vehicle){
       System.out.println("Starting my driveable thing");
       vehicle.mileage++;
    }
}


public interface Machine {
    default void start(){
       System.out.println("Starting my machine");
    }
}



public class ElectricCar extends Vehicle implements Driveable, Machine {
    public void start(){
       Driveable.super.start(this);
    }
}

public class DIYCar extends Vehicle implements Driveable, Machine {
    public void start(){
        System.out.println("instant fire");
    }
}

如您所见,您可以在界面中实现默认方法,并在具体类中使用它。在这种情况下,ElectriCar是可驱动的和机器,但我们希望它使用Driveable start()方法,因为在一天结束时,无论我们的车中有多少台机器(计算机),我们仍然只想驾驶它。

这只是一个例子,虽然这个例子可能有点奇怪,但我希望它有助于理解能够实现默认方法。

您的示例来源的更新:

在你的Monster and Animal能够运行的情况下,你应该有一个RunnableCreature接口,其实现为run()。这样,如果Monster和Animal运行相同,他们可以引用默认的run()方法,否则它可以覆盖它并实现它自己的方法。

如果您需要默认方法来操作变量,那么具有相同run()方法的两个(或更多)类将具有公共属性,因此应具有公共基类。您可以将此基类传递给默认方法并根据需要操作其变量。

答案 1 :(得分:2)

这是一个非常糟糕的设计。你不应该首先使用钻石结构,因为它可以在界面中使用。

OOP的基本原则之一是

首选组合而不是继承!

我所说的是你根本不需要接口D.无论您何时需要使用DImpl,只需提供对interface A的引用,然后根据您的运行时需要将BImlCImpl实例传递给它。这样,您需要更改的是BImpl代码或CImpl代码以进行错误修复,并且它将在您今天使用DImpl实例的任何位置使用。

根据您的评论,代码类似于 -

public class ScaryMonster {
  Animal animal;
  public  ScaryMonster(Animal animal) {
      this.animal = animal;
  }

  public void fly() {
      if(animal instanceof FlyingAnimal ) {
        ((FlyingAnimal )animal).fly();
      }
      else {
        throw new Exception("This mosnter cannot fly");
      }     
  }

  public void run() {
    if(animal instanceof RunningAnimal ) {
        ((RunningAnimal )animal).run();
      }
      else {
        throw new Exception("This mosnter cannot run");
      }
  }

  public void sneakAround() {
    ...
  }
}

如果你想让你的怪物同时飞行并将MonsterImpl的实例传递给构造函数。请注意,ScaryMonster不会扩展或实现任何内容。

这就是我所说的 - 首选组合而不是继承!

答案 2 :(得分:1)

不要在D中实现C,而不是继承在D中构建它。这样你就不会复制你的代码并只有一个C的实现。

如果你以某种方式需要从C继承(我认为你需要审查设计),那么我仍然建议你编写C并在D中实现C的所有方法并通过组合对象委托调用。

答案 3 :(得分:1)

一种常见的替代方法是使用组合而不是继承。说D B也是 C是否有意义? D是否真的需要公开BC中包含的每个公开方法?如果这些问题的答案都是否定的,那么继承就不适合这项工作。

如果您想进一步阅读撰写的优点,请参阅Effective Java:Favor composition over inheritance