web-dev-qa-db-fra.com

Quelle est la meilleure façon d'appeler une méthode qui n'est disponible que pour une classe qui implémente une interface mais pas l'autre?

Fondamentalement, je dois exécuter différentes actions dans une certaine condition. Le code existant est écrit de cette façon

Interface de base

// DoSomething.Java
interface DoSomething {

   void letDoIt(String info);
}

Mise en place de la première classe ouvrière

class DoItThisWay implements DoSomething {
  ...
}

Mise en place de la deuxième classe ouvrière

class DoItThatWay implements DoSomething {
   ...
}

La classe principale

   class Main {
     public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
          worker = new DoItThisWay();
        } else {
          worker = new DoItThatWay();
        }
        worker.letDoIt(info)
     }

Ce code fonctionne bien et est facile à comprendre.

Maintenant, en raison d'une nouvelle exigence, je dois transmettre une nouvelle information qui n'a de sens que pour DoItThisWay.

Ma question est la suivante: le style de codage suivant convient-il à cette exigence?.

tiliser une nouvelle variable et méthode de classe

// Use new class variable and method

class DoItThisWay implements DoSomething {
  private int quality;
  DoSomething() {
    quality = 0;
  }

  public void setQuality(int quality) {
    this.quality = quality;
  };

 public void letDoIt(String info) {
   if (quality > 50) { // make use of the new information
     ...
   } else {
     ...
   }
 } ;

}

Si je le fais de cette façon, je dois apporter la modification correspondante à l'appelant:

   class Main {
     public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
          int quality = obtainQualityInfo();
          DoItThisWay tmp = new DoItThisWay();
          tmp.setQuality(quality)
          worker = tmp;

        } else {
          worker = new DoItThatWay();
        }
        worker.letDoIt(info)
     }

Est-ce un bon style de codage? Ou puis-je simplement le lancer

   class Main {
     public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
          int quality = obtainQualityInfo();
          worker = new DoItThisWay();
          ((DoItThisWay) worker).setQuality(quality)
        } else {
          worker = new DoItThatWay();
        }
        worker.letDoIt(info)
     }
11
Anthony Kong

Je suppose que quality doit être défini à côté de chaque appel de letDoIt() sur un DoItThisWay().

Le problème que je vois surgir ici est le suivant: vous introduisez couplage temporel (c'est-à-dire ce qui se passe si vous oubliez d'appeler setQuality() avant d'appeler letDoIt() sur un DoItThisWay?). Et les implémentations de DoItThisWay et DoItThatWay sont divergentes (l'une doit avoir setQuality() appelée, l'autre pas).

Bien que cela ne puisse pas causer de problèmes pour le moment, cela pourrait éventuellement vous hanter. Il pourrait être utile de jeter un autre regard sur letDoIt() et de déterminer si les informations de qualité peuvent avoir besoin de faire partie du info que vous traversez; mais cela dépend des détails.

6
CharonX

Est-ce un bon style de codage?

De mon point de vue, aucune de vos versions ne l'est. Le premier appel à setQuality avant de pouvoir appeler letDoIt est un couplage temporel . Vous êtes bloqué en regardant DoItThisWay comme un dérivé de DoSomething, mais ce n'est pas (du moins pas fonctionnellement), c'est plutôt quelque chose comme un

interface DoSomethingWithQuality {
   void letDoIt(String info, int quality);
}

Cela ferait de Main quelque chose comme

class Main {
    // omitting the creation
    private DoSomething doSomething;
    private DoSomethingWithQuality doSomethingWithQuality;

    public doingIt(String info) {
        DoSomething worker;
        if (info == 'this') {
            int quality = obtainQualityInfo();
            doSomethingWithQuality.letDoIt(info, quality);
        } else {
            doSomething.letDoIt(info);
        }
    }
}

Vous pouvez en revanche passer directement les paramètres aux classes (en supposant que cela soit possible) et déléguer la décision à utiliser à une fabrique (cela rendrait les instances interchangeables à nouveau et les deux peuvent être dérivées de DoSomething ). Cela ferait ressembler Main à ceci

class Main {
    private DoSomethingFactory doSomethingFactory;

     public doingIt(String info) {
         int quality = obtainQualityInfo();
         DoSomething doSomethingWorker = doSomethingFactory.Create(info, quality);
         doSomethingWorker.letDoIt();
     }
}

Je sais que vous avez écrit cela

car pour des raisons de performances, la construction de DoItThisWay et DoItThatWay est effectuée une fois dans le constructeur de Main

Mais vous pouvez mettre en cache les pièces coûteuses à créer en usine et les transmettre au constructeur également.

15
Paul Kertscher

Supposons que quality ne peut pas être passé dans le constructeur et que l'appel à setQuality est requis.

Actuellement, un extrait de code comme

    int quality = obtainQualityInfo();
    worker = new DoItThisWay();
    ((DoItThisWay) worker).setQuality(quality);

est beaucoup trop petit pour y investir trop de pensées. À mon humble avis, il semble un peu moche, mais pas vraiment difficile à comprendre.

Des problèmes surviennent lorsqu'un tel extrait de code se développe et en raison de la refactorisation, vous vous retrouvez avec quelque chose comme

    int quality = obtainQualityInfo();
    worker = CreateAWorkerForThisInfo();
    ((DoItThisWay) worker).setQuality(quality);

Maintenant, vous ne voyez pas immédiatement si ce code est toujours correct et le compilateur ne vous le dira pas. Ainsi, l'introduction d'une variable temporaire du type correct et l'évitement de la conversion sont un peu plus sûrs pour le type, sans aucun effort supplémentaire réel.

Cependant, je donnerais en fait à tmp un meilleur nom:

      int quality = obtainQualityInfo();
      DoItThisWay workerThisWay = new DoItThisWay();
      workerThisWay.setQuality(quality)
      worker = workerThisWay;

Une telle dénomination aide à rendre le mauvais code semble incorrect.

10
Doc Brown

Une interface commune où l'initialisation varie en fonction des données d'exécution se prête généralement au modèle d'usine.

DoThingsFactory factory = new DoThingsFactory(thingThatProvidesQuality);
DoSomething doSomething = factory.getDoer(info);

doSomething.letDoIt();

DoThingsFactory peut alors se soucier d'obtenir et de définir les informations de qualité à l'intérieur de la méthode getDoer(info) avant de renvoyer l'objet DoThingsWithQuality concret casté à l'interface DoSomething.

2
Greg Burghardt