État
Intention
État est un patron de conception comportemental qui permet de modifier le comportement d’un objet lorsque son état interne change. L’objet donne l’impression qu’il change de classe.
Problème
Le patron de conception est très proche du concept de l’Automate fini .
Le principe repose sur le fait qu’un programme possède un nombre fini d'états. Le programme se comporte différemment selon son état et peut en changer instantanément. En revanche, selon l’état dans lequel il se trouve, certains états ne lui sont pas accessibles. Ces règles de changement d’état sont appelées transitions. Elles sont également finies et prédéterminées.
Vous pouvez appliquer cette approche aux objets. Imaginons une classe Document
. Un document peut être dans l’un des trois états suivants : Brouillon
(draft), Modération
et Publié
. La méthode publier
du document fonctionne un peu différemment en fonction de son état :
- Dans
Brouillon
, elle passe le document en modération. - Dans
Modération
, elle rend le document public si l’utilisateur actuel est un administrateur. - Dans
Publié
, elle ne fait rien du tout.
Les automates sont généralement implémentés avec beaucoup d’opérateurs conditionnels (if
ou switch
) qui choisissent le comportement approprié en fonction de l’état actuel de l’objet. Cet « état » se limite souvent à un ensemble de valeurs dans les attributs de l’objet. Même si vous n’avez jamais entendu parler des automates finis, vous avez probablement déjà implémenté un état au moins une fois. La structure du code suivant vous dit-elle quelque chose ?
La plus grosse faiblesse de l’automate fini devient visible lorsque l’on commence à ajouter de plus en plus d’états et de comportements qui en sont dépendants à la classe Document
. La majorité des méthodes va contenir d’énormes blocs de conditions qui vont choisir le comportement d’une méthode en fonction de l’état actuel. Ce genre de code est très difficile à maintenir, car tout changement dans la logique de transition demande de modifier les états conditionnels dans chaque méthode.
Plus le projet évolue et plus cette faiblesse s’aggrave. Il est très difficile de prédire tous les états et transitions possibles lors de la phase de conception. Un automate fini doté d’un nombre limité de conditions peut se transformer en un bazar pas possible au bout d’un certain temps.
Solution
Le patron de conception état propose de créer de nouvelles classes pour tous les états possibles d’un objet et d’extraire les comportements liés aux états dans ces classes.
Plutôt que d’implémenter tous les comportements de lui-même, l’objet original que l’on nomme contexte, stocke une référence vers un des objets état qui représente son état actuel. Il délègue tout ce qui concerne la manipulation des états à cet objet.
Pour faire passer le contexte dans un autre état, remplacez l’objet état par un autre qui représente son nouvel état. Vous ne pourrez le faire que si toutes les classes suivent la même interface et si le contexte utilise cette dernière pour manipuler ces objets.
Cette structure ressemble de près au patron de conception Stratégie, mais il y a une différence majeure. Dans le patron de conception état, les états ont de la visibilité entre eux et peuvent lancer les transitions d’un état à l’autre, alors que les stratégies ne peuvent pas se voir.
Analogie
Les boutons de votre smartphone fonctionnent différemment selon l’état de l’appareil :
- Si le téléphone est déverrouillé, appuyer sur des boutons lance différentes fonctionnalités.
- Si le téléphone est verrouillé, appuyer sur n’importe quel bouton envoie sur l’écran de déverrouillage.
- Si la batterie du téléphone est faible, appuyer sur n’importe quel bouton montre l’écran de charge.
Structure
-
Le Contexte stocke une référence vers un des objets concrets État et lui délègue toutes les tâches concernant les états. Il utilise l’interface état pour communiquer avec l’objet état. Il expose un setter pour lui passer un nouvel état.
-
L’interface État déclare les méthodes spécifiques aux états. Ces méthodes doivent fonctionner avec tous les états concrets : des méthodes inutiles qui ne sont jamais appelées à l’intérieur de vos états sont à proscrire.
-
Les États Concrets fournissent leurs propres implémentations aux méthodes qui agissent sur les états. Pour éviter d’écrire le même code dans les différents états, vous pouvez créer des classes abstraites intermédiaires qui encapsulent les comportements identiques.
Les états peuvent garder une référence vers le contexte. Grâce à cette référence, l’état peut récupérer des informations depuis le contexte et lancer des transitions.
-
Le contexte et les états concrets peuvent modifier le prochain état du contexte et lancer une transition en remplaçant l’état lié au contexte.
Pseudo-code
Dans cet exemple, le patron de conception État permet aux touches du lecteur multimédia d’avoir un comportement relatif à l’état actuel de la lecture.
L’objet principal du lecteur est toujours associé à un objet état qui effectue la majeure partie du travail pour le lecteur. Certaines actions remplacent l’état actuel du lecteur par un autre, modifiant sa manière de réagir aux interactions de l’utilisateur.
Possibilités d’application
Utilisez le patron de conception état lorsque le comportement de l’un de vos objets varie en fonction de son état, qu’il y a beaucoup d’états différents et que ce code change souvent.
Ce patron vous propose d’extraire tout le code lié aux états et de le mettre dans des classes distinctes. Ceci vous permet d’ajouter de nouveaux états ou de modifier ceux qui existent indépendamment des autres, et de réduire les coûts de maintenance.
Utilisez ce patron si l’une de vos classes est polluée par d’énormes blocs conditionnels qui modifient le comportement de la classe en fonction de la valeur de ses attributs.
Le patron de conception état vous permet d’extraire des branches de ces conditions et de les transformer en méthodes dans les classes état. Tout en faisant vos modifications, vous pouvez retirer les attributs temporaires et les méthodes qui gèrent les changements d’état du code de votre classe principale.
Utilisez ce patron de conception si vous avez trop de code dupliqué dans des états et transitions similaires de votre automate.
Le patron de conception état vous permet d’assembler des hiérarchies de classes état et de réduire la duplication de code en regroupant le code commun dans des classes de base abstraites.
Mise en œuvre
-
Choisissez la classe qui va prendre le rôle du contexte. Cette classe peut déjà exister et posséder du code qui gère les états, mais vous pouvez en créer une nouvelle si ce code est réparti dans plusieurs classes.
-
Déclarez l’interface état. Vous pourriez très bien vous contenter de recopier toutes les méthodes déclarées dans le contexte, mais ne reprenez que celles qui concernent les états.
-
Pour chaque état, créez une classe qui dérive de l’interface état. Parcourez ensuite les méthodes du contexte pour identifier le code qui concerne cet état et recopiez-le dans votre nouvelle classe.
En effectuant cette manipulation, vous pourriez tomber sur des membres privés dans le contexte. Il y a plusieurs moyens de contournement :
- Rendez ces attributs ou ces méthodes publics.
- Transformez le comportement que vous extrayez en méthode publique que vous mettez dans le contexte, puis appelez-la depuis la classe état. Ce n’est pas le plus esthétique, mais vous pourrez revenir dessus plus tard.
- Imbriquez les classes état dans la classe contexte si votre langage de programmation le permet.
-
Dans votre classe contexte, ajoutez un attribut qui référence le type de l’interface état et un setter public qui permet de redéfinir la valeur de cet attribut.
-
Parcourez à nouveau les méthodes du contexte et remplacez les conditions concernant les états par des appels aux méthodes correspondantes de l’objet état.
-
Pour changer l’état du contexte, créez une instance de l’une des classes état et passez-la au contexte. Ceci peut être fait à l’intérieur du contexte, dans les différents états ou dans le client. Où qu’elle soit, cette classe devient dépendante de la classe concrète État qu’elle instancie.
Avantages et inconvénients
- Principe de responsabilité unique. Organisez le code lié aux différents états dans des classes séparées.
- Principe ouvert/fermé. Ajoutez de nouveaux états sans modifier les classes état ou le contexte existants.
- Simplifiez le code du contexte en éliminant les gros blocs conditionnels de l’automate.
- L’utilisation de ce patron est un peu exagérée si votre automate n’a que quelques états ou qu’il y a peu de transitions.
Liens avec les autres patrons
-
Le Pont, l’État, la Stratégie (et dans une certaine mesure l’Adaptateur) ont des structures très similaires. En effet, ces patrons sont basés sur la composition, qui délègue les tâches aux autres objets. Cependant, ils résolvent différents problèmes. Un patron n’est pas juste une recette qui vous aide à structurer votre code d’une certaine manière. C’est aussi une façon de communiquer aux autres développeurs le problème qu’il résout.
-
L’État peut être considéré comme une extension de la Stratégie. Ces deux patrons de conception sont basés sur la composition : ils changent le comportement du contexte en déléguant certaines tâches aux objets assistant. La stratégie rend ces objets complètement indépendants sans aucune visibilité l’un sur l’autre. Cependant, l’état n’impose pas de restrictions sur les dépendances entre les états concrets, et leur laisse modifier l’état du contexte à volonté.