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)
}
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.
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.
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.
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.