Java多重继承

She*_*eli 165 java oop multiple-inheritance diamond-problem multiple-interface-implem

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

可以说我有类Animal此有子类BirdHorse我需要做一个类Pegasus,从扩展BirdHorsePegasus既是一只鸟和一匹马.

我认为这是经典的钻石问题.从我能理解经典的方式来解决,这是使Animal,BirdHorse类接口,并实现Pegasus从他们.

我想知道是否有另一种方法来解决我仍然可以为鸟类和马创造物体的问题.如果有一种方法可以创造动物,那将是伟大的但不是必要的.

Mor*_*sen 115

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

然后你仍然可以创建一个

public class Bird implements Avialae {
}
Run Code Online (Sandbox Code Playgroud)

public class Horse implements Equidae {}
Run Code Online (Sandbox Code Playgroud)

并且

public class Pegasus implements Avialae, Equidae {}
Run Code Online (Sandbox Code Playgroud)

从评论中添加:

为了减少重复代码,您可以创建一个抽象类,其中包含您要实现的动物的大多数常用代码.

public abstract class AbstractHorse implements Equidae {}

public class Horse extends AbstractHorse {}

public class Pegasus extends AbstractHorse implements Avialae {}
Run Code Online (Sandbox Code Playgroud)

更新

我想补充一点细节.正如布莱恩所言,这是OP已经知道的事情.

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

  • 哪个......正是OP所说他们知道你可以在Q中做什么. (9认同)
  • 我不确定,我还没见过Pegasus.但是,在这种情况下,我更喜欢使用"AbstractHorse",它也可以用来制造斑马或其他类似马的动物. (8认同)
  • @MoritzPetersen如果你真的想重新使用抽象并给出有意义的名字,那么`AbstractEquidae`可能比`AbstractHorse`更合适.让斑马延伸抽象马会很奇怪.顺便说一句,答案很好. (5认同)
  • 由于Pegasus已经是马(苍蝇),我认为如果扩展Horse并实施Avialae,你可以重复使用更多代码. (4认同)
  • 实现鸭子打字也会涉及一个实现'Avialae`的'Duck`类? (3认同)
  • 我还会仔细研究在这种情况下使用“组合继承”。有些人建议你让这个类继承“Horse”,这样你就可以重用它的一些代码,但我认为这会导致你走下坡路。为什么延长马而不是鸟?当你有另一种像飞马一样的动物时会发生什么……然后还有另一种类似的动物。您突然拥有一长串通过继承紧密耦合的具体类。通过组合继承,您可以坚持实现多个接口,但不能扩展/耦合这些具体类! (2认同)

Tim*_*m B 87

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

  • 第一个是继承.正如您已经确定继承的局限性意味着您无法在此处执行所需操作.
  • 第二个是组合.由于继承失败,您需要使用组合.

这种方式的工作方式是你有一个Animal对象.然后在该对象中添加进一步的对象,这些对象提供您需要的属性和行为.

例如:

  • Bird扩展Animal实现IFlier
  • 延伸动物实施IHerbivore,IQuadruped
  • Pegasus延伸动物实施IHerbivore,IQuadruped,IFlier

现在IFlier看起来像这样:

 interface IFlier {
     Flier getFlier();
 }
Run Code Online (Sandbox Code Playgroud)

所以Bird看起来像这样:

 class Bird extends Animal implements IFlier {
      Flier flier = new Flier();
      public Flier getFlier() { return flier; }
 }
Run Code Online (Sandbox Code Playgroud)

现在你拥有继承的所有优点.您可以重复使用代码.您可以拥有一组IFliers,并可以使用多态性等所有其他优点.

但是,您也拥有Composition的所有灵活性.您可以根据需要为每种类型应用尽可能多的不同接口和复合支持类Animal- 尽可能多地控制每个位的设置.

策略模式替代组合方法

根据您正在做什么以及如何做的另一种方法是让Animal基类包含一个内部集合来保存不同行为的列表.在这种情况下,您最终会使用更接近战略模式的东西.这确实在简化代码方面具有优势(例如Horse,不需要知道任何关于Quadruped或的东西Herbivore),但如果你不进行接口方法,则会失去多态性等许多优点.


Pav*_*cek 41

我有一个愚蠢的想法:

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();
  }
}
Run Code Online (Sandbox Code Playgroud)

  • 它不是那么愚蠢,但确实打破了语义. (23认同)
  • 它会起作用,但我不喜欢那种方法(Wrappper),因为那时似乎Pegasus是一匹马,而不是它是一匹马. (22认同)
  • 这几乎就像Tim B的答案中未完成的构图方法. (3认同)
  • @Pablo不,飞马有马特征和有鸟类特征.答案为+1,因为它保持代码简单,不像clunkier,类产生,适当的Java解决方案. (2认同)

snr*_*rlx 25

我可以建议鸭子打字的概念吗?

很可能你倾向于让Pegasus延伸一个Bird and a Horse界面,但鸭子打字实际上暗示你应该宁愿继承行为.正如评论中已经说明的那样,飞马座不是一只鸟,但它可以飞翔.所以你的Pegasus应该继承一个接口Flyable,让我们说一个接口Gallopable.

战略模式中使用了这种概念.在给定的例子其实显示了一只鸭子如何继承FlyBehaviourQuackBehaviour,仍然可以有鸭,例如RubberDuck,它不能飞.他们本可以使Duck延伸成为一级,Bird但是他们会放弃一些灵活性,因为每个人Duck都可以飞,甚至是穷人RubberDuck.


Smu*_*tje 18

从技术上讲,你一次只能扩展一个类并实现多个接口,但是当涉及到软件工程时,我宁愿建议一个特定于问题的解决方案,而不是一般的问题.顺便说一下,这是一个很好的OO实践,扩展具体类/只扩展抽象类来防止不必要的继承行为 - 没有"动物"之类的东西,也没有使用动物对象而只使用混凝土动物.


GOT*_*O 0 13

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


Ian*_*ose 12

将马放在有半门的马厩里是安全的,因为马不能越过半门.因此,我设置了马匹服务,接受任何类型的马类,并把它放在一个半门的马厩里.

那种像马一样的动物甚至可以飞马吗?

我过去常常考虑多重继承,但是现在我已经编程了超过15年,我不再关心实现多重继承了.

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

要么

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


Mik*_*kke 8

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

有不同的策略可以缓解这个问题.最容易实现的是Pavel建议的Composite对象(实质上是C++如何处理它).我不知道通过C3线性化(或类似)的多重继承是否适用于Java的未来,但我对此表示怀疑.

如果你的问题是学术性的,那么正确的解决方案是鸟和马更具体,假设飞马只是一只鸟和马的组合是错误的.说Pegasus具有与鸟类和马匹相同的某些内在特性(即它们可能具有共同的祖先)会更为正确.Moritz的答案指出,这可以充分模仿.


Bal*_*der 6

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

如果您希望能够在Pegasus课程中使用您的马和鸟实施的方法和功能,那么您可以将Pegasus实施为鸟和马的组合:

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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

另一种可能性是使用实体 - 组件 - 系统方法而不是继承来定义您的动物.当然,这意味着您不会拥有动物的单独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);
}
Run Code Online (Sandbox Code Playgroud)