Java - Collision de nom de méthode dans l'implémentation de l'interface

87

Si j'ai deux interfaces, toutes deux assez différentes dans leurs objectifs, mais avec la même signature de méthode, comment faire pour qu'une classe implémente les deux sans être obligé d'écrire une seule méthode qui sert pour les deux interfaces et d'écrire une logique alambiquée dans la méthode implémentation qui vérifie pour quel type d'objet l'appel est effectué et appelle le code approprié?

En C #, cela est surmonté par ce que l'on appelle une implémentation d'interface explicite. Existe-t-il un moyen équivalent en Java?

Bhaskar
la source
37
Quand une classe doit mettre en œuvre deux méthodes avec la même signature que faire des choses différentes , votre classe est presque certainement fait trop de choses.
Joachim Sauer
15
Ce qui précède peut ne pas toujours être vrai IMO. Parfois, dans une seule classe, vous avez besoin de méthodes qui doivent confirmer à un contrat externe (donc contraignant sur les signatures), mais qui ont des implémentations différentes. En fait, ce sont des exigences courantes lors de la conception d'une classe non triviale. La surcharge et le dépassement sont nécessairement des mécanismes pour permettre des méthodes qui font des choses différentes qui peuvent ne pas différer dans la signature, ou différer très légèrement.Ce que j'ai ici est juste un peu plus restrictif en cela qu'il ne permet pas de sous-classer / et ne permet même pas moindre variation sur les signatures.
Bhaskar
1
Je serais intrigué de savoir quelles sont ces classes et méthodes.
Uri
2
J'ai rencontré un tel cas où une ancienne classe "Address" implémentait des interfaces Person et Firm qui avaient une méthode getName () retournant simplement une chaîne du modèle de données. Une nouvelle exigence commerciale spécifiait que le Person.getName () retournait une chaîne au format "Nom, Prénom". Après de longues discussions, les données ont été reformatées dans la base de données à la place.
belwood
12
Le simple fait de dire que la classe fait presque certainement trop de choses n'est PAS CONSTRUCTIF. J'ai ce cas précis en ce moment que ma classe a des collisions de noms de méthodes à partir de 2 interfaces différentes, et ma classe ne fait PAS trop de choses. Les objectifs sont assez similaires, mais font des choses légèrement différentes. N'essayez pas de défendre un langage de programmation manifestement gravement handicapé en accusant le questionneur d'implémenter une mauvaise conception de logiciel!
j00hi

Réponses:

75

Non, il n'y a aucun moyen d'implémenter la même méthode de deux manières différentes dans une classe en Java.

Cela peut conduire à de nombreuses situations déroutantes, c'est pourquoi Java l'a interdit.

interface ISomething {
    void doSomething();
}

interface ISomething2 {
    void doSomething();
}

class Impl implements ISomething, ISomething2 {
   void doSomething() {} // There can only be one implementation of this method.
}

Ce que vous pouvez faire est de composer une classe à partir de deux classes qui implémentent chacune une interface différente. Ensuite, cette classe aura le comportement des deux interfaces.

class CompositeClass {
    ISomething class1;
    ISomething2 class2;
    void doSomething1(){class1.doSomething();}
    void doSomething2(){class2.doSomething();}
}
jjnguy
la source
9
Mais de cette façon, je ne peux pas passer une instance de CompositeClass quelque part une référence des interfaces (ISomething ou ISomething2) sont attendues? Je ne peux même pas m'attendre à ce que le code client soit capable de convertir mon instance sur l'interface appropriée, alors est-ce que je ne perds pas quelque chose par cette restriction? Notez également que de cette manière, lors de l'écriture de classes qui implémentent réellement les interfaces respectives, nous perdons l'avantage d'avoir le code dans une seule classe, ce qui peut parfois être un sérieux obstacle.
Bhaskar
9
@Bhaskar, vous marquez des points valables. Le meilleur conseil que j'ai est d'ajouter une méthode ISomething1 CompositeClass.asInterface1();et ISomething2 CompositeClass.asInterface2();à cette classe. Ensuite, vous pouvez simplement extraire l'un ou l'autre de la classe composite. Il n'y a cependant pas de grande solution à ce problème.
jjnguy
1
En parlant des situations confuses que cela peut entraîner, pouvez-vous donner un exemple? Ne pouvons-nous pas penser au nom de l'interface ajouté au nom de la méthode comme une résolution de portée supplémentaire qui peut alors éviter la collision / confusion?
Bhaskar
@Bhaskar C'est mieux si nos classes adhèrent au principe de responsabilité unique. S'il existe une classe qui implémente deux interfaces très différentes, je pense que la conception devrait être retravaillée pour diviser les classes pour prendre en charge une seule responsabilité.
Anirudhan J
1
À quel point serait-il déroutant, vraiment, d'autoriser quelque chose comme public long getCountAsLong() implements interface2.getCount {...}[au cas où l'interface requiert un longmais les utilisateurs de la classe s'attendent int] ou private void AddStub(T newObj) implements coolectionInterface.Add[en supposant qu'il collectionInterfaceexiste une canAdd()méthode, et pour toutes les instances de cette classe, elle retourne false]?
supercat du
13

