171 votes

Héritage multiple en Java

Dans une tentative de comprendre pleinement comment résoudre les problèmes d'héritage multiple de Java, j'ai une question classique qui doit être clarifiée.

Disons que j'ai une classe Animal Ceci a des sous classes Bird y Horse et je dois créer une classe Pegasus qui s'étend de Bird y 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 façon classique de résoudre ce problème est de faire le Animal , Bird y Horse les interfaces des classes et mettre en œuvre Pegasus d'eux.

Je me demandais s'il existait un autre moyen de résoudre le problème en permettant de créer des objets pour les oiseaux et les chevaux. S'il y avait un moyen de pouvoir créer des animaux également, ce serait génial mais pas nécessaire.

7 votes

Je pense que vous pouvez créer manuellement les classes et les stocker comme membres (composition au lieu d'héritage). Avec le Proxy ( docs.oracle.com/javase/7/docs/api/java/lang/reflect/Proxy.html ), cela pourrait être une option, bien que vous ayez également besoin des interfaces.

0 votes

J'aime la solution de @GáborBakos, mais le seul inconvénient est qu'il ne peut pas avoir un type de référence générique.

4 votes

@RAM alors il ne devrait pas étendre Bird mais plutôt avoir un comportement qui lui permettra de prendre un vol :D problème résolu

118voto

Moritz Petersen Points 6082

Vous pourriez 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 biologiste, donc les termes peuvent être erronés).

Alors vous pouvez toujours créer un

public class Bird implements Avialae {
}

y

public class Horse implements Equidae {}

et aussi

public class Pegasus implements Avialae, Equidae {}

Ajouté à partir des commentaires :

Afin de réduire le code dupliqué, vous pouvez créer une classe abstraite qui contient la plupart du code commun des animaux que vous voulez 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 supplémentaire. Comme Remarques de Brian c'est quelque chose que l'OP savait déjà.

Cependant, je tiens à souligner que je suggère de contourner le problème de la "multi-héritage" avec des interfaces et que je ne recommande pas d'utiliser des interfaces qui représentent déjà un type concret (comme Bird) mais plus un comportement (d'autres font référence au duck-typing, 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'interfaces commençant par un 'I' majuscule, comme par exemple 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 en utilisant des interfaces, utiliser des classes abstraites quand c'est utile, implémenter des classes concrètes quand c'est nécessaire et utiliser la délégation si nécessaire.

9 votes

Ce qui ... est exactement ce que l'OP dit qu'ils savent que vous pouvez faire dans le Q.

4 votes

Comme Pegasus est déjà un cheval (qui vole), je pense que vous pourriez réutiliser plus de code si vous étendez Horse et implémentez Avialae.

8 votes

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.

89voto

Tim B Points 19851

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

  • Le premier est 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 Composition . Puisque l'héritage a échoué, vous devez utiliser la composition.

Le principe de fonctionnement est le suivant : vous disposez d'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 :

  • Oiseau étend Animal met en œuvre IFlier
  • Cheval étend Animal met en œuvre IHerbivore, IQuadruped
  • Pegasus étend Animal met en œuvre IHerbivore, IQuadruped, IFlier

Maintenant IFlier ressemble juste à ça :

 interface IFlier {
     Flier getFlier();
 }

Alors Bird ressemble à ça :

 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.

Toutefois, vous bénéficiez également de toute la souplesse de la composition. Vous pouvez appliquer autant d'interfaces différentes et de classes de support composites que vous le souhaitez à chaque type d'objet Animal - avec autant de contrôle que nécessaire sur la façon dont chaque bit est configuré.

Stratégie Pattern approche alternative de la composition

Une approche alternative, en fonction de ce que vous faites et de la manière dont vous le faites, est de faire en sorte que le Animal La classe de base contient une collection interne pour conserver la liste des différents comportements. Dans ce cas, vous finissez par utiliser quelque chose de plus proche du Strategy Pattern. Cela présente des avantages en termes de simplification du code (par exemple Horse n'a pas besoin de savoir quoi que ce soit sur Quadruped o Herbivore ) mais si vous n'adoptez pas également l'approche par interface, vous perdez une grande partie des avantages du polymorphisme, etc.

0 votes

Une alternative similaire pourrait également être l'utilisation de classes de type, bien que celles-ci ne soient pas si naturelles à utiliser en Java (vous devez utiliser des méthodes de conversion, etc.), cette introduction pourrait être utile pour s'en faire une idée : typeclassopedia.bitbucket.org

1 votes

Cette solution est bien meilleure que l'approche recommandée dans la réponse acceptée. Par exemple, si je veux plus tard ajouter la "couleur du bec" à l'interface Bird, j'ai un problème. Pegasus est un composite, qui prend des éléments de chevaux et d'oiseaux mais qui n'est ni un cheval ni un oiseau à part entière. L'utilisation de la composition est parfaitement logique dans ce scénario.

1 votes

Mais getFlier() doit être réimplémenté pour chaque type d'oiseau.

45voto

Pavel Janicek Points 4552

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

28 votes

Cela peut fonctionner, mais je n'aime pas ce genre d'approche (Wrappper) parce qu'il semble alors que Pégase A un cheval, alors qu'il EST un cheval.

0 votes

Merci. Je n'ai pas pu m'empêcher de poster cette idée. Je sais que c'est stupide, mais je ne vois pas POURQUOI c'est stupide...

3 votes

C'est presque comme une version non raffinée de l'approche de composition de la réponse de Tim B.

26voto

nvrmnd Points 1078

Puis-je suggérer le concept de Le canard à taper ?

Le plus souvent, vous auriez tendance à faire en sorte que Pégase étende une interface Oiseau et Cheval, mais la typographie du canard suggère en fait que vous devriez plutôt hériter de comportement . Comme déjà dit dans les commentaires, un pégase n'est pas un oiseau mais il peut voler. Donc votre Pégase devrait plutôt hériter d'un Flyable -et disons qu'une Gallopable -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 de l' FlyBehaviour y QuackBehaviour et il peut encore y avoir des canards, par exemple le RubberDuck qui ne peut pas voler. Ils auraient pu aussi faire le Duck prolonger un Bird -classe, mais ils auraient alors renoncé à une certaine flexibilité, parce que chaque Duck seraient capables de voler, même les pauvres RubberDuck .

19voto

Smutje Points 4956

Techniquement parlant, vous ne pouvez étendre qu'une seule classe à la fois et implémenter plusieurs interfaces, mais lorsque je m'occupe d'ingénierie logicielle, je préfère proposer une solution spécifique à un problème qui ne peut être résolu de manière générale. D'ailleurs, c'est une bonne pratique OO, pas d'étendre les classes concrètes/de n'étendre que les classes abstraites pour éviter tout comportement indésirable en matière d'héritage - il n'existe pas d'"animal" et il n'est pas possible d'utiliser un objet animal, mais uniquement des animaux concrets.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X