On m'a donné du code Java à regarder, qui simule une course de voitures, dont une implémentation d'une machine d'état de base. Il ne s'agit pas d'une machine à états informatique classique, mais simplement d'un objet qui peut avoir plusieurs états et qui peut basculer entre ses états en fonction d'une série de calculs.
Pour décrire le problème, j'ai une classe Car, avec une classe enum imbriquée qui définit certaines constantes pour l'état de la voiture (telles que OFF, IDLE, DRIVE, REVERSE, etc.). Dans cette même classe de voitures, j'ai une fonction de mise à jour, qui consiste essentiellement en une grande déclaration de commutateur qui active l'état actuel des voitures, effectue des calculs, puis modifie l'état des voitures.
Pour autant que je puisse voir, l'état Cars n'est utilisé que dans sa propre classe.
Ma question est la suivante: est-ce la meilleure façon de traiter la mise en œuvre d'une machine à états de la nature décrite ci-dessus? Cela semble être la solution la plus évidente, mais dans le passé, j'ai toujours entendu dire que "les instructions de commutation sont mauvaises".
Le principal problème que je peux voir ici est que l'instruction switch peut devenir très volumineuse à mesure que nous ajoutons plus d'états (si cela est jugé nécessaire) et que le code peut devenir lourd et difficile à maintenir.
Quelle serait une meilleure solution à ce problème?
object.state = object.function(object.state);
Réponses:
J'ai transformé la voiture en une sorte de machine à états en utilisant State Pattern . Remarquez qu'aucune
switch
ou aucuneif-then-else
instruction n'est utilisée pour la sélection de l'état.Dans ce cas, tous les états sont des classes internes, mais cela pourrait être implémenté autrement.
Chaque état contient les états valides dans lesquels il peut changer.
L'utilisateur est invité à indiquer l'état suivant si plusieurs sont possibles, ou simplement à confirmer si un seul est possible.
Vous pouvez le compiler et l'exécuter pour le tester.
J'ai utilisé une boîte de dialogue graphique car c'était plus facile de cette façon de l'exécuter de manière interactive dans Eclipse.
Le diagramme UML est extrait d' ici .
la source
C'est ce genre de simplification excessive qui donne un mauvais nom à la programmation orientée objet. L'utilisation
if
est tout aussi "mauvaise" que l'utilisation d'une instruction switch. Quoi qu'il en soit, vous ne répartissez pas de manière polymorphe.Si vous devez avoir une règle qui rentre dans une morsure saine, essayez celle-ci:
Une instruction switch qui n'est pas dupliquée ailleurs dans la base de code peut parfois réussir à ne pas être mauvaise. Si les cas ne sont pas publics, mais sont encapsulés, ce n'est vraiment l'affaire de personne d'autre. Surtout si vous savez comment et quand le refactoriser en cours. Ce n'est pas parce que vous le pouvez que vous le devez. C'est parce que vous le pouvez qu'il est moins essentiel de le faire maintenant.
Si vous vous retrouvez à essayer d'introduire de plus en plus de choses dans l'instruction switch, à diffuser les connaissances sur les cas, ou à souhaiter que ce ne soit pas si mal d'en faire une copie, alors il est temps de refactoriser les cas en classes distinctes.
Si vous avez le temps de lire plus de quelques extraits sonores sur la refactorisation des instructions switch, c2 a une page très bien équilibrée sur l' odeur de l'instruction switch .
Même dans le code POO, tous les commutateurs ne sont pas mauvais. C'est comment vous l'utilisez et pourquoi.
la source
La voiture est un type de machine d'état. Les instructions switch sont le moyen le plus simple d'implémenter une machine à états dépourvue de super états et de sous-états.
la source
Les instructions de commutation ne sont pas mauvaises. N'écoutez pas les gens qui disent des choses comme "changer les déclarations sont mauvaises"! Certaines utilisations particulières des instructions switch sont un contre-modèle, comme l'utilisation de switch pour émuler le sous-classement. (Mais vous pouvez également implémenter cet antipattern avec des ifs, donc je suppose que les ifs sont mauvais aussi!).
Votre implémentation sonne bien. Vous avez raison, il sera difficile à maintenir si vous ajoutez de nombreux autres états. Mais ce n'est pas seulement une question d'implémentation - avoir un objet avec de nombreux états avec un comportement différent est en soi un problème. L'imagerie de votre voiture a 25 états, chacun présentant un comportement différent et des règles de transition d'état différentes. Préciser et documenter ce comportement serait une tâche énorme. Vous aurez des milliers de règles de transition d'état! La taille de la
switch
ne serait qu'un symptôme d'un problème plus important. Donc, si possible, évitez de suivre cette voie.Un remède possible consiste à diviser l'État en sous-états indépendants. Par exemple, REVERSE est-il vraiment un état distinct de DRIVE? Peut-être que les états de la voiture pourraient être divisés en deux: l'état du moteur (OFF, IDLE, DRIVE) et la direction (FORWARD, REVERSE). L'état et la direction du moteur seront probablement principalement indépendants, vous réduisez donc la duplication logique et les règles de transition d'état. Plus d'objets avec moins d'états sont beaucoup plus faciles à gérer qu'un seul objet avec de nombreux états.
la source
Dans votre exemple, les voitures sont simplement des machines d'État au sens classique de l'informatique. Ils ont un petit ensemble d'états bien définis et une sorte de logique de transition d'état.
Ma première suggestion est d'envisager de décomposer la logique de transition en sa propre fonction (ou classe, si votre langage ne prend pas en charge les fonctions de première classe).
Ma deuxième suggestion est d'envisager de décomposer la logique de transition en l'état lui-même, qui aurait sa propre fonction (ou classe, si votre langage ne prend pas en charge les fonctions de première classe).
Dans les deux cas, le processus de transition de l'état ressemblerait à ceci:
ou
La seconde pourrait bien sûr être insignifiante dans la catégorie des voitures pour ressembler à la première.
Dans les deux scénarios, l'ajout d'un nouvel état (par exemple, RÉDACTION), impliquerait uniquement l'ajout d'un nouveau type d'objet d'état et la modification des objets qui basculent spécifiquement vers le nouvel état.
la source
Cela dépend de sa taille
switch
.Dans votre exemple, je pense que
switch
c'est OK car il n'y a pas vraiment d'autre état auquel je puisse penser que vousCar
pourriez avoir, donc il ne s'aggraverait pas avec le temps.Si le seul problème est d'avoir un grand commutateur où chacun
case
a beaucoup d'instructions, alors créez simplement des méthodes privées distinctes pour chacun.Parfois, les gens suggèrent le modèle de conception de l' état , mais il est plus approprié lorsque vous traitez avec une logique complexe et que les États prennent des décisions commerciales différentes pour de nombreuses opérations distinctes. Sinon, les problèmes simples devraient avoir des solutions simples.
Dans certains scénarios, vous pouvez avoir des méthodes qui n'effectuent des tâches que lorsque l'état est A ou B, mais pas C ou D, ou plusieurs méthodes avec des opérations très simples qui dépendent de l'état. Ensuite, une ou plusieurs
switch
déclarations seraient mieux.la source
Cela ressemble à une machine d'état de la vieille école du type qui était utilisé avant que quiconque ne fasse de la programmation orientée objet, sans parler des modèles de conception. Il peut être implémenté dans n'importe quel langage contenant des instructions switch, comme C.
Comme d'autres l'ont dit, il n'y a rien de fondamentalement mauvais avec les instructions switch. Les alternatives sont souvent plus compliquées et plus difficiles à comprendre.
À moins que le nombre de boîtiers de commutation ne devienne ridiculement grand, la chose peut rester tout à fait gérable. La première étape pour le garder lisible consiste à remplacer le code dans chaque cas par un appel de fonction pour implémenter le comportement de l'état.
la source