Il n'y a pas vraiment de moyen de résoudre ce problème en Java. Vous pouvez utiliser des classes internes comme solution de contournement:

interface Alfa { void m(); }
interface Beta { void m(); }
class AlfaBeta implements Alfa {
    private int value;
    public void m() { ++value; } // Alfa.m()
    public Beta asBeta() {
        return new Beta(){
            public void m() { --value; } // Beta.m()
        };
    }
}

Bien que cela ne permette pas les conversions de AlfaBetaà Beta, les downcasts sont généralement mauvais, et si l'on peut s'attendre à ce qu'une Alfainstance ait souvent un Betaaspect aussi, et pour une raison quelconque (généralement l'optimisation est la seule raison valable), vous voulez pouvoir pour le convertir Beta, vous pouvez créer une sous-interface Alfaavec Beta asBeta()dedans.

Gustafc
la source
Voulez-vous dire une classe anonyme plutôt qu'une classe interne?
Zaid Masud
2
@ZaidMasud je veux dire les classes internes, puisqu'elles peuvent accéder à l'état privé de l'objet englobant). Ces classes internes peuvent bien sûr être également anonymes.
gustafc
11

Si vous rencontrez ce problème, c'est probablement parce que vous utilisez l' héritage là où vous devriez utiliser la délégation . Si vous devez fournir deux interfaces différentes, bien que similaires, pour le même modèle de données sous-jacent, vous devez utiliser une vue pour fournir à moindre coût l'accès aux données à l'aide d'une autre interface.

Pour donner un exemple concret pour ce dernier cas, supposons que vous souhaitiez implémenter à la fois Collectionet MyCollection(qui n'hérite pas de Collectionet possède une interface incompatible). Vous pouvez fournir des fonctions Collection getCollectionView()et MyCollection getMyCollectionView()qui fournissent une implémentation légère de Collectionet MyCollection, en utilisant les mêmes données sous-jacentes.

Pour le premier cas ... supposons que vous vouliez vraiment un tableau d'entiers et un tableau de chaînes. Au lieu d'hériter des deux List<Integer>et List<String>, vous devriez avoir un membre de type List<Integer>et un autre membre de type List<String>, et faire référence à ces membres, plutôt que d'essayer d'hériter des deux. Même si vous n'aviez besoin que d'une liste d'entiers, il est préférable d'utiliser la composition / délégation sur l'héritage dans ce cas.

Michael Aaron Safyan
la source
Je ne pense pas. Vous oubliez les bibliothèques qui vous obligent à implémenter différentes interfaces pour être compatibles avec elles. Vous pouvez rencontrer cela en utilisant plusieurs bibliothèques en conflit beaucoup plus souvent que vous le rencontrez dans votre propre code.
nightpool
1
@nightpool si vous utilisez plusieurs bibliothèques nécessitant chacune des interfaces différentes, il n'est toujours pas nécessaire qu'un seul objet implémente les deux interfaces; vous pouvez demander à l'objet d'avoir des accesseurs pour renvoyer chacune des deux interfaces différentes (et appeler l'accesseur approprié lors du passage de l'objet à l'une des bibliothèques sous-jacentes).
Michael Aaron Safyan
1

Le problème Java "classique" affecte également mon développement Android ...
La raison semble simple:
plus de frameworks / bibliothèques que vous devez utiliser, plus facilement les choses peuvent être hors de contrôle ...

Dans mon cas, j'ai une classe BootStrapperApp hérité de android.app.Application ,
alors que la même classe devrait également implémenter une interface Platform d'un framework MVVM afin de s'intégrer.
Une collision de méthode s'est produite sur une méthode getString () , qui est annoncée par les deux interfaces et devrait avoir une implémentation de différenet dans différents contextes.
La solution de contournement (moche..IMO) utilise une classe interne pour implémenter tous plates-formesméthodes, juste à cause d'un conflit mineur de signature de méthode ... dans certains cas, une telle méthode empruntée n'est même pas utilisée du tout (mais a affecté la sémantique de conception majeure).
J'ai tendance à convenir que l'indication de contexte / espace de noms explicite de style C # est utile.

Tiancheng
la source
1
Je n'avais jamais réalisé à quel point C # était réfléchi et riche en fonctionnalités avant de commencer à utiliser Java pour le développement Android. J'ai pris ces fonctionnalités C # pour acquises. Java manque trop de fonctionnalités.
Damn Vegetables
1

La seule solution qui m'est venue à l'esprit consiste à utiliser des objets de référence à celui que vous souhaitez implémenter de multiples interfaces.

ex: en supposant que vous ayez 2 interfaces à implémenter

public interface Framework1Interface {

    void method(Object o);
}

et

public interface Framework2Interface {
    void method(Object o);
}

vous pouvez les inclure dans deux objets Facador:

public class Facador1 implements Framework1Interface {

    private final ObjectToUse reference;

    public static Framework1Interface Create(ObjectToUse ref) {
        return new Facador1(ref);
    }

    private Facador1(ObjectToUse refObject) {
        this.reference = refObject;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Framework1Interface) {
            return this == obj;
        } else if (obj instanceof ObjectToUse) {
            return reference == obj;
        }
        return super.equals(obj);
    }

    @Override
    public void method(Object o) {
        reference.methodForFrameWork1(o);
    }
}

et

public class Facador2 implements Framework2Interface {

    private final ObjectToUse reference;

    public static Framework2Interface Create(ObjectToUse ref) {
        return new Facador2(ref);
    }

    private Facador2(ObjectToUse refObject) {
        this.reference = refObject;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Framework2Interface) {
            return this == obj;
        } else if (obj instanceof ObjectToUse) {
            return reference == obj;
        }
        return super.equals(obj);
    }

    @Override
    public void method(Object o) {
        reference.methodForFrameWork2(o);
    }
}

