web-dev-qa-db-fra.com

Comment les interfaces peuvent-elles remplacer le besoin d'héritage multiple lorsque des classes existent

Tout d'abord ... Désolé pour ce post. Je sais qu'il existe de nombreux messages sur stackoverflow qui traitent de l'héritage multiple. Mais je sais déjà que Java ne prend pas en charge l'héritage multiple et je sais que l'utilisation des interfaces devrait être une alternative. Mais je ne comprends pas et vois mon dilemme:

Je dois apporter des modifications à un outil très très grand et complexe écrit en Java. Dans cet outil, il existe une structure de données construite avec de nombreux objets de classe différents avec une hiérarchie de membres liés. En tous cas...

  • J'ai une classe Tagged qui a plusieurs méthodes et renvoie une balise d'objet en fonction de la classe de l'objet. Il a besoin de membres et de variables statiques.
  • Et une deuxième classe appelée XMLElement permet de lier des objets et de générer finalement un fichier XML. J'ai également besoin de variables membres et statiques ici.
  • Enfin, j'ai ces nombreuses classes de données qui devraient presque toutes étendre XMLElement et certaines d'entre elles Tagged.

Ok ok, cela ne fonctionnera pas car il n'est possible d'étendre qu'une seule classe. Je lis très souvent que tout avec Java est ok et il n'est pas nécessaire d'avoir un héritage multiple. Je crois, mais je ne vois pas comment une interface devrait remplacer l'héritage.

  1. Cela n'a aucun sens de mettre la vraie implémentation dans toutes les classes de données car c'est la même à chaque fois mais cela serait nécessaire avec les interfaces (je pense).
  2. Je ne vois pas comment je pourrais changer une de mes classes d'héritage en interface. J'ai des variables ici et elles doivent être exactement là.

Je ne comprends vraiment pas, alors s'il vous plaît quelqu'un peut-il m'expliquer comment gérer cela?

53
fpdragon

Vous devriez probablement privilégier la composition (et la délégation) à l'héritage:

public interface TaggedInterface {
    void foo();
}

public interface XMLElementInterface {
    void bar();
}

public class Tagged implements TaggedInterface {
    // ...
}

public class XMLElement implements XMLElementInterface {
    // ...
}

public class TaggedXmlElement implements TaggedInterface, XMLElementInterface {
    private TaggedInterface tagged;
    private XMLElementInterface xmlElement;

    public TaggedXmlElement(TaggedInterface tagged, XMLElementInterface xmlElement) {
        this.tagged = tagged;
        this.xmlElement = xmlElement;
    }

    public void foo() {
        this.tagged.foo();
    }

    public void bar() {
        this.xmlElement.bar();
    }

    public static void main(String[] args) {
        TaggedXmlElement t = new TaggedXmlElement(new Tagged(), new XMLElement());
        t.foo();
        t.bar();
    }
}
56
JB Nizet

En fait, je n'ai pas de bonne réponse autre que Java DEVRAIT avoir l'héritage multiple. Le point entier que les interfaces devraient être en mesure de remplacer le besoin d'héritage multiple est comme le gros mensonge qui quand répété suffisamment de fois devient vrai.

L'argument est que l'héritage multiple provoque tous ces problèmes (la-di-dah), mais j'entends toujours ces arguments de Java développeurs qui n'ont jamais utilisé C++. Je ne me souviens pas non plus C++ les programmeurs disant "Gee, j'adore le C++, mais s'ils se débarrassaient seulement de l'héritage multiple, cela deviendrait un excellent langage". Les gens l'utilisaient quand c'était pratique et pas quand c'était faux.

Votre problème est un cas classique où l'héritage multiple serait approprié. Toute suggestion de refactoriser le code vous indique vraiment comment contourner le PROBLÈME qui Java n'a pas d'héritage multiple.

De plus, toute la discussion selon laquelle "oh, la délégation est meilleure, la-di-dah" confond la religion et le design. Il n'y a pas de bonne façon. Les choses sont soit plus utiles soit moins utiles et c'est tout.

Dans votre cas, l'héritage multiple serait plus utile et une solution plus élégante.

En ce qui concerne la refactorisation de votre code sous une forme moins utile pour satisfaire toutes les personnes religieuses qui n'ont jamais utilisé l'héritage multiple et qui croient que "l'héritage multiple est mauvais", je suppose que vous devrez rétrograder votre code car je ne vois pas Java "s'améliorant" de cette manière de sitôt. Il y a trop de gens qui répètent le mantra religieux au point d'être stupide que je ne vois pas qu'il soit jamais ajouté au langage.

En fait, ma solution pour vous serait "x étend Tagged, XMLElement" et ce serait tout.

... mais comme vous pouvez le voir dans les solutions fournies ci-dessus, la plupart des gens pensent qu'une telle solution serait bien trop complexe et confuse!

