web-dev-qa-db-fra.com

Java Héritage multiple

Pour tenter de comprendre parfaitement comment résoudre les problèmes d'héritage multiples de Java, j'ai une question classique à clarifier.

Disons que j'ai la classe Animal cela a des sous-classes Bird et Horse et j'ai besoin de faire une classe Pegasus qui s'étend de Bird et Horse depuis Pegasus est à la fois un oiseau et un cheval.

Je pense que c'est le problème classique du diamant. D'après ce que je peux comprendre, la solution classique consiste à rendre les interfaces de classes Animal, Bird et Horse et à implémenter Pegasus à partir d'elles.

Je me demandais s'il y avait une autre façon de résoudre le problème dans lequel je peux toujours créer des objets pour oiseaux et chevaux. S'il existait un moyen de créer des animaux, ce serait formidable mais pas nécessaire.

165
Sheli

Vous pouvez créer des interfaces pour les classes d'animaux (classe au sens biologique), telles que public interface Equidae pour les chevaux et public interface Avialae pour les oiseaux (je ne suis pas un biologiste, les termes peuvent donc être erronés).

Ensuite, vous pouvez toujours créer un

public class Bird implements Avialae {
}

et

public class Horse implements Equidae {}

et aussi

public class Pegasus implements Avialae, Equidae {}

Ajout des commentaires:

Afin de réduire le code en double, vous pouvez créer une classe abstraite contenant la plupart du code commun des animaux que vous souhaitez implémenter.

public abstract class AbstractHorse implements Equidae {}

public class Horse extends AbstractHorse {}

public class Pegasus extends AbstractHorse implements Avialae {}

Mise à jour

J'aimerais ajouter un détail de plus. Comme remarque Brian , c’est quelque chose que le PO savait déjà.

Cependant, je tiens à souligner que je suggère de contourner le problème de "multi-héritage" avec les interfaces et que je ne recommande pas d'utiliser des interfaces qui représentent déjà un type concret (tel que Bird), mais davantage un comportement (autres). typage de canard, ce qui est bien aussi, mais je veux dire simplement: la classe biologique des oiseaux, Avialae). Je ne recommande pas non plus d'utiliser des noms d'interface commençant par un "I" majuscule, tel que IBird, qui ne dit rien sur la raison pour laquelle vous avez besoin d'une interface. C'est la différence avec la question: construire la hiérarchie d'héritage à l'aide d'interfaces, utiliser des classes abstraites lorsque cela est utile, implémenter des classes concrètes si nécessaire et utiliser une délégation si nécessaire.

115
Moritz Petersen

Il existe deux approches fondamentales pour combiner des objets:

  • Le premier est héritage. Comme vous avez déjà identifié les limites de l'héritage, vous ne pouvez pas faire ce dont vous avez besoin ici.
  • La seconde est Composition. Comme l'héritage a échoué, vous devez utiliser la composition.

La façon dont cela fonctionne est que vous avez un objet Animal. Dans cet objet, vous ajoutez ensuite d'autres objets qui fournissent les propriétés et les comportements requis.

Par exemple:

  • Oisea étend Animal implémente IFlier
  • Cheval étend Animal implémente IHerbivore, IQuadruped
  • Pegasus étend Animal implémente IHerbivore, IQuadruped, IFlier

Maintenant, IFlier ressemble à ceci:

 interface IFlier {
     Flier getFlier();
 }

Donc, Bird ressemble à ceci:

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

Maintenant, vous avez tous les avantages de l'héritage. Vous pouvez réutiliser le code. Vous pouvez avoir une collection d'IFliers et utiliser tous les autres avantages du polymorphisme, etc.

Cependant, vous avez également toute la flexibilité de Composition. Vous pouvez appliquer autant d'interfaces et de classes de support composites différentes que vous le souhaitez à chaque type de Animal - avec autant de contrôle que nécessaire sur la configuration de chaque bit.

Stratégie de stratégie alternative à la composition

Une approche alternative, selon ce que vous faites et comment vous le faites, consiste à faire en sorte que la classe de base Animal contienne une collection interne pour conserver la liste des différents comportements. Dans ce cas, vous utilisez quelque chose de plus proche du modèle de stratégie. Cela donne des avantages en termes de simplification du code (par exemple, Horse n'a pas besoin de savoir quoi que ce soit sur Quadruped ou Herbivore) mais si vous ne faites pas aussi l'interface avec vous perdre beaucoup des avantages du polymorphisme, etc.

87
Tim B

J'ai une idée stupide:

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();
  }
}
41
Pavel Janicek

Puis-je suggérer le concept de Duck-typing ?

Il est fort probable que vous auriez tendance à faire en sorte que le Pegasus étende les interfaces Oiseau et Cheval, mais la frappe au moyen de canards suggère en fait que vous devriez plutôt hériter du comportement . Comme déjà indiqué dans les commentaires, un pégase n’est pas un oiseau mais il peut voler. Donc, votre Pegasus devrait plutôt hériter d'une interface Flyable- et disons d'une interface Gallopable-.

Ce type de concept est utilisé dans le modèle de stratégie . L’exemple donné montre en fait comment un canard hérite des FlyBehaviour et QuackBehaviour et qu’il peut toujours y avoir des canards, par exemple. le RubberDuck, qui ne peut pas voler. Ils auraient également pu faire en sorte que la Duck étende la classe Bird- mais ils auraient alors renoncé à une certaine flexibilité, car chaque Duck serait capable de voler, même les pauvres RubberDuck.

25
snrlx

Techniquement parlant, vous ne pouvez étendre qu'une classe à la fois et implémenter de multiples interfaces, mais lorsque je travaille en ingénierie logicielle, je suggère plutôt une solution spécifique au problème, qui ne soit généralement pas redevable. En passant, il est bon OO pratique, pas d'étendre des classes concrètes/d'étendre seulement des classes abstraites pour empêcher tout comportement indésirable en matière d'héritage - il n'y a rien de "animal" et pas d'utilisation d'un objet animal mais seulement des animaux concrets.

18
Smutje

Dans Java 8, qui est toujours en phase de développement en février 2014, vous pouvez utiliser méthodes par défaut pour obtenir une sorte d'héritage multiple de type C++. Vous pouvez également consulter ce didacticiel qui présente quelques exemples avec lesquels il devrait être plus facile de commencer à travailler que la documentation officielle.

13
GOTO 0

Il est prudent de garder un cheval dans une écurie avec une demi-porte, car un cheval ne peut pas dépasser une demi-porte. Par conséquent, j’ai mis en place un service d’hébergement pour chevaux qui accepte n’importe quel article de type cheval et le range dans une écurie avec une demi-porte.

Un cheval est-il comme un animal capable de voler même un cheval?

J'avais l'habitude de beaucoup penser à l'héritage multiple, mais maintenant que je programme depuis plus de 15 ans, je ne me soucie plus de la mise en œuvre d'un héritage multiple.

Plus souvent qu'autrement, lorsque j'ai essayé de gérer un projet qui mettait l'accent sur l'héritage multiple, je suis venu plus tard pour dire que je n'avais pas compris le domaine du problème.

OR

Si cela ressemble à un canard et que les charlatans ressemblent à un canard mais qu'il a besoin de piles, vous avez probablement la mauvaise abstraction .

12
Ian Ringrose

Java n'a pas de problème d'héritage multiple, car il n'a pas d'héritage multiple. Ceci est voulu, afin de résoudre le véritable problème de l’héritage multiple (le problème du diamant).

Il existe différentes stratégies pour atténuer le problème. Le plus immédiatement réalisable est l’objet Composite suggéré par Pavel (essentiellement comment C++ le traite). Je ne sais pas si l'héritage multiple via la linéarisation C3 (ou similaire) est prévu pour l'avenir de Java, mais j'en doute.

Si votre question est académique, alors la solution correcte est que Bird and Horse sont plus concrets et il est faux de supposer qu'un Pegasus est simplement un oiseau et un cheval combinés. Il serait plus correct de dire qu'un Pegasus a certaines propriétés intrinsèques communes avec Birds and Horses (c'est-à-dire qu'ils ont peut-être des ancêtres communs). Ceci peut être suffisamment modélisé comme le souligne la réponse de Moritz.

8
Mikkel Løkke

Je pense que cela dépend beaucoup de vos besoins et de la manière dont vos classes d'animaux doivent être utilisées dans votre code.

Si vous voulez pouvoir utiliser les méthodes et les fonctionnalités de vos implémentations Horse and Bird dans votre classe Pegasus, vous pouvez implémenter Pegasus en tant que composition d'un oiseau et d'un cheval:

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

Une autre possibilité consiste à utiliser une approche Entity-Component-System au lieu d'un héritage pour définir vos animaux. Bien entendu, cela signifie que vous n'aurez pas de classes Java individuelles des animaux, mais qu'elles ne seront définies que par leurs composants.

Un pseudo-code pour une approche Entity-Component-System pourrait ressembler à ceci:

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);
}
6
Balder

Ehm, votre classe peut être la sous-classe pour seulement 1 autre, mais vous pouvez toujours avoir autant d’interfaces implémentées que vous le souhaitez.

Un pégase est en fait un cheval (c'est un cas particulier d'un cheval), qui est capable de voler (ce qui est la "compétence" de ce cheval spécial). D’un autre côté, vous pouvez dire que le Pégase est un oiseau qui peut marcher et qui a quatre pattes - tout dépend de la facilité avec laquelle vous écrivez le code.

Comme dans votre cas, vous pouvez dire:

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
   }
}
4
András Iványi

vous pouvez avoir une hiérarchie d'interface, puis étendre vos classes à partir des interfaces sélectionnées:

public interface IAnimal {
}

public interface IBird implements IAnimal {
}

public  interface IHorse implements IAnimal {
}

public interface IPegasus implements IBird,IHorse{
}

puis définissez vos classes selon vos besoins, en étendant une interface spécifique:

public class Bird implements IBird {
}

public class Horse implements IHorse{
}

public class Pegasus implements IPegasus {
}
4
richardtz
  1. Définissez interfaces pour définir les capacités. Vous pouvez définir plusieurs interfaces pour plusieurs fonctionnalités. Ces capacités peuvent être implémentées par Animal ou Bird.
  2. Utilisez héritage pour établir des relations entre les classes en partageant des données/méthodes non statiques et non publiques.
  3. Utilisez Decorator_pattern pour ajouter des fonctionnalités de manière dynamique. Cela vous permettra de réduire le nombre de classes et de combinaisons d'héritage.

Regardez l'exemple ci-dessous pour une meilleure compréhension

Quand utiliser le motif de décorateur?

3
Ravindra babu

Les interfaces ne simulent pas plusieurs héritages. Java les créateurs ont considéré que l'héritage multiple était incorrect, il n'y a donc rien de tel en Java.

Si vous souhaitez combiner les fonctionnalités de deux classes en une composition d’objets à usage unique. C'est à dire.

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

Et si vous souhaitez exposer certaines méthodes, définissez-les et laissez-les déléguer l'appel au contrôleur correspondant.

Ici, les interfaces peuvent être utiles - si Component1 implémente l'interface Interface1 et Component2 implémente Interface2, vous pouvez définir

class Main implements Interface1, Interface2

Pour que vous puissiez utiliser des objets de façon interchangeable lorsque le contexte le permet.

Donc, à mon point de vue, vous ne pouvez pas entrer dans le problème des diamants.

3

Comme vous le savez déjà, l'héritage multiple de classes dans Java n'est pas possible, mais c'est possible avec les interfaces. Vous pouvez également envisager d'utiliser le modèle de conception de composition.

J'ai écrit un article très complet sur la composition il y a quelques années ...

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

3
series0ne

Pour résoudre le problème de l'héritage multiple dans Java → l'interface est utilisée

J2EE (noyau Java) Notes Par Mr. K.V.R Page 51

Jour - 27

  1. Les interfaces sont essentiellement utilisées pour développer des types de données définis par l'utilisateur.
  2. En ce qui concerne les interfaces, nous pouvons réaliser le concept d'héritages multiples.
  3. Avec les interfaces, nous pouvons atteindre le concept de polymorphisme, de liaison dynamique et ainsi améliorer les performances d’un programme Java en termes d’espace mémoire et de temps d’exécution.

Une interface est une construction qui contient la collection de méthodes purement non définies ou une interface est une collection de méthodes purement abstraites.

[...]

Jour - 28:

Syntaxe-1 pour la réutilisation des fonctionnalités d'interface (s) à la classe:

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

Dans la syntaxe ci-dessus, clsname représente le nom de la classe qui hérite des fonctionnalités du nombre n d’interfaces. ‘Implements’ est un mot-clé utilisé pour hériter des fonctionnalités d’interface (s) vers une classe dérivée.

[...]

Syntaxe-2 héritant du nombre n d’interfaces vers une autre interface:

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

[...]

Syntaxe 3:

[abstract] class <derived class name> extends <base class name> implements <intf 1>,<intf 2>.........<intf n>
{
  variable declaration;
  method definition or declaration;
};
2
Awesome

Pour réduire la complexité et simplifier le langage, l'héritage multiple n'est pas pris en charge en Java.

Prenons un scénario où A, B et C sont trois classes. La classe C hérite des classes A et B. Si les classes A et B ont la même méthode et que vous l'appelez depuis un objet de classe enfant, il sera ambigu d’appeler la méthode des classes A ou B.

Etant donné que les erreurs de compilation sont meilleures que les erreurs d’exécution, Java génère une erreur de compilation si vous héritez de 2 classes. Donc, que vous ayez la même méthode ou une méthode différente, il y aura une erreur de compilation maintenant.

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?  
    }
} 
2
Syeful Islam