Héritage multiple Java

168

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

Disons que j'ai une classe, Animalcela a des sous-classes Birdet Horseque j'ai besoin de faire une classe Pegasusqui s'étend de Birdet Horsedepuis Pegasusest à 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 façon classique c'est de résoudre pour rendre le Animal, Birdet les Horseinterfaces des classes et la mise en œuvre Pegasusde leur part .

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

Sheli
la source
6
Je pense que vous pouvez créer manuellement les classes et les stocker en tant que membres (composition au lieu d'héritage). Avec la classe Proxy ( docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html ), cela peut être une option, bien que vous ayez également besoin des interfaces.
Gábor Bakos
4
@RAM alors il ne faut pas étendre Bird plutôt avoir un comportement qui lui permettra de prendre son envol. : D problème résolu
Yogesh
11
Exactement. Que diriez-vous d'une interface CanFly. :-)
Surprised Coconut
28
Je pense que c'est la mauvaise approche. Vous avez des animaux - des chevaux, des oiseaux. Et vous avez des propriétés - Voler, Carnivore. Un Pegasus n'est pas un HorseBird, c'est un cheval qui peut voler. Les propriétés devraient dans les interfaces. Alors public class Pegasus extends Horse implements Flying.
Boris the Spider
12
Je comprends pourquoi vous pensez que c'est faux et ne respecte pas les règles de la biologie et appréciez votre inquiétude, mais en ce qui concerne le programme que je dois construire qui concerne en fait les banques, c'était la meilleure approche pour moi. Comme je ne voulais pas publier mes problèmes réels car cela serait contraire aux règles, j'ai un peu changé l'exemple. Merci bien ...
Sheli

Réponses:

115

Vous pouvez créer des interfaces pour les classes d'animaux (classe au sens biologique), comme public interface Equidaepour les chevaux et public interface Avialaepour les oiseaux (je ne suis pas biologiste, donc les termes peuvent être faux).

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

Mettre à jour

Je voudrais ajouter un autre détail. Comme Brian le fait remarquer , 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 (comme Bird) mais plutôt un comportement (d'autres se réfèrent à le typage du canard, ce qui est bon aussi, mais je veux dire juste: la classe biologique des oiseaux, Avialae). Je ne recommande pas non plus d'utiliser des noms d'interface commençant par un «I» majuscule, comme IBird, qui ne dit simplement 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 la délégation si nécessaire.

Moritz Petersen
la source
9
Ce qui ... est exactement ce que l'OP dit qu'ils savent que vous pouvez faire dans le Q.
Brian Roach
4
Comme Pegasus EST déjà un cheval (qui vole), je pense que vous pourriez réutiliser plus de code si étend Horse et implémente Avialae.
Pablo Lozano
8
Je ne suis pas sûr, je n'ai pas encore vu de Pegasus. Cependant, dans ce cas, je préférerais utiliser un AbstractHorse, qui peut également être utilisé pour construire des zèbres ou d'autres animaux ressemblant à des chevaux.
Moritz Petersen
5
@MoritzPetersen Si vous voulez vraiment réutiliser des abstractions et donner des noms significatifs, ce AbstractEquidaeserait probablement plus approprié que AbstractHorse. Il serait étrange qu'un zèbre étende un cheval abstrait. Bonne réponse, au fait.
afsantos
3
La mise en œuvre du typage canard impliquerait également une Duckimplémentation de classe Avialae?
mgarciaisaia
88

Il existe deux approches fondamentales pour combiner des objets entre eux:

  • Le premier est l' héritage . Comme vous l'avez déjà identifié, les limites de l'héritage signifient que vous ne pouvez pas faire ce dont vous avez besoin ici.
  • Le second est la composition . Puisque 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 donnent les propriétés et les comportements dont vous avez besoin.

Par exemple:

  • Bird étend les outils animaux IFlier
  • Cheval étend les outils animaux IHerbivore, IQuadruped
  • Pegasus étend les outils animaux IHerbivore, IQuadruped, IFlier

Maintenant IFlierressemble à ceci:

 interface IFlier {
     Flier getFlier();
 }

Alors Birdressemble à ceci:

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

Vous bénéficiez désormais de tous les avantages de l'héritage. Vous pouvez réutiliser le code. Vous pouvez avoir une collection d'IFliers, et pouvez utiliser tous les autres avantages du polymorphisme, etc.

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

Modèle de stratégie approche alternative à la composition

Une approche alternative en fonction de ce que vous faites et de la manière dont vous faites est de faire en sorte que la Animalclasse de base contienne une collection interne pour conserver la liste des différents comportements. Dans ce cas, vous finissez par utiliser quelque chose de plus proche du modèle de stratégie. Cela présente des avantages en termes de simplification du code (par exemple, Horsen'a pas besoin de savoir quoi que ce soit sur Quadrupedou Herbivore) mais si vous ne faites pas également l'approche d'interface, vous perdez beaucoup des avantages du polymorphisme, etc.

Tim B
la source
Une alternative similaire pourrait également être l'utilisation de classes de types, bien que celles-ci ne soient pas si naturelles à utiliser en Java (vous devez utiliser des méthodes de conversion et ainsi de suite), cette introduction pourrait être utile pour avoir l'idée: typeclassopedia.bitbucket.org
Gábor Bakos
1
C'est une bien meilleure solution que l'approche recommandée dans la réponse acceptée. Par exemple, si je veux ajouter plus tard "la couleur du bec" à l'interface Bird, j'ai un problème. Pegasus est un composite, prenant des éléments de chevaux et d'oiseaux mais ni entièrement de cheval ni d'oiseau. L'utilisation de la composition est parfaitement logique dans ce scénario.
JDB se souvient encore de Monica le
Mais getFlier()doit être réimplémenté pour chaque type d'oiseau.
SMUsamaShah
1
@ LifeH2O Divisez-les en blocs de fonctionnalités partagées, puis donnez-leur cela. à- dire que vous pourriez avoir MathsTeacheret à la EnglishTeacherfois héritant Teacher, ChemicalEngineer, MaterialsEngineeretc Hériter Engineer. Teacheret les Engineerdeux implémentent Component. Le Personpuis a juste une liste de Components, et vous pouvez leur donner les bons Componentpour cela Person. à- dire person.getComponent(Teacher.class), person.getComponent(MathsTeacher.class)etc.
Tim B
1
C'est la meilleure réponse. En règle générale, l'héritage représente «est» et une interface représente «peut». Un Pegasus EST un animal qui peut voler et marcher. Un oiseau est un animal qui peut voler. Un cheval est un animal qui peut marcher.
Robear
43

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();
  }
}
Pavel Janicek
la source
24
Cela fonctionnera, mais je n'aime pas ce genre d'approche (Wrappper) parce qu'alors il semble que Pegasus A un cheval, à la place c'est un cheval.
Pablo Lozano
Je vous remercie. Je ne pouvais pas m'empêcher de publier l'idée. Je sais un peu que c'est stupide, mais je n'ai pas vu POURQUOI c'est stupide ...
Pavel Janicek
3
C'est presque comme une version non raffinée de l'approche de composition de la réponse de Tim B.
Stephan
1
C'est plus ou moins la manière acceptée de le faire, bien que vous devriez aussi avoir quelque chose comme IJumps avec une méthode "jump" qui est implémentée par Horse et Pegasus et IFlies avec une méthode "fly" qui est implémentée par Bird et Pegasus.
MatsT
2
@Pablo no, un Pegasus HAS horseFeatures et HAS birdFeatures. +1 pour la réponse, car il garde le code simple, contrairement aux solutions Java plus maladroites, génératrices de classes et appropriées.
JaneGoodall
25

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

Très probablement, vous auriez tendance à faire étendre le Pegasus à une interface Oiseau et Cheval, mais le typage du canard 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 Flyableinterface et disons unGallopable -interface.

Ce type de concept est utilisé dans le modèle de stratégie . L'exemple donné vous montre en fait comment un canard hérite du FlyBehaviouret QuackBehaviouret il peut toujours y avoir des canards, par exemple le RubberDuck, qui ne peuvent pas voler. Ils auraient également pu faire de l' Duckextension une Birdclasse, mais ils auraient alors renoncé à une certaine flexibilité, car tout le monde Duckserait capable de voler, même les pauvres RubberDuck.

snrlx
la source
19

Techniquement parlant, vous ne pouvez étendre qu'une classe à la fois et implémenter plusieurs interfaces, mais lorsque vous mettez la main sur l'ingénierie logicielle, je préfère suggérer une solution spécifique à un problème qui ne peut généralement pas répondre. Soit dit en passant, c'est une bonne pratique OO, de ne pas étendre les classes concrètes / seulement d'étendre les classes abstraites pour empêcher un comportement d'héritage indésirable - il n'y a pas de "animal" et pas d'utilisation d'un objet animal mais seulement des animaux concrets.

Smutje
la source
13

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

GOTO 0
la source
1
Sachez cependant que si votre oiseau et votre cheval ont tous les deux une méthode par défaut, vous rencontrerez toujours le problème du diamant et devrez l'implémenter séparément dans votre classe Pegasus (ou obtenir une erreur de compilation).
Mikkel Løkke
@Mikkel Løkke: La classe Pegasus doit définir des remplacements pour les méthodes ambiguës mais peut les implémenter en déléguant simplement à l'une ou l'autre des super méthodes (ou aux deux dans un ordre choisi).
Holger
12

Il est prudent de garder un cheval dans une écurie avec une demi-porte, car un cheval ne peut pas franchir une demi-porte. C'est pourquoi j'ai mis en place un service d'hébergement pour chevaux qui accepte tout article de type cheval et le met dans une écurie avec une demi-porte.

Alors, est-ce qu'un cheval comme un animal qui peut voler est 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 d'implémenter l'héritage multiple.

Plus souvent qu'autrement, quand j'ai essayé de faire face à une conception qui pointait vers l'héritage multiple, je suis venu plus tard pour publier que j'avais mal compris le domaine du problème.

OU

Si cela ressemble à un canard et charlatan comme un canard mais qu'il a besoin de piles, vous avez probablement la mauvaise abstraction .

Ian Ringrose
la source
Si je lis correctement votre analogie, vous dites qu'au début, les interfaces semblent géniales, car elles vous permettent de contourner les problèmes de conception, par exemple, vous pouvez utiliser l'API de quelqu'un d'autre en forçant vos classes à travers leurs interfaces. Mais après quelques années, vous vous êtes rendu compte que le problème était au départ une mauvaise conception.
CS
8

Java n'a pas de problème d'héritage multiple, car il n'a pas d'héritage multiple. C'est par conception, afin de résoudre le vrai problème d'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 étant l'objet Composite suggéré par Pavel (essentiellement la manière dont C ++ le gère). Je ne sais pas si l'héritage multiple via la linéarisation C3 (ou similaire) est sur les cartes pour l'avenir de Java, mais j'en doute.

Si votre question est théorique, alors la bonne solution est que l'oiseau et le cheval 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 en commun avec les oiseaux et les chevaux (c'est-à-dire qu'ils ont peut-être des ancêtres communs). Cela peut être suffisamment modélisé comme le souligne la réponse de Moritz.

Mikkel Løkke
la source
6

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 souhaitez pouvoir utiliser les méthodes et les fonctionnalités de vos implémentations Horse et 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é est d'utiliser un système Entité-Composant approche au lieu de l'héritage pour définir vos animaux. Bien sûr, cela signifie que vous n'aurez pas de classes Java individuelles des animaux, mais au lieu de cela, elles ne sont 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);
}
Balder
la source
4

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 {
}
Richardtz
la source
1
Ou il peut simplement: La classe publique Pegasus étend les outils animaux Cheval, oiseau
Batman
OP est déjà au courant de cette solution, il cherche une autre façon de le faire
Yogesh
@Batman, bien sûr, il peut mais s'il veut étendre la hiérarchie, il devra suivre cette approche
richardtz
IBirdet IHorsedevrait mettre en œuvre à la IAnimalplace deAnimal
oliholz
@Yogesh, vous avez raison. J'ai négligé l'endroit où il le déclare. En tant que "débutant", que dois-je faire maintenant, supprimer la réponse ou la laisser là?, Merci.
richardtz
4