En fin de compte, la classe que vous vouliez devrait quelque chose comme

public class ObjectToUse {

    private Framework1Interface facFramework1Interface;
    private Framework2Interface facFramework2Interface;

    public ObjectToUse() {
    }

    public Framework1Interface getAsFramework1Interface() {
        if (facFramework1Interface == null) {
            facFramework1Interface = Facador1.Create(this);
        }
        return facFramework1Interface;
    }

    public Framework2Interface getAsFramework2Interface() {
        if (facFramework2Interface == null) {
            facFramework2Interface = Facador2.Create(this);
        }
        return facFramework2Interface;
    }

    public void methodForFrameWork1(Object o) {
    }

    public void methodForFrameWork2(Object o) {
    }
}

vous pouvez maintenant utiliser les méthodes getAs * pour "exposer" votre classe

pas du tout
la source
0

Vous pouvez utiliser un modèle d'adaptateur pour les faire fonctionner. Créez deux adaptateurs pour chaque interface et utilisez-les. Cela devrait résoudre le problème.

AlexCon
la source
-1

Tout va bien lorsque vous avez un contrôle total sur tout le code en question et que vous pouvez l'implémenter dès le départ. Imaginez maintenant que vous avez une classe publique existante utilisée dans de nombreux endroits avec une méthode

public class MyClass{

    private String name;

    MyClass(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

Maintenant, vous devez le passer dans le WizzBangProcessor standard qui nécessite des classes pour implémenter le WBPInterface ... qui a également une méthode getName (), mais au lieu de votre implémentation concrète, cette interface s'attend à ce que la méthode renvoie le nom d'un type de Wizz Bang Processing.

En C # ce serait un trvial

public class MyClass : WBPInterface{

    private String name;

    String WBPInterface.getName(){
        return "MyWizzBangProcessor";
    }

    MyClass(String name){
        this.name = name;
    }

    public String getName(){
        return name;
    }
}

Dans Java Tough, vous allez devoir identifier chaque point dans la base de code déployée existante où vous devez convertir d'une interface à l'autre. Bien sûr, la société WizzBangProcessor aurait dû utiliser getWizzBangProcessName (), mais ce sont aussi des développeurs. Dans leur contexte, getName était bien. En fait, en dehors de Java, la plupart des autres langages basés sur OO le prennent en charge. Java est rare en forçant toutes les interfaces à être implémentées avec la même méthode NAME.

La plupart des autres langages ont un compilateur qui est plus qu'heureux de prendre une instruction pour dire "cette méthode dans cette classe qui correspond à la signature de cette méthode dans cette interface implémentée est son implémentation". Après tout, le but de la définition des interfaces est de permettre à la définition d'être abstraite de l'implémentation. (Ne me lancez même pas sur les méthodes par défaut dans les interfaces en Java, et encore moins sur le remplacement par défaut ... parce que bien sûr, chaque composant conçu pour une voiture de route devrait pouvoir être claqué dans une voiture volante et juste fonctionner - hé ce sont deux voitures ... Je suis sûr que la fonctionnalité par défaut de dire que votre GPS ne sera pas affectée par les entrées par défaut de tangage et de roulis, car les voitures ne font que lacer!

user8232920
la source