Je préférerais m'aventurer moi-même dans le territoire "x étend a, b", même si c'est une solution très effrayante qui pourrait submerger les capacités de la plupart des programmeurs Java Java.

Ce qui est encore plus étonnant avec les solutions suggérées ci-dessus, c'est que tous ceux ici qui ont suggéré que vous refactorisez votre code en "délégation" parce que l'héritage multiple est mauvais, s'ils étaient confrontés au même problème, le résoudraient simplement en faisant : "x étend a, b" et en finir avec cela, et tous leurs arguments religieux sur "délégation vs héritage" disparaîtraient. Tout le débat est stupide, et il n'est avancé que par des programmeurs désemparés qui ne font que démontrer à quel point ils peuvent réciter un livre et combien ils peuvent penser par eux-mêmes.

Vous avez raison à 100% que l'héritage multiple aiderait, et non, vous faites quelque chose de mal dans votre code si vous pensez que Java devrait l'avoir.

74
SouthRoad

Similaire à ce que Andreas_D a suggéré mais avec l'utilisation de classes internes. De cette façon, vous étendez en effet chaque classe et pouvez la remplacer dans votre propre code si vous le souhaitez.

interface IBird {
    public void layEgg();
}

interface IMammal {
    public void giveMilk();
}

class Bird implements IBird {
    public void layEgg() {
        System.out.println("Laying eggs...");
    }
}

class Mammal implements IMammal {
    public void giveMilk() {
        System.out.println("Giving milk...");
    }
}

class Platypus implements IMammal, IBird {

    private class LayingEggAnimal extends Bird {}
    private class GivingMilkAnimal extends Mammal {}

    private LayingEggAnimal layingEggAnimal = new LayingEggAnimal();

    private GivingMilkAnimal givingMilkAnimal = new GivingMilkAnimal();

    @Override
    public void layEgg() {
        layingEggAnimal.layEgg();
    }

    @Override
    public void giveMilk() {
        givingMilkAnimal.giveMilk();
    }
}
5
SHOJAEI BAGHINI

Tout d'abord, cela n'a aucun sens de mettre la véritable implémentation dans toutes les classes de données car elle est la même à chaque fois, mais cela serait nécessaire avec les interfaces (je pense).

Que diriez-vous d'utiliser l'agrégation pour les balises?

  1. Renommez votre classe Tagged en Tags.

  2. Créez une interface Tagged:

    interface Tagged {Tags getTags (); }

  3. Laissez chaque classe qui doit être "balisée", implémenter Tagged et laissez-la avoir un champ tags, qui est renvoyé par getTags.

Deuxièmement, je ne vois pas comment je pourrais changer une de mes classes d'héritage en interface. J'ai des variables ici et elles doivent être exactement là.

C'est vrai, les interfaces ne peuvent pas avoir de variables d'instance. Cependant, les structures de données stockant les balises ne devraient pas nécessairement faire partie des classes marquées. Factorisez les balises dans une structure de données distincte.

4
aioobe

Je le résoudrais de cette façon: extraire les interfaces pour les classes Tagged et XMLElement (peut-être que vous n'avez pas besoin de toutes les méthodes dans l'interface publique). Ensuite, implémentez les deux interfaces et la classe d'implémentation a une Tagged (votre classe concrète Tagged) et une XMLElement (votre classe concrète XMLElement):

 public class MyClass implements Tagged, XMLElement {

    private Tagged tagged;
    private XMLElement xmlElement;

    public MyClass(/*...*/) {
      tagged = new TaggedImpl();
      xmlElement = new XMLElementImpl();
    }

    @Override
    public void someTaggedMethod() {
      tagged.someTaggedMethod();
    }
  }

  public class TaggedImpl implements Tagged {
    @Override
    public void someTaggedMethod() {
      // so what has to be done
    }
  }

  public interface Tagged {
     public void someTaggedMethod();
  }

(et la même chose pour XMLElement)

2
Andreas_D

une façon possible;

1- Vous pouvez créer des classes de base pour des fonctionnalités communes, les rendre abstraites si vous n'avez pas besoin de les instancier.

2- Créez des interfaces et implémentez ces interfaces dans ces classes de base. Si une implémentation spécifique est nécessaire, rendez la méthode abstraite. chaque classe concrète peut avoir son propre impl.

3- étendre la classe de base abstraite pour des classes concrètes et implémenter des interfaces spécifiques à ce niveau également

1
fmucar

En utilisant bien l'interface et la classe de base unique, vous déclarez simplement:

    A) Un objet ne peut être que d'un seul type (ce qui est vrai dans la vraie vie si vous pensez, Un pigeon est un oiseau, un toyota est une voiture, etc. Un pigeon est aussi un animal mais chaque oiseau est un animal de toute façon, donc son hiérarchiquement au-dessus du type d'oiseau -Et dans votre OOP design La classe animale doit être la base de la classe Bird au cas où vous auriez besoin de la représenter -) et
    B) peut faire beaucoup de choses différentes (Un oiseau peut chanter, voler. Une voiture peut courir, s'arrêter, etc.)

Dans un monde où les objets peuvent être de plusieurs types (horizontalement) Disons qu'un dauphin est un mammifère et aussi un animal marin, dans ce cas l'héritage multiple aurait plus de sens. Il serait plus facile de le représenter en utilisant l'héritage multiple.

1
Jeff Schreib

Vous vous demandez simplement si l'on ne pourrait pas simplement utiliser des classes internes (membres) (LRM 5.3.7)? Par exemple. comme ceci (basé sur la première réponse ci-dessus):

// original classes:
public class Tagged {
    // ...
}

public class XMLElement {
    // ...
}

public class TaggedXmlElement {
    public/protected/private (static?) class InnerTagged extends Tagged {
      // ...
    }

    public/protected/private (static?) class InnerXmlElement extends XMLElement  {
        // ...
    }

}

De cette façon, vous avez une classe TaggedXmlElement qui contient en fait tous les éléments des deux classes d'origine et dans TaggedXmlElement, vous avez accès aux membres non privés des classes membres. Bien sûr, on n'utiliserait pas "super", mais appeler des méthodes de classe membre. Alternativement, on pourrait étendre l'une des classes et faire de l'autre une classe membre. Il y a certaines restrictions, mais je pense qu'elles peuvent toutes être contournées.

1
Einar H.

Je rencontre un problème similaire sur Android. J'avais besoin d'étendre un Button et un TextView (tous deux héritant de View) avec des fonctions supplémentaires. En raison de ne pas avoir accès à leur super classe, j'avais besoin de trouver une autre solution. J'ai écrit une nouvelle classe qui encapsule toutes les implémentations:

class YourButton extends Button implements YourFunctionSet {
    private Modifier modifier;

    public YourButton(Context context) {
        super(context);
        modifier = new Modifier(this);
    }

    public YourButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        modifier = new Modifier(this);
    }

    public YourButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        modifier = new Modifier(this);
    }

    @Override
    public void generateRandomBackgroundColor() {
        modifier.generateRandomBackgroundColor();
    }
}

class Modifier implements YourFunctionSet {

    private View view;

    public Modifier(View view) {
        this.view = view;
    }

    @Override
    public void generateRandomBackgroundColor() {
        /**
         * Your shared code
         *
         * ......
         *
         * view.setBackgroundColor(randomColor);
         */
    }
}


interface YourFunctionSet {
    void generateRandomBackgroundColor();
}

Le problème ici est que vos classes ont besoin de la même super classe. Vous pouvez également essayer d'utiliser différentes classes, mais vérifiez de quel type il s'agit, par exemple

public class Modifier{
private View view;
private AnotherClass anotherClass;

public Modifier(Object object) {
    if (object instanceof View) {
        this.view = (View) object;
    } else if (object instanceof AnotherClass) {
        this.anotherClass = (AnotherClass) object;
    }
}

public void generateRandomBackgroundColor(){
    if(view!=null){
        //...do
    }else if(anotherClass!=null){
        //...do
    }
}
}

Voici donc essentiellement ma classe Modifier, la classe qui encapsule toutes les implémentations.

J'espère que cela aide quelqu'un.

0

Utiliser la composition serait la voie à suivre comme l'a suggéré un autre développeur. Le principal argument contre l'héritage multiple est l'ambiguïté créée lorsque vous étendez à partir de deux classes avec la même déclaration de méthode (même nom de méthode et paramètres). Personnellement, cependant, je pense que c'est un tas de merde. Une erreur de compilation pourrait facilement être lancée dans cette situation, ce qui ne serait pas très différent de la définition de plusieurs méthodes du même nom dans une seule classe. Quelque chose comme l'extrait de code suivant pourrait facilement résoudre ce dilema:

public MyExtendedClass extends ClassA, ClassB {
    public duplicateMethodName() {
        return ClassA.duplicateMethodName();
    }
}

Un autre argument contre l'héritage multiple est que Java essayait de garder les choses simples pour que les développeurs amateurs ne créent pas un réseau de classes interdépendantes qui pourraient créer un système logiciel désordonné et déroutant. Mais comme vous le voyez dans votre cas, cela complique et confond également les choses lorsqu'il n'est pas disponible. De plus, cet argument pourrait être utilisé pour 100 autres choses dans le codage, c'est pourquoi les équipes de développement ont des revues de code, des logiciels de vérification de style et des builds nocturnes.

Dans votre situation particulière, vous devrez vous contenter de la composition (voir la réponse de Shojaei Baghini). Il ajoute un peu de code de plaque de chaudière, mais il émule le même comportement que l'héritage multiple.

0
Greg H