Modèles de Conception Comportementaux

Les patterns comportementaux définissent comment organiser les objets pour que ceux-ci collaborent (distribution des responsabilités) et expliquent le fonctionnement des algorithmes impliqués.

Chaîne de Responsabilité


Chaîne de responsabilité est un patron de conception comportemental qui permet de faire circuler des demandes dans une chaîne de handlers. Lorsqu’un handler reçoit une demande, il décide de la traiter ou de l’envoyer au handler suivant de la chaîne.

Commande

Commande est un patron de conception comportemental qui prend une action à effectuer et la transforme en un objet autonome qui contient tous les détails de cette action. Cette transformation permet de paramétrer des méthodes avec différentes actions, planifier leur exécution, les mettre dans une file d’attente ou d’annuler des opérations effectuées.

```console
<< interface >>
Commande

+Execute():void
AjouterThemeCmd
-sujet:string

+Execute():void
PublierArticleCmd
-auteur:string
-sujet:string
-titre:string
-urlContenu:string
+Execute():void

PublierNewsCmd
-sujet:string
-auteur:string
-titre:string
+Execute():void

```
La Commande présente l'avantage de simplifier la traçabilité entre les besoins des utilisateurs finaux et leur implémentation dans le système. Mais tout de même, quelle lourdeur pour arriver à quelque chose qui ne semble pas très éloigné de la simple Façade précédente!

Non, en réalité, la valeur ajoutée de la Commande sur la Façade est ailleurs. Chaque invocation de service est maintenant un objet en mémoire, et il en découle de nombreuses (bonnes) propriétés:
  • Il est maintenant possible de créer un historique de tous les services sollicités par les utilisateurs finaux.
  • En cas de problème majeur, il sera donc toujours possible de rejouer les dernières commandes d'un utilisateur pour remettre le système dans un état cohérent.
  • Certains domaines peuvent tirer parti de la connaissance des opérations déclenchées par un client.
La gestion de la relation client en particulier est friande de ce type de renseignement. De même pour la gestion de la navigation des utilisateurs à travers un site Web.
  • Comme nous disposons d'un historique chronologique de commandes, il devient possible de défaire ce que les commandes ont fait, dans le sens inverse, puis éventuellement de rejouer les commandes annulées.
  • Les commandes seront exécutées soit par celui qui en fait la demande, soit par un intermédiaire que nous appellerons l'ordonnanceur de commandes. Ce dernier peut tout à fait gérer une notion de priorité entre commandes, traiter certaines de manière asynchrone ou même déléguer l'exécution de certaines commandes à d'autres noeuds de traitement du système (d'autres serveurs, dans le cas d'un cluster par exemple).

Remarque: ce Pattern a été poussé à l'extrême dans l'approche par Prévalence. Suivant les préceptes de Bertrand Meyer, les outils comme Prevayler et Bamboo.NET vont jusqu'à distinguer les commandes qui ont un effet sur l'état du système et celles qui ne font que le consulter. Seules les premières sont enregistrées dans un journal de commandes (typiquement sur disque dur, par sérialisation), ce qui permet, associé à un mécanisme de persistance périodique des objets métier, d'obtenir un système complètement fiable, persistant, orienté objet... sans utiliser la moindre base de données. Une initiative très intéressante et prometteuse...

Gérer les événements applicatifs

Imaginons que les administrateurs du système de publication souhaitent avoir sous les yeux un tableau de bord graphique qui leur permette de prendre connaissance en temps réel de l'état des 
demandes de publications de nouveaux Articles.

La première technique que l'on peut mettre en oeuvre est simple: il suffit de scruter le Catalogue de Livrables et de réagir en déclenchant une alarme dès que l'état d'un Livrable change. Mais pour cela, il faudrait avoir fait une copie de l'état de chaque objet et comparer cet état "précédent" avec l'état "courant" de chaque objet lors de la scrutation périodique. C'est envisageable si le nombre d'objets à scruter n'est pas très important ou si la fréquence de scrutation est faible.
Dans le cas contraire, il serait plus judicieux d'inverser la dépendance. Après tout, celui qui paraît le plus à même de savoir quand change l'état d'un Livrable, c'est bien le Livrable lui-même. Il suffit donc de faire en sorte que ce dernier puisse informer, notifier les objets intéressés qu'il est en train
de changer d'état. Pour cela, le Livrable doit offrir aux objets extérieurs un moyen de s'abonner auprès de lui et de se désabonner ultérieurement. Inversement, chaque objet extérieur doit convenir avec le Livrable de la méthode à déclencher pour notifier son changement d'état. Ce mécanisme
d'Abonnement-Publication, que l'on aperçoit également dans les Middleware Orientés Message, porte le nom de "Pattern Observateur-Observable".

Interpréteur 

  • Interpréteur (Interpreter)

Iterator

Itérateur est un patron de conception comportemental qui permet de parcourir les éléments d’une collection sans révéler sa représentation interne (liste, pile, arbre, etc.).

Le but du patron de conception itérateur est d’extraire le comportement qui permet de parcourir une collection et de le mettre dans un objet que l’on nomme itérateur.
```console
Collection
+CreerIterateur():Iterateur

<< interface >>

Iterateur
+Avancer():void
+GetElementCourant():Livrable
+GetParcoursTermine():boolean
CollectionIterateur
+CollectionIterateur(aParcourir:Collection):
+Avancer():void
+GetElementCourant():Livrable
+IsParcoursTermine():boolean
```
A noter que l'Itérateur que nous avons modélisé est très ancré dans le modèle des objets métier et qu'il ne pourrait pas être réutilisé dans d'autres situations. Cette contrainte a amené les concepteurs des frameworks Java et .NET, par exemple, à proposer des Itérateurs génériques qui parcourent et renvoient n'importe quel "Object".

L'avantage induit est bien évidemment la souplesse, qui se paie par une diminution de la robustesse de l'application car on peut se tromper lors du transtypage des objets parcourus et le compilateur n'est d'aucune aide puisque pour lui, l'Itérateur peut se promener sur n'importe quel Object. Cette gêne n'existe pas en C++ où les Templates permettent d'implémenter des Itérateurs paramétrés par le type d'objets qu'ils parcourent; et fort heureusement, les langages Java 1.5 et C# 2.0 nous proposent un équivalent aux Templates, les Generics.

D'autre part, on trouve également dans certains frameworks des Itérateurs "améliorés" qui permettent de supprimer l'objet courant lors d'une traversée de structure de données. Appelons ce type d'Itérateurs des "Itérateurs modifiables". Ceux-ci s'avèrent très pratiques comparés aux autres techniques de modification des Collections au gré d'un parcours. Mais à mieux y réfléchir, si nous renvoyons un Itérateur modifiable à un utilisateur de nos classes, par exemple à un utilisateur du Catalogue, rien ne l'empêcherait de parcourir les Livrables et d'en supprimer certains sans que le Catalogue ne puisse effectuer le moindre contrôle. Ce qui revient à nouveau à briser l'encapsulation: il est quasiment aussi grave de renvoyer un Itérateur modifiable qu'une référence sur la Collection d'objets elle-même.

Médiateur 

Médiateur est un patron de conception comportemental qui diminue les dépendances chaotiques entre les objets. Il restreint les communications directes entre les objets et les force à collaborer uniquement via un objet médiateur.

https://github.com/jbogard/MediatR

Mémento 

Mémento est un patron de conception comportemental qui permet de sauvegarder et de rétablir l’état précédent d’un objet sans révéler les détails de son implémentation.

Observateur 

