繁体   English   中英

Java多重继承

[英]Java Multiple Inheritance

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

假设我有类Animal它有子类BirdHorse ,我需要创建一个从BirdHorse扩展的Pegasus类,因为Pegasus既是鸟又是马。

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

我想知道是否有另一种方法可以解决我仍然可以为鸟和马创建对象的问题。 如果有一种方法也可以创造动物,那会很棒但不是必需的。

您可以为动物类(生物学意义中的类)创建接口,例如用于马的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 ,它没有说明为什么需要接口。 这就是问题的不同之处:使用接口构建继承层次结构,在有用时使用抽象类,在需要时实现具体类并在适当时使用委托。

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

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

这样做的方式是您有一个 Animal 对象。 然后在该对象中添加更多对象,以提供所需的属性和行为。

例如:

  • Bird扩展了Animal工具IFlier
  • 扩展动物工具IHerbivore, IQuadruped
  • Pegasus扩展了Animal工具IHerbivore、IQuadruped、IFlier

现在IFlier看起来像这样:

 interface IFlier {
     Flier getFlier();
 }

所以Bird看起来像这样:

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

现在您拥有继承的所有优势。 您可以重复使用代码。 您可以拥有一组 IFlier,并且可以使用多态性等的所有其他优势。

但是,您也拥有 Composition 的所有灵活性。 您可以将尽可能多的不同接口和复合支持类应用于每种类型的Animal - 对每个位的设置方式进行尽可能多的控制。

策略模式替代组合方法

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

我有一个愚蠢的想法:

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-typing的概念吗?

很可能您倾向于让 Pegasus 扩展 Bird 和 Horse 接口,但鸭子类型实际上表明您应该继承行为 正如评论中已经指出的那样,飞马不是鸟,但它可以飞。 所以你的 Pegasus 应该继承一个Flyable ,让我们说一个Gallopable

策略模式中使用了这种概念。 给定的示例实际上向您展示了鸭子如何继承FlyBehaviourQuackBehaviour并且仍然可以存在不能飞的鸭子,例如RubberDuck 他们也可以让Duck扩展一个Bird级,但随后他们会放弃一些灵活性,因为每只Duck都可以飞,即使是可怜的RubberDuck

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

在 Java 8 及更高版本中,您可以使用默认方法来实现一种类似于 C++ 的多重继承。 您还可以查看本教程,其中显示了一些示例,这些示例应该比官方文档更容易上手。

将马放在有半扇门的马厩中是安全的,因为马不能越过半扇门。 因此,我设置了一个马房服务,可以接受任何类型的马并将其放入带有半门的马厩中。

那么像马一样的动物是不是连马都可以飞呢?

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

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

或者

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

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

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

如果你的问题是学术性的,那么正确的答案是鸟和马更具体,假设飞马只是鸟和马的组合是错误的。 更准确地说,飞马座与鸟类和马匹具有某些共同的内在属性(即它们可能有共同的祖先)。 正如 Moritz 的回答所指出的那样,这可以充分建模。

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

如果您希望能够在 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);
}

嗯,您的类只能是其他 1 个的子类,但是,您仍然可以根据需要实现任意数量的接口。

飞马其实就是一匹马(是马的特例),会飞(这就是这匹特马的“本领”)。 另一方面,您可以说 Pegasus 是一只可以走路的鸟,并且是 4 条腿的——这完全取决于您如何更轻松地编写代码。

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

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 {
}

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

如果您想将两个类的功能合二为一 - 使用对象组合。 IE

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

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

这里接口可能会派上用场 - 如果Component1实现接口Interface1并且Component2实现Interface2 ,您可以定义

class Main implements Interface1, Interface2

这样您就可以在上下文允许的情况下互换使用对象。

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

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

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

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

  1. 定义用于定义功能的接口 您可以为多个功能定义多个接口。 这些功能可以由特定的AnimalBird 实现
  2. 使用继承通过共享非静态和非公共数据/方法来建立类之间的关系。
  3. 使用Decorator_pattern动态添加功能。 这将允许您减少继承类和组合的数量。

看一下下面的例子以更好地理解

什么时候使用装饰者模式?

解决Java中的多继承问题→接口使用

J2EE(核心JAVA)KVR先生的笔记 第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”个接口继承特性的类的名称。 'Implements' 是一个关键字,用于将接口的特性继承到派生类。

[...]

语法 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?  
    }
} 

问题没有解决。 为了充分建模并防止代码复制,您需要多重继承或混合。 具有默认功能的接口是不够的,因为您不能在接口中保留成员。 接口建模导致子类或静态中的代码复制,这都是邪恶的。

您所能做的就是使用自定义构造并将其拆分为更多组件并将其组合在一起......

玩具语言

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM