Médiateur
Intention
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.
Problème
Prenons une boîte de dialogue qui crée et modifie des profils client. Elle comporte plusieurs contrôles de formulaires comme des champs de texte, des cases à cocher, des boutons, etc.
Certains éléments du formulaire peuvent interagir. Par exemple, en cochant la case « J’ai un chien », vous allez révéler un champ de texte caché qui vous permet d’écrire le nom du chien. Vous pourriez également avoir un bouton Envoyer qui doit valider les valeurs de tous les champs avant de sauvegarder les données.
En écrivant directement la logique dans le code des éléments du formulaire, vous rendez les classes de ces éléments bien plus difficiles à réutiliser dans l’application. Par exemple, vous ne pourrez pas utiliser la classe de la case à cocher dans un autre formulaire, car elle est couplée avec le champ de texte du chien. Vous êtes par conséquent obligé d’utiliser toutes les classes du formulaire du profil si vous voulez vous servir de l’une d’entre elles.
Solution
Le patron de conception médiateur vous propose de mettre fin à toutes les communications directes entre les composants et de rendre ces derniers indépendants les uns des autres. À la place, ces composants collaborent indirectement en utilisant un objet spécial médiateur qui redirige les appels vers les composants appropriés. Ainsi, les composants ne reposent que sur une seule classe médiateur plutôt que d’être couplés à de nombreux collègues.
Dans notre exemple qui porte sur l’édition du formulaire d’un profil, la classe dialogue peut prendre le rôle du médiateur. Elle connait déjà probablement tous ses sous-éléments, vous n’avez donc pas besoin d’y ajouter des dépendances.
Le changement le plus important intervient sur les éléments du formulaire. Prenons le bouton Envoyer. Avant, chaque fois qu’un utilisateur appuyait sur ce bouton, ce dernier devait valider les valeurs des éléments individuels du formulaire. À présent, il se contente d’informer la classe dialogue lorsqu’un clic a lieu. Après avoir reçu cette notification, la classe dialogue effectue la validation ou délègue la tâche aux différents éléments. Le bouton n’est ainsi couplé qu’avec la classe dialogue, au lieu d’être relié à un paquet d’éléments.
Vous pouvez pousser le vice plus loin et diminuer encore plus cette dépendance en extrayant l’interface commune pour toutes ces boîtes de dialogue. L’interface devrait déclarer la méthode de notification que tous les éléments du formulaire utilisent pour prévenir la classe dialogue des événements qui les affectent. Notre bouton Envoyer devrait dorénavant pouvoir manipuler n’importe quelle boîte de dialogue qui implémente cette interface.
Ainsi, le patron de conception médiateur vous permet d’encapsuler une toile complexe de relations entre divers objets à l’intérieur d’un simple objet médiateur. Moins une classe a de dépendances et plus il est facile de la modifier, de l’étendre ou de la réutiliser.
Analogie
Les pilotes qui vont décoller ou atterrir sur la piste ne communiquent pas ensemble directement. Ils s’adressent à un contrôleur aérien qui est assis dans une grande tour près de la piste d’atterrissage. Sans lui, les pilotes seraient obligés de savoir si d’autres avions sont à proximité et décider des priorités d’atterrissage avec un ensemble de pilotes. Les accidents d’avion augmenteraient probablement beaucoup.
La tour n’a pas besoin de contrôler la totalité d’un vol. Elle n’est là que pour faire respecter des contraintes au départ et à l’arrivée, pour faire en sorte que les pilotes n’aient pas trop de paramètres à gérer.
Structure
-
Les classes Composant contiennent de la logique métier. Chaque composant possède une référence vers un médiateur, déclaré avec le type de l’interface médiateur. Le composant ne connait pas la classe du médiateur, vous pouvez ainsi réutiliser les mêmes composants dans d’autres programmes en les liant à un autre médiateur.
-
L’interface Médiateur déclare des méthodes pour communiquer avec les composants et n’est généralement dotée que d’une seule méthode de notification. Les composants peuvent passer n’importe quel contexte en paramètre de cette méthode (ce qui inclut leurs propres objets), mais en évitant de provoquer un couplage entre un composant récepteur et la classe du demandeur.
-
Les Médiateurs Concrets encapsulent les relations entre les divers composants. Les médiateurs concrets gardent souvent des références vers les composants qu’ils gèrent et s’occupent même parfois de leur cycle de vie.
-
Les composants ne doivent pas avoir de visibilité sur les autres composants. S’il arrive quelque chose d’important à un composant, il doit seulement en prévenir le médiateur. Quand ce dernier reçoit la notification, il doit pouvoir facilement identifier le demandeur, ce qui peut suffire pour déterminer le composant qui doit être déclenché en retour.
De son point de vue, le composant ne voit qu’une boîte noire. Le demandeur ne sait pas qui va se charger de sa demande, et le récepteur ignore qui l’a envoyée.
Pseudo-code
Dans cet exemple, le Médiateur vous aide à éliminer les dépendances mutuelles entre différentes classes de l’UI (boutons, cases à cocher et libellés de texte).
Un élément déclenché par un utilisateur ne communique pas directement avec les autres éléments, même si c’est l’impression qui s’en dégage. À la place, l’élément n’a besoin que de prévenir son médiateur qu’un événement a eu lieu, en lui donnant les informations contextuelles avec la notification.
Dans cet exemple, la boîte de dialogue d’identification prend le rôle du médiateur. Elle sait comment les éléments concrets sont censés collaborer et facilite leur communication indirecte. Lorsque la boîte de dialogue reçoit une notification d’événement, elle sait quel élément est concerné et redirige l’appel en conséquence.
Possibilités d’application
Utilisez ce patron si vous rencontrez des difficultés pour modifier certaines classes trop fortement couplées avec d’autres.
Le médiateur vous permet d’extraire toutes les relations entre les classes dans une classe séparée, en isolant les modifications appliquées à un composant spécifique du reste des composants.
Utilisez ce patron quand vous ne pouvez pas réutiliser un composant ailleurs, car il est trop dépendant des autres composants.
Après avoir mis en place le médiateur, les composants individuels n’ont plus de visibilité sur les autres composants. Ils peuvent toujours communiquer ensemble mais indirectement, via un objet médiateur. Pour réutiliser un composant dans une application différente, vous n’avez besoin que de lui fournir une classe médiateur.
Utilisez le médiateur lorsque vous créez des tonnes de sous-classes pour les composants, juste pour pouvoir bénéficier de leur comportement de base dans différents contextes.
Toutes les relations entre composants se trouvent à l’intérieur d’un médiateur. De nouvelles classes médiateur peuvent donc facilement définir de nouveaux moyens de collaboration pour ces composants sans avoir à les modifier.
Mise en œuvre
-
Identifiez des classes qui sont fortement couplées et qui pourraient bénéficier de plus d’indépendance (pour une maintenance plus aisée ou pour faciliter la réutilisation de ces classes).
-
Déclarez l’interface médiateur et écrivez le protocole de communication voulu entre les médiateurs et les différents composants. Dans la plupart des cas, une seule méthode est suffisante pour recevoir les notifications des composants.
Cette interface est cruciale si vous voulez pouvoir réutiliser les classes des composants dans différents contextes. Tant que le composant communique avec le médiateur en passant par l’interface générique, vous pouvez relier le composant avec une implémentation différente du médiateur.
-
Implémentez la classe concrète du médiateur. Faites en sorte que cette classe garde des références vers tous les composants qu’elle gère. Elle en tirera de gros bénéfices.
-
Vous pouvez encore aller plus loin en rendant le médiateur responsable de la création et de la destruction des objets composant. Le médiateur ressemblera ainsi à une fabrique ou à une façade.
-
Les composants doivent avoir une référence vers l’objet médiateur. La connexion est souvent établie dans le constructeur du composant, dans lequel un objet médiateur est passé en paramètre.
-
Modifiez le code des composants afin qu’ils appellent la méthode de notification du médiateur plutôt que des méthodes écrites dans d’autres composants. Mettez tout le code qui appelle les autres composants dans la classe médiateur, puis appelez-le lorsque le médiateur reçoit des notifications de ce composant.
Avantages et inconvénients
- Principe de responsabilité unique. Vous pouvez mettre les communications entre les différents composants au même endroit, rendant le code plus facile à comprendre et à maintenir.
- Principe ouvert/fermé. Vous pouvez ajouter de nouveaux médiateurs sans avoir à modifier les composants déjà en place.
- Vous diminuez le couplage entre les différents composants d’un programme.
- Vous pouvez réutiliser les composants individuels plus facilement.
- Avec le temps, un médiateur peut évoluer en Objet Omniscient.
Liens avec les autres patrons
-
La Chaîne de responsabilité, la Commande, le Médiateur et l’Observateur proposent différentes solutions pour associer les demandeurs et les récepteurs.
- La chaîne de responsabilité envoie une demande ordonnée qui est passée tout au long d’une chaîne dynamique de récepteurs potentiels, jusqu’à ce que l’un d’entre eux décide de la traiter.
- La commande établit des connexions unidirectionnelles entre les demandeurs et les récepteurs.
- Le médiateur élimine les liens directs entre les demandeurs et les récepteurs, et les force à communiquer indirectement via un objet médiateur.
- L’observateur permet aux récepteurs de s’inscrire et de se désinscrire dynamiquement à la réception des demandes.
-
La Façade et le Médiateur ont des rôles similaires : ils essayent de faire collaborer des classes étroitement liées.
- La façade définit une interface simplifiée pour un sous-système d’objets, mais elle n’ajoute pas de nouvelles fonctionnalités. Le sous-système lui-même n’a pas connaissance de la façade. Les objets situés à l’intérieur du sous-système peuvent communiquer directement.
- Le médiateur centralise la communication entre les composants du système. Les composants ne voient que l’objet médiateur et ne communiquent pas directement.
-
La différence entre le Médiateur et l’Observateur est souvent très fine. Dans la majorité des cas, vous pouvez implémenter l’un ou l’autre, mais parfois vous pouvez les utiliser simultanément. Regardons comment faire.
Le but principal du médiateur est d’éliminer les dépendances mutuelles entre un ensemble de composants du système. À la place, ces composants peuvent devenir dépendants d’un unique objet médiateur. Le but de l’observateur est d’établir des connexions dynamiques à sens unique entre les objets, où certains objets peuvent être les subordonnés d’autres objets.
Il existe une implémentation populaire du médiateur qui repose sur l’observateur. L’objet médiateur joue le rôle du diffuseur et les composants agissent comme des souscripteurs qui s’inscrivent et se désinscrivent des événements du médiateur. Lorsque ce type de conception est mis en place, le médiateur ressemble de près à l’observateur.
Si vous êtes un peu perdu, rappelez-vous qu’il y a plusieurs manières d’implémenter le médiateur. Par exemple, vous pouvez associer de manière permanente tous les composants au même objet médiateur. Cette implémentation ne ressemblera pas à l’observateur, mais sera tout de même une instance du patron de conception médiateur.
Maintenant, imaginez un programme dont tous les composants sont devenus des diffuseurs, permettant des connexions dynamiques les uns avec les autres. Nous n’aurons pas d’objet médiateur centralisé, seulement un ensemble d’observateurs distribués.