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?
Utiliser 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)
}
java
coding-style
Anthony Kong
la source
la source
quality
au constructeur pourDoItThisWay
?DoItThisWay
etDoItThatWay
se fait une fois dans le constructeur deMain
.Main
est une classe à longue durée de vie etdoingIt
est appelée plusieurs fois.setQuality
méthode sera appelée plusieurs fois pendant la durée de vie de l'DoItThisWay
objet?Réponses:
Je suppose que cela
quality
doit être défini à côté de chaqueletDoIt()
appel sur unDoItThisWay()
.Le problème que je vois surgir ici est le suivant: vous introduisez un couplage temporel (c'est-à-dire ce qui se passe si vous oubliez d'appeler
setQuality()
avant d'appelerletDoIt()
unDoItThisWay
?). Et les implémentations pourDoItThisWay
etDoItThatWay
sont divergentes (l'une doit avoirsetQuality()
appelé, 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 réexaminer la question
letDoIt()
et de déterminer si les informations de qualité doivent faire partie de celles queinfo
vous passez par elle; mais cela dépend des détails.la source
De mon point de vue, aucune de vos versions ne l'est. Le premier appel à faire
setQuality
avantletDoIt
peut être appelé est un couplage temporel . Vous êtes bloqué enDoItThisWay
tant que dérivé deDoSomething
, mais ce n'est pas (du moins pas sur le plan fonctionnel), c'est plutôt quelque chose comme unCela ferait
Main
plutôt quelque chose commeVous 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 usine (cela rendrait les instances interchangeables à nouveau et les deux peuvent être dérivées
DoSomething
). Cela feraitMain
ressembler à ceciJe sais que vous avez écrit cela
Mais vous pouvez mettre en cache les pièces coûteuses à créer en usine et les transmettre au constructeur également.
la source
DoItThisWay
nécessite que la qualité soit définie avant de l'appeler,letDoIt
il se comporte différemment. Je m'attends à ce que aDoSomething
fonctionne de la même manière (sans définir la qualité auparavant) pour tous les dérivés. Est-ce que ça a du sens? Bien sûr, c'est un peu flou, car si nous appelions lasetQuality
méthode depuis une usine, le client serait agnostique quant à savoir si la qualité doit être définie ou non.letDoIt()
(sans informations supplémentaires) "Je peux correctement exécuter (faire ce que je fais) si vous me le fournissezString info
". LesetQuality()
fait d'exiger un appel préalable renforce la condition préalable à l'appelletDoIt()
et constituerait donc une violation de LSP.letDoIt
" ne lèverait aucune exception, et oublier d'appeler "setQuality" feraitletDoIt
alors lever une exception, alors je suis d'accord, une telle implémentation violerait le LSP. Mais cela me semble être des hypothèses très artificielles.Supposons
quality
qu'il ne puisse pas être transmis au constructeur et que l'appel àsetQuality
est requis.Actuellement, un extrait de code comme
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
Maintenant, vous ne voyez pas immédiatement si ce code est toujours correct et le compilateur ne vous le dira pas. Donc, introduire une variable temporaire du type correct et éviter la conversion est un peu plus sûr pour le type, sans aucun effort supplémentaire réel.
Cependant, je donnerais en fait
tmp
un meilleur nom:Une telle dénomination aide à donner un mauvais code.
la source
DoItThisWay
que vous obtenez des paramètres plus spécifiques - selon ce que vous suggérez, le nom doit être changé à chaque fois. Il est préférable d'utiliser des noms pour des variables qui rendent le type d'objet clair, et non les noms de méthodes disponibles.workerThisWay
devienne quelque choseusingQuality
. Dans ce petit extrait de code, il est plus sûr d'être plus précis. (Et, s'il y a une meilleure expression dans leur domaine que "usingQuality", utilisez-la.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 peut alors se soucier d'obtenir et de définir les informations de qualité à l'intérieur de la
getDoer(info)
méthode avant de renvoyer l'objet DoThingsWithQuality concret casté à l'interface DoSomething.la source