关于oop:Java多重继承

Java Multiple Inheritance

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

假设我有Animal类,这有BirdHorse两个子类,我需要制作一个从BirdHorse延伸的Pegasus类,因为Pegasus既是鸟又是马。

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

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


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

然后您仍然可以创建

1
2
public class Bird implements Avialae {
}

1
public class Horse implements Equidae {}

而且

1
public class Pegasus implements Avialae, Equidae {}

从注释添加:

为了减少重复的代码,可以创建一个抽象类,该类包含要实现的动物的大多数公共代码。

1
2
3
4
5
public abstract class AbstractHorse implements Equidae {}

public class Horse extends AbstractHorse {}

public class Pegasus extends AbstractHorse implements Avialae {}

更新

我想再加一个细节。正如布赖恩所言,这是OP已经知道的。

然而,我要强调的是,我建议绕过接口的"多重继承"问题,我不建议使用已经表示具体类型(如bird)但更多是一种行为的接口(其他接口也指duck类型,这也很好,但我的意思只是:鸟类的生物类,Avialae)。我也不建议使用以大写字母"i"开头的接口名称,例如IBird,它只告诉您需要接口的原因。这就是问题的区别:使用接口构造继承层次结构,在有用时使用抽象类,在需要时实现具体类,并在适当时使用委托。


将对象组合在一起有两种基本方法:

  • 首先是继承。正如您已经确定的,继承的局限性意味着您不能在这里做您需要做的事情。
  • 二是构图。由于继承失败,您需要使用组合。

它的工作方式是你有一个动物物体。然后在该对象中添加更多的对象,以提供所需的属性和行为。

例如:

  • 鸟把动物的工具伸展得更快。
  • 马伸展动物工具iherbivore,iquadrued
  • 飞马座扩展了动物器具iHerbivore,iQuadruped,iLier

现在IFlier看起来是这样的:

1
2
3
 interface IFlier {
     Flier getFlier();
 }

所以Bird看起来是这样的:

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

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

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

战略模式组合替代方法

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


我有一个愚蠢的想法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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();
  }
}


我可以提出鸭子打字的概念吗?

很可能你会让飞马座扩展一个鸟和马的接口,但duck类型实际上建议你应该继承行为。正如评论中所述,飞马座不是鸟,但它能飞。所以你的Pegasus应该继承一个Flyable接口,比如说Gallopable接口。

这种概念被用于战略模式中。给出的例子实际上向您展示了鸭子是如何继承FlyBehaviourQuackBehaviour的,但仍然可以有鸭子,例如不能飞的RubberDuck。他们本可以让Duck扩展一个Bird类,但后来他们就放弃了一些灵活性,因为每个Duck都能飞,甚至可怜的RubberDuck也能飞。


从技术上讲,一次只能扩展一个类并实现多个接口,但是在实践软件工程时,我更愿意建议一个特定于问题的解决方案,而不是通常可以回答的。顺便说一句,这是一个很好的OO实践,不是扩展具体的类/只是扩展抽象类以防止不必要的继承行为-没有"动物"这样的东西,也没有使用动物对象,只有具体的动物。


在Java 8中,它仍然在2014年2月的开发阶段,您可以使用默认方法来实现一种类似C++的多重继承。您还可以查看本教程,其中显示了一些比正式文档更容易开始使用的示例。


把马关在有半扇门的马厩里是安全的,因为马不能跨过半扇门。因此,我建立了一个马舍服务,接受任何类型的马,并把它放在一个马厩半门。

那么,像马一样的动物,即使是马也能飞吗?

我曾经考虑过很多关于多重继承的问题,但是现在我已经编程超过15年了,我不再关心实现多重继承。

通常,当我试图处理指向多个继承的设计时,我会在稍后发布我错过的理解问题域的设计。

If it looks like a duck and quacks like a duck but it needs
batteries, you probably have the wrong abstraction.


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

有不同的缓解问题的策略。最快实现的是帕维尔提出的复合对象(基本上C++是如何处理的)。我不知道通过C3线性化(或类似的)是否有多重继承性是Java未来的关键,但我对此表示怀疑。

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


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

如果希望能够在Pegasus类中使用horse和bird实现的方法和特性,那么可以将Pegasus实现为bird和horse的组合:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
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类动物,而是只由它们的组件定义。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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);
}

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

1
2
3
4
5
6
7
8
9
10
11
public interface IAnimal {
}

public interface IBird implements IAnimal {
}

public  interface IHorse implements IAnimal {
}

public interface IPegasus implements IBird,IHorse{
}

然后根据需要定义类,通过扩展特定接口:

1
2
3
4
5
6
7
8
public class Bird implements IBird {
}

public class Horse implements IHorse{
}

public class Pegasus implements IPegasus {
}


ehm,您的类只能是另外一个类的子类,但是,您仍然可以实现任意多的接口。

飞马座实际上是一匹马(这是马的一种特殊情况),它能飞(这是这匹特殊马的"技能")。从另一方面来说,你可以说,飞马座是一只可以行走的鸟,有4条腿——这一切都取决于,你如何更容易编写代码。

就像你的情况一样,你可以说:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
   }
}


正如您已经意识到的,Java中的类的多重继承是不可能的,但是接口是可能的。您也可以考虑使用组合设计模式。

几年前我写了一篇非常全面的作文…

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


  • 定义用于定义功能的接口。您可以为多个功能定义多个接口。这些能力可以通过特定的动物或鸟类来实现。
  • 使用继承通过共享非静态和非公共数据/方法在类之间建立关系。
  • 使用decorator_模式动态添加功能。这将允许您减少继承类组合的数量。
  • 请看下面的示例,以便更好地理解

    什么时候使用装饰图案?


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

    如果要将两个类的功能组合成一个使用对象组合。即。

    1
    2
    3
    4
    public class Main {
        private Component1 component1 = new Component1();    
        private Component2 component2 = new Component2();
    }

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

    如果Component1实现接口Interface1Component2实现接口Interface2,可以定义

    1
    class Main implements Interface1, Interface2

    以便在上下文允许的情况下可以互换使用对象。

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


    为了解决Java和RARR中的多继承问题,使用了接口。

    J2EE(核心Java)注释K.V.R Page先生51

    Day - 27

  • Interfaces are basically used to develop user defined data types.
  • With respect to interfaces we can achieve the concept of multiple inheritances.
  • With interfaces we can achieve the concept of polymorphism, dynamic binding and hence we can improve the performance of a JAVA program in
    turns of memory space and execution time.
  • An interface is a construct which contains the collection of purely
    undefined methods or an interface is a collection of purely abstract
    methods.

    [...]

    Day - 28:

    Syntax-1 for reusing the features of interface(s) to class:

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

    In the above syntax clsname represents name of the class which is
    inheriting the features from ‘n’ number of interfaces. ‘Implements’ is
    a keyword which is used to inherit the features of interface(s) to a
    derived class.

    [...]

    Syntax-2 inheriting ‘n’ number of interfaces to another interface:

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

    [...]

    Syntax-3:

    1
    2
    3
    4
    5
    [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会导致编译时错误。所以,无论您有相同的方法还是不同的方法,现在都会有编译时错误。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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?  
        }
    }