有没有关于克服 Java 中缺乏多重继承的既定做法?
Any established practices on overcoming the lack of multiple inheritance in Java?
我有一个 classic 钻石继承问题
A
/ \
B C
\ /
D
都是接口,我有
AImpl(A)
| \
| \
BImpl(B) CImpl(C)
| \
| \
DImpl(B,C) \
| F(C)
|
E(B,C)
其中 class E
实现接口 B
和 C
,但 F
仅实现接口 C
.
由于缺少多重继承,我目前在 DImpl
和 CImpl
中有重复的功能。
我刚刚修复了 CImpl
中的一个错误,但忘记为 DImpl
做同样的事情。显然,记住始终将代码从 CImpl
复制到 DImpl
,反之亦然,因为代码库不断增长,这不是很可持续。尽管不允许多重继承,是否有任何既定的最佳实践将两者的共享代码放在一个地方?
EDIT -- 多重继承的解决方案是让 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()
。
在 Java8 中,您可以在接口内实现默认方法,因此如果您有一个具有通用实现的接口,只需在接口内定义它们,并在需要稍微更改时覆盖它们。当然,这是假设您使用的是 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");
}
}
如您所见,您可以在接口中实现默认方法并在具体 class 中使用它。在这种情况下,ElectriCar 是 Driveable 和 Machine,但我们希望它使用 Driveable start() 方法,因为在一天结束时,无论我们的车里有多少台机器(计算机),我们仍然只想驾驶它。
这只是一个例子,虽然这个例子可能有点奇怪,但我希望它有助于理解能够实现默认方法的要点。
更新您的示例来源:
在您的 Monster 和 Animal 能够 运行 的情况下,您应该有一个带有 run()
实现的 RunnableCreature 接口。这样,如果 Monster 和 Animal 运行 相同,它们可以引用默认的 run()
方法,否则它可以覆盖它并实现自己的方法。
如果您需要您的默认方法来操作变量,您的两个(或更多)具有相同 运行() 方法的 classes 将具有共同的属性,因此应该具有共同的基础class。您可以将此基 class 传递给默认方法并根据需要操作其变量。
不要在 D 中实现 C,而是在 D 中继承组合它。这样你就不会复制你的代码并且只有一个 C 的实现。
如果您以某种方式需要从 C 继承(我认为您需要审查设计),那么我仍然建议您编写 C 并在 D 中实现 C 的所有方法,并通过组合对象委托调用。
一个常见的替代方法是使用组合而不是继承。说 D
是一个 B
并且 也是一个 C
有意义吗? D
真的需要公开 B
和 C
中包含的 every public 方法吗?如果以上任何一个问题的答案是否定的,那么继承就不是完成这项工作的正确工具。
如果您想进一步了解组合的优点,请查看 Effective Java 中的这一章:Favor composition over inheritance
这是一个非常糟糕的设计。首先,您不应该仅仅因为它在接口中是可能的就拥有菱形结构。
OOP 的基本原则之一是
组合优于继承!
我的意思是你根本不需要接口 D。无论您需要在何处使用 DImpl
,只需提供对 interface A
的引用,然后根据您的 运行 时间需要将 BIml
或 CImpl
实例传递给它。这样一来,您只需更改 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
没有扩展或实现任何东西。
这就是我要说的 - 组合优于继承!。
我有一个 classic 钻石继承问题
A
/ \
B C
\ /
D
都是接口,我有
AImpl(A)
| \
| \
BImpl(B) CImpl(C)
| \
| \
DImpl(B,C) \
| F(C)
|
E(B,C)
其中 class E
实现接口 B
和 C
,但 F
仅实现接口 C
.
由于缺少多重继承,我目前在 DImpl
和 CImpl
中有重复的功能。
我刚刚修复了 CImpl
中的一个错误,但忘记为 DImpl
做同样的事情。显然,记住始终将代码从 CImpl
复制到 DImpl
,反之亦然,因为代码库不断增长,这不是很可持续。尽管不允许多重继承,是否有任何既定的最佳实践将两者的共享代码放在一个地方?
EDIT -- 多重继承的解决方案是让 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()
。
在 Java8 中,您可以在接口内实现默认方法,因此如果您有一个具有通用实现的接口,只需在接口内定义它们,并在需要稍微更改时覆盖它们。当然,这是假设您使用的是 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");
}
}
如您所见,您可以在接口中实现默认方法并在具体 class 中使用它。在这种情况下,ElectriCar 是 Driveable 和 Machine,但我们希望它使用 Driveable start() 方法,因为在一天结束时,无论我们的车里有多少台机器(计算机),我们仍然只想驾驶它。
这只是一个例子,虽然这个例子可能有点奇怪,但我希望它有助于理解能够实现默认方法的要点。
更新您的示例来源:
在您的 Monster 和 Animal 能够 运行 的情况下,您应该有一个带有 run()
实现的 RunnableCreature 接口。这样,如果 Monster 和 Animal 运行 相同,它们可以引用默认的 run()
方法,否则它可以覆盖它并实现自己的方法。
如果您需要您的默认方法来操作变量,您的两个(或更多)具有相同 运行() 方法的 classes 将具有共同的属性,因此应该具有共同的基础class。您可以将此基 class 传递给默认方法并根据需要操作其变量。
不要在 D 中实现 C,而是在 D 中继承组合它。这样你就不会复制你的代码并且只有一个 C 的实现。
如果您以某种方式需要从 C 继承(我认为您需要审查设计),那么我仍然建议您编写 C 并在 D 中实现 C 的所有方法,并通过组合对象委托调用。
一个常见的替代方法是使用组合而不是继承。说 D
是一个 B
并且 也是一个 C
有意义吗? D
真的需要公开 B
和 C
中包含的 every public 方法吗?如果以上任何一个问题的答案是否定的,那么继承就不是完成这项工作的正确工具。
如果您想进一步了解组合的优点,请查看 Effective Java 中的这一章:Favor composition over inheritance
这是一个非常糟糕的设计。首先,您不应该仅仅因为它在接口中是可能的就拥有菱形结构。
OOP 的基本原则之一是
组合优于继承!
我的意思是你根本不需要接口 D。无论您需要在何处使用 DImpl
,只需提供对 interface A
的引用,然后根据您的 运行 时间需要将 BIml
或 CImpl
实例传递给它。这样一来,您只需更改 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
没有扩展或实现任何东西。
这就是我要说的 - 组合优于继承!。