Observateur (Observer) : utilisé en programmation pour envoyer un signal à des modules qui jouent le rôle d'observateurs. En cas de notification, les observateurs effectuent alors l'action adéquate en fonction des informations qui parviennent depuis les modules qu'ils observent (les « observables »). Le principe est que chaque classe observable contient une liste d'observateurs, ainsi à l'aide d'une méthode de notification l'ensemble des observateurs est prévenu. La classe observée hérite de Observable qui gère la liste des observateurs. La classe Observateur est quant à elle purement abstraite, la fonction de mise à jour ne pouvant être définie que par une classe spécialisée.
<code>
Livrable
+Abonnement(obs:ObservateurDeLivrable):void
+Desabonnement(obs:ObservateurDeLivrable):void
<< interface >>
ObservateurDeLivrable
+Notification(l:Livrable):void
InterfaceGraphiqueAdministrateur
+Notification(l:Livrable):void
*
</code>
La même remarque déjà formulée au sujet de l'Itérateur s'applique à nouveau ici: notre Observateur est ancré dans le modèle métier du système de publication puisqu'il ne peut être notifié que des modifications de l'état d'un Livrable. La généricité permet de généraliser ce Pattern tout en conservant un typage fort.
D'autre part, en particulier si l'on transpose l'Observateur-Observable dans une architecture distribuée, une question revient souvent: vaut-il mieux passer systématiquement l'objet dont l'état change en paramètre ou se limiter à sa référence, son identifiant? Même s'il est difficile de trancher dans le cas général, la première solution est souvent choisie:
* Elle n'implique que n invocations de méthodes à distance (autant que d'observateurs), alors que la seconde technique en implique jusqu'à 2*n voire même davantage (tous les Observateurs risquent d'invoquer une méthode sur l'Observable pour prendre connaissance de la modification qui a eu lieu, et éventuellement pour récupérer le nouvel état de l'objet).
* Elle garantit la cohérence des notifications. Dans la seconde approche, les Observateurs reviennent vers l'Observable pour récupérer les informations complémentaires; or ce dernier risque d'avoir encore changé d'état entre temps! Un état intermédiaire peut ainsi ne jamais être porté à la connaissance de certains Observateurs.

Etat

É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. Le principal intérêt de tels objets est d'éviter d'avoir de grosses portions de code constituées uniquement de switch cases. Utilisé entre autres lorsqu'il est souhaité pouvoir changer le comportement d'un objet sans pour autant en changer l'instance. Ce type de comportement généralise les automates à états qui sont souvent utilisés comme intelligences artificielles simples.

Le patron état propose deux classes principales :

la classe État, qui définit l'abstraction des comportements du patron
la classe Contexte, qui s'occupe de l'interfaçage du patron avec le reste de son contexte (l'application).

La classe État permet via ses spécialisations de créer et gérer dynamiquement les comportements dont l'application aura besoin. La classe Contexte qui permet éventuellement à l'application de choisir le comportement à mettre en œuvre et surtout d'orchestrer l'exécution des comportements. Il est à noter que la classe Contexte n'a pas l'exclusivité du choix de comportements. Il est en effet possible d'automatiser le changement d'état à la fin de l'exécution de l'état actif. Cela permet le développement d'automates à états à développement rapide (la spécialisation permettant de récupérer le comportement d'un état parent en lui ajoutant le service qui lui manquait). À grande échelle un automate à état peut résoudre des problèmes très complexes ce qui les hisse au rang d'intelligences artificielles simples, notamment très utilisés dans les jeux vidéo (stratégie, cartes, plateaux, ...) et sont un bon compromis entre la formule mathématique et les réseaux de neurones pour bon nombre d'applications industrielles.

La classe Contexte garde un lien vers une classe abstraite État. Chaque comportement est une spécialisation de cet État. Lorsque l'instance de la classe État change, le comportement du patron change. Ce patron permet donc à la classe Contexte de changer de comportement dynamiquement sans changer ni d'instance ni d'interface.

Stratégie

Sélection d’algorithme à la volée Le patron stratégie est prévu pour fournir le moyen de définir une famille d'algorithmes, encapsuler chacun d'eux en tant qu'objet, et les rendre interchangeables. Ce patron laisse les algorithmes changer indépendamment des clients qui les emploient.

Patron de Méthode

Patron de Méthode est un patron de conception comportemental qui permet de mettre le squelette d’un algorithme dans la classe mère, mais laisse les sous-classes redéfinir certaines étapes de l’algorithme sans changer sa structure.

Visiteur


Ressources
https://refactoring.guru/fr/design-patterns/command

Commentaires

Posts les plus consultés de ce blog

Sécurité des Applications

Principes de la Programmation Orientée Objet

Principe de Responsabilité Unique