Java Héritage Multiple
dans une tentative 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 la classe Animal
cela a les sous-classes Bird
et Horse
et je dois 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. De ce que je peux comprendre la façon classique de résoudre cela est de faire les Animal
, Bird
et Horse
interfaces de classes et d'implémenter Pegasus
d'eux.
je me demandais s'il y avait un autre moyen 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 d'être capable de créer des animaux aussi ce serait grand, mais pas nécessaire.
16 réponses
vous pourriez créer des interfaces pour les classes d'animaux (classe au sens biologique), comme 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 encore créer un
public class Bird implements Avialae {
}
et
public class Horse implements Equidae {}
et aussi
public class Pegasus implements Avialae, Equidae {}
ajoutant des commentaires:
afin de réduisez 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. Comme Brian remarque , c'est quelque chose que L'OP savait déjà.
cependant, je tiens à souligner, que je suggère de contourner le problème "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 font référence à duck-typing, 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 tout simplement rien sur la raison pour laquelle vous avez besoin d'une interface. C'est la différence par rapport à la question: construire la hiérarchie de l'héritage en utilisant des interfaces, utiliser des classes abstraites quand utile, mettre en œuvre des classes concrètes si nécessaire et utiliser la délégation si nécessaire.
il existe deux approches fondamentales pour combiner des objets ensemble:
- le premier est héritage . Comme vous avez déjà identifié les limites de l'héritage signifie que vous ne pouvez pas faire ce dont vous avez besoin ici.
- la seconde est Composition . Depuis l'héritage a échoué, vous devez utiliser la composition.
La façon dont cela fonctionne est que vous avoir un objet Animal. À l'intérieur de cet objet, vous ajoutez d'autres objets qui donnent les propriétés et les comportements dont vous avez besoin.
par exemple:
- Oiseau s'étend Animal met en œuvre IFlier
- Cheval s'étend Animal met en œuvre IHerbivore, IQuadruped
- Pegasus extends Animal implements IHerbivore, Iquaduped, 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 de IFliers, et peut utiliser tous les autres avantages du polymorphisme, etc.
cependant vous avez aussi toute la flexibilité de la Composition. Vous pouvez appliquer autant d'interfaces différentes et la classe de renfort composite que vous voulez à chaque type de Animal
- avec autant de contrôle que vous avez besoin sur la façon dont chaque bit est configuré.
schéma de stratégie approche alternative de la composition
une approche alternative selon ce que vous faites et comment vous le faites est d'avoir la classe de base Animal
contenant une collection interne pour garder la liste des différents comportements. Dans ce cas, vous finissez par utiliser quelque chose 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 au sujet de Quadruped
ou Herbivore
) mais si vous ne faites pas aussi l'approche d'interface vous perdez beaucoup des avantages du polymorphisme, etc.
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();
}
}
puis-je suggérer le concept de Duck-typing ?
très probablement, vous auriez tendance à faire le Pegasus étendre un oiseau et une interface de cheval, mais le type de canard suggère en fait que vous devriez plutôt hériter comportement . Comme déjà dit dans les commentaires, un pegasus n'est pas un oiseau, mais il peut voler. Donc votre Pegasus devrait plutôt hériter d'une interface Flyable
et disons une interface Gallopable
.
ce type de concept est utilisé dans le schéma de stratégie . L'exemple donné vous montre en fait comment un canard hérite du FlyBehaviour
et du QuackBehaviour
et peut encore y avoir des canards, par exemple le RubberDuck
, qui ne peuvent pas voler. Ils auraient pu faire aussi le Duck
étendre un Bird
- classe, mais alors ils auraient renoncé à une certaine flexibilité, parce que chaque Duck
serait en mesure de voler, même les pauvres RubberDuck
.
techniquement parlant, vous ne pouvez étendre qu'une classe à la fois et implémenter plusieurs interfaces, mais en mettant la main sur l'ingénierie logicielle, je préférerais proposer une solution spécifique au problème qui ne répond généralement pas. D'ailleurs, c'est une bonne pratique, pas d'étendre les classes concrètes/seulement étendre les classes abstraites pour prévenir les comportements héréditaires indésirables - il n'y a pas une telle chose comme un "animal" et pas d'utilisation d'un objet animal, mais seulement des animaux concrets.
il est sûr de garder un cheval dans une étable avec une demi-porte, comme un cheval ne peut pas passer plus d'une demi-porte. Par conséquent, j'ai mis en place un service de logement de chevaux qui accepte tout élément de type cheval et le met dans une étable avec une demi-porte.
est-ce qu'un cheval est comme un animal qui peut voler, même un cheval?
j'avais l'habitude de penser beaucoup à l'héritage multiple, mais maintenant que j'ai été programmer pendant plus de 15 ans, Je ne me soucie plus de mettre en œuvre plusieurs héritage.
plus souvent qu'autrement, quand j'ai essayé de faire face à un design qui pointait vers l'héritage multiple, je suis venu plus tard pour libérer que j'avais manqué compris le domaine du problème.
ou
en Java 8, qui est encore dans la phase de développement à partir de février 2014, vous pouvez utiliser méthodes par défaut pour obtenir une sorte de C++-comme l'héritage multiple. Vous pouvez aussi jeter un oeil à ce tutoriel qui montre quelques exemples qui devraient être plus faciles à démarrer que la documentation officielle.
Java n'a pas de problème d'héritage Multiple, car il n'a pas d'héritage multiple. C'est à dessein, 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 étant l'objet Composite que Pavel suggère (essentiellement comment 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 Java, mais j'en doute.
si votre question Est académique, alors la bonne solution est que L'oiseau et le cheval sont plus concrets, et il est faux de supposer qu'un Pégase est simplement un oiseau et un cheval combinés. Il serait plus exact de dire qu'un Pegasus a certaines propriétés intrinsèques en commun avec les Oiseaux et les Chevaux (c'est qu'ils ont peut-être des ancêtres communs). Cela peut être suffisamment modélisé comme Moritz réponse.
je pense que cela dépend beaucoup de vos besoins, et de la façon dont vos classes d'animaux doivent être utilisées dans votre code.
si vous voulez pouvoir utiliser les méthodes et les caractéristiques de vos implémentations de chevaux et D'oiseaux dans votre classe Pegasus, alors vous pouvez implémenter Pegasus comme une 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 entité-composant-système approche au lieu d'héritage pour définir vos animaux. Bien sûr, cela signifie que vous n'aurez pas de classes Java individuelles des animaux, mais qu'ils ne seront définis que par leurs composants.
un pseudo-code pour une approche entité-composant-système 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);
}
vous pouvez avoir une hiérarchie d'interface et ensuite étendre vos classes à partir d'interfaces sélectionnées:
public interface IAnimal {
}
public interface IBird implements IAnimal {
}
public interface IHorse implements IAnimal {
}
public interface IPegasus implements IBird,IHorse{
}
et ensuite définir vos classes selon les besoins, en étendant une interface spécifique:
public class Bird implements IBird {
}
public class Horse implements IHorse{
}
public class Pegasus implements IPegasus {
}
Ehm, votre classe peut être la sous-classe pour seulement 1 autre, mais tout de même, vous pouvez avoir autant d'interfaces mises en œuvre, comme vous le souhaitez.
Pegasus est en fait un cheval (c'est un cas particulier d'un cheval), qui est capable de voler (qui est la "compétence" de cette spéciale de cheval). D'autre part, vous pouvez dire, le Pegasus est un oiseau, qui est capable de marcher et est 4legged - tout dépend comment il est plus facile pour vous d'écrire le code.
Comme dans votre cas, vous pouvez dis:
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
}
}
ne simulent pas les héritages multiples. Les créateurs de Java considèrent que l'héritage multiple est une erreur, il n'y a donc pas de telle chose en Java.
si vous voulez combiner la fonctionnalité de deux classes dans la composition d'un objet à usage unique. C'est-à-dire:
public class Main {
private Component1 component1 = new Component1();
private Component2 component2 = new Component2();
}
et si vous voulez 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
met en œuvre Interface2
, vous pouvez définir
class Main implements Interface1, Interface2
de sorte que vous pouvez utiliser les objets de façon interchangeable lorsque le contexte le permet.
donc de mon point de vue, vous ne pouvez pas entrer dans diamond problem.
comme vous le savez déjà, l'héritage multiple de classes en Java n'est pas possible, mais c'est possible avec des interfaces. Vous pouvez également envisager d'utiliser le modèle de conception de la composition.
j'ai écrit un article très complet sur la composition il y a quelques années...
pour réduire la complexité et simplifier le langage, l'héritage multiple n'est pas pris en charge en java.
envisager 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 de l'objet de classe enfant, il y aura ambiguïté pour appeler la méthode de classe A ou B.
puisque les erreurs de temps de compilation sont meilleures que les erreurs d'exécution, java rend les erreurs de temps de compilation si vous héritez 2 classes. Ainsi, 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?
}
}
pour résoudre le problème de l'héritage mutiple en Java → l'interface est utilisée
J2EE (core JAVA) Notes By Mr. K. V. R Page 51
jour-27
- les Interfaces sont essentiellement utilisées pour développer des types de données définis par l'utilisateur.
- en ce qui concerne les interfaces, nous pouvons atteindre les objectifs suivants: concept d'héritages multiples.
- avec des interfaces nous pouvons réaliser le concept de polymorphisme, liaison dynamique et donc nous pouvons améliorer la performance d'un programme JAVA en tours de l'espace mémoire et de temps d'exécution.
une interface est une construction qui contient la collection de undefined méthodes ou d'une interface est une collection de purement abstrait méthode.
[...]
jour-28:
syntaxe-1 pour réutiliser les caractéristiques de l'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 est hériter les fonctionnalités à partir du nombre " n " d'interfaces. ‘Implémente’ est un mot-clé qui est utilisée pour hériter les fonctionnalités de l'interface(s) à un la classe dérivée.
[...]
Syntaxe-2 hériter " n " le nombre d'interfaces à l'autre de l'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; };
- Définir 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 Animal ou oiseau .
- Utiliser héritage établir des relations entre les classes par le partage non-statique et de la non-public des données/méthodes.
- Utiliser Decorator_pattern pour ajouter des capacités de manière dynamique. Cela vous permettra de réduire le nombre de classes d'héritage et de combinaisons.
Regardez ci-dessous exemple pour une meilleure compréhension