Ehm, votre classe ne peut être la sous-classe que pour 1 autre, mais vous pouvez quand même avoir autant d'interfaces implémentées que vous le souhaitez.

Un Pegasus est en fait un cheval (c'est un cas particulier de 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 Pegasus est un oiseau, qui peut marcher et qu'il a 4 pattes - tout dépend, comment il est plus facile pour vous d'écrire 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
   }
}
András Iványi
la source
3

Les interfaces ne simulent pas l'héritage multiple. Les créateurs de Java considéraient que l'héritage multiple était faux, il n'y a donc rien de tel en Java.

Si vous souhaitez combiner les fonctionnalités de deux classes en une seule, utilisez la composition d'objets. 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 Component1implémente l'interface Interface1et Component2implémente Interface2, vous pouvez définir

class Main implements Interface1, Interface2

Pour que vous puissiez utiliser des objets de manière interchangeable là où le contexte le permet.

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

Karthik Surianarayanan
la source
Ce n'est pas faux de la même manière que les pointeurs de mémoire directs, les types non signés et la surcharge d'opérateurs ne sont pas faux; ce n'est tout simplement pas nécessaire pour faire le travail. Java a été conçu comme un langage allégé facile à comprendre. N'inventez pas de trucs et espérez le mieux, c'est un domaine de connaissance, pas de suppositions.
Gimby
3
  1. Définissez les interfaces pour définir les capacités. Vous pouvez définir plusieurs interfaces pour plusieurs capacités. Ces capacités peuvent être mises en œuvre par un animal ou un oiseau spécifique .
  2. Utiliser l' 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 capacités de manière dynamique. Cela vous permettra de réduire le nombre de classes et de combinaisons d'héritage.

Jetez un œil à l'exemple ci-dessous pour une meilleure compréhension

Quand utiliser le motif décorateur?

Ravindra babu
la source
2

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

Considérons 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 à partir d'un objet de classe enfant, il y aura ambiguïté pour appeler la méthode de la classe A ou B.

Étant 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 maintenant une erreur de compilation.

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?  
    }
} 
Islam syeful
la source
2

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

J2EE (core JAVA) Notes de M. KVR 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 atteindre le concept d'héritages multiples.
  3. Avec les interfaces, nous pouvons atteindre le concept de polymorphisme, de liaison dynamique et par conséquent, nous pouvons 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 indéfinies ou une interface est une collection de méthodes purement abstraites.

[...]

Jour - 28:

Syntaxe-1 pour réutiliser les 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) à 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;
};
Impressionnant
la source