L’adapter, il rend tout le monde d’accord !

Un adaptateur entre deux classes UML

Un autre article signé design pattern ! Et cette fois-ci, je mets la lumière sur le pattern Adapter ! Aussi nommé Wrapper, ce pattern rentre dans la catégorie des patterns structurels dont leurs rôles sont de gérer la relation entre les classes. Vous connaissez tous le principe d’un adaptateur ? Un petit exemple, quand on essaye de brancher un appareil anglais vers une prise française, il faut ce dispositif pour bien le brancher ils ne sont pas compatibles. Ici, c’est la même chose ! Voyons voir le fameux adaptateur du code source ! C’est parti !

Le pattern Adapter

Qu’est ce qu’il fait

Le pattern Adapter permet à ce que deux classes puissent communiquer entre elles, même si leurs interfaces sont incompatibles ! Pour ça, il se glisse entre les deux classes et se charge de gérer les problèmes. Le pattern porte bien son nom, il joue le rôle d’adaptateur !

C’est quoi deux interfaces incompatibles ? C’est par exemple une classe qui attend un certain type de paramètre et qu’une autre ne peut pas lui fournir le type attendu. C’est aussi tous simplement, une classe qui se sert d’une ou de plusieurs méthodes d’une autre classe et que pour x raisons, ces méthodes changent de nom. Je parle de communication entre deux classes, mais ça peut aussi être un appel provenant d’une fonction ou du code directement. Mais dans la programmation orientée objet, on aime parler entre classes. 🙂

L’Adapter permet donc d’utiliser l’interface d’une classe avec une autre même s’ils ne peuvent pas travailler ensemble, et ça sans les modifier !

Quand l’utiliser ?

Alors vous me direz, pourquoi ne pas changer les classes pour qu’elles puissent redevenir compatibles entre elles ? Et si pour une quelconque raison c’était impossible ? C’est là que l’Adapter prend tout son sens ! Dans quel moment est-il impossible de changer son code source ? En général, ce n’est pas lié à notre propre code source directement.

C’est le cas en général pour des outils qu’on utilise dans les programmes comme des librairies. Il n’est pas recommandé de changer le fonctionnement interne d’une librairie, si bien entendu l’outil donne la possibilité d’être modifié.

Une autre raison d’utiliser ce pattern est lorsque l’outil utilisé est susceptible de faire des changements dans son code. Vous commencez à voir le principe ? C’est lorsqu’un changement, futur ou non, intervient dans une librairie utilisée par notre programme et dont la modification de celle-ci est impossible, que le pattern Adapter est adapté 8-).

Enfin, si vous hésitez entre deux outils similaires et que vous en choisissez un après mûre réflexions. Si plus tard vous décidez d’utiliser le deuxième outil, le pattern Adapter va rendre la modification de votre code source beaucoup plus facile !

En bref, le pattern Adapter est idéal lorsqu’un changement intervient ou serait susceptible d’intervenir et qui pourrait poser des problèmes.

OK, dans le cas de deux librairies qui communiquent entre elles, le pattern Adapter est tout à fait légitime ! Mais s’il n’y a que notre programme qui utilise l’outil, pourquoi ne tout simplement pas modifier son code source pour qu’il soit compatible avec l’outil ? Et bien dans le meilleur des cas, oui c’est ce qu’il faudrait faire. Mais imaginez que les changements à appliquer sont lourds, car les paramètres utilisés ont changés, que leurs noms ont changés et qu’en plus, les méthodes sont utilisées une bonne centaine de fois dans votre application.

Pire, vous avez des contraintes de temps et vous ne pouvez pas vous permettre de tout changer dans votre code. Là, le pattern Adapter est aussi une solution. Une solution qui peut être à titre temporaire ou même permanent, si l’outil en question a la fâcheuse tendance de changer le fonctionnement de ces méthodes.

Fort heureusement, ce genre de problème est rare. Les développeurs de librairie laissent les anciennes méthodes qui redirigent vers les nouvelles. Mais si ce genre de cas arrive, vous avez un outil pour y faire face !

Une autre utilisation du pattern adapter est si vous êtes justement, un de ces développeurs de librairie ou tout autre outil utilisé par un grand nombre de personnes ! Si pour une quelconque raison vous décidez de changer les méthodes de votre outil, il faut assurer à tous les utilisateurs de votre librairie que leurs logiciels fonctionnent bien avec les anciennes méthodes.

Quand ne pas l’utiliser ?

L’adapter est une solution lorsque vous utilisez des classes dont vous ne contrôlez pas l’évolution ou lorsque vous développez des outils utilisés par beaucoup de personnes. Si vous n’avez pas les problèmes cités plus haut, il est inutile de mettre ce pattern dans votre code. En effet, l’Adapter peut s’implémenter facilement et pratiquement n’importe où dans votre code, c’est ce qui fait sa grande force. Mais cela augmente aussi la taille du code dans votre projet et pourrait perturber la lisibilité de celui-ci.

Implémentation de l’Adapter

Caractéristiques

Il y a deux manières d’implémenter un adapter. La première consiste à utiliser l’agrégation ou la composition. Sous ces noms barbares et bien complexes se cache un concept simple. L’Adapter va posséder une variable qui contiendra une instance de notre classe devenue sauvage et incompatible ! L’instanciation de la classe incompatible se fait généralement lors de la construction de l’Adapter. Soit on lui fournit l’objet en question en paramètre, soit il est créé directement dans le constructeur de l’Adapter.

L’Adapter fournira par la suite des méthodes pour pouvoir travailler avec la classe. Ces méthodes vont recevoir les arguments de ceux qui l’utilisent, effectuent divers traitements si nécessaire et enfin utilisent les méthodes de la classe incompatible. Cette façon d’implémenter ce pattern s’appelle l’object adapter !

La deuxième manière de l’implémenter est d’utiliser l’héritage. Le principe est le même qu’en haut à savoir fournir une méthode aux utilisateurs de l’Adapter pour travailler avec une classe. Simplement, il ne contiendra pas la classe, mais sera en quelque sorte la classe, ou plutôt une sous-classe grâce à l’héritage. Cette deuxième méthode s’appelle la class adapter !

Un pattern qui cache en fait deux patterns ? Non, c’est juste deux manières d’implémenter l’Adapter, simplement on leurs a donnés des noms :-). Mais, lequel utiliser ? L’object adapter est à favoriser dans la majorité des cas, car il est par nature plus flexible, plus facile, favorise le découplage entre deux classes et peut être utilisé dans tous langages orienté objet.

Conclusion sur les caractéristiques et les solutions qu'apporte le pattern Adapter

Implémentation en PHP

Agrégation/Composition : Class Adapter

Diagramme de classe UML : Object Adapter

	class ClasseIncompatible {
		private $a;
		private $b;

		public function __construct($a,$b) {
			$this->a = $a;
			$this->b = $b;
		}
		
		public function multiply(){
			return ($this->a * $this->b);
		}

	}

	// Classe suivant le pattern Adapter
	class ClasseAdapter{
		private $classeIncompatible;
		
		public function __construct(ClasseIncompatible $classeIncompatible) {
			$this->classeIncompatible = $classeIncompatible;
		}
		
		public function multiplier(){
			return $this->classeIncompatible->multiply();
		}
	}

	// Utilisation
	$multiplicateur = new ClasseAdapter(new ClasseIncompatible(2,3));
	echo $multiplicateur->multiplier();

Héritage : Object Adapter

Diagramme de classe UML : Class Adapter

	class ClasseIncompatible {
		private $a;
		private $b;

		public function __construct($a,$b) {
			$this->a = $a;
			$this->b = $b;
		}
		
		public function multiply(){
			return ($this->a * $this->b);
		}

	}

	// Interface de notre Adapter, pas obligatoire pour que ça fonctionne
	interface IClasseAdapter {
		public function multiplier();
	}

	// Classe suivant le pattern Adapter
	class ClasseAdapter extends ClasseIncompatible implements IClasseAdapter{	
		public function multiplier(){
			return $this->multiply();
		}
	}

	// Utilisation
	$multiplicateur = new ClasseAdapter(2,3);
	echo $multiplicateur->multiplier();

Voilà, si vous testez ce code, il vous affichera un magnifique 6 ! 😀

Il existe des tonnes de manières d’implémenter ce pattern, mais le principe reste le même, l’Adapter joue le rôle d’intermédiaire entre deux classes et corrige des problèmes d’incompatibilité qui existent ou qui pourraient survenir.

Par exemple , si la fonction multiply change de nom, cela n’aura aucun impact sur le code qui utilise l’Adapter. Seul l’adapter sera à changer. En fait, tous changements qui aura lieu dans la ClasseIncompatible sera gérés par l’Adapter et n’affectera en rien ceux qui l’utilisent.

Exemple concret : Le constructeur de bouton.

Nous y sommes, nouveau business en ligne, les gens adorent les boutons sur les sites internet, car c’est trop à la mode, on clique sur tous ce qui bouge ! Vous, saisissant une belle opportunité de vous enrichir, vous vous lancez corps et âme dans ce business avec votre application full web « ButtonMania ». C’est la folie, succès grandiose et l’argent coule à flots ! Votre application est très bien faite, et en plus votre code source est impeccable ! Bien entendu, vous n’aimez pas réinventer la roue, par conséquent vous utilisez plusieurs librairies dans votre projet, dont le fameux ButtonConstructor.

ButtonConstructor vous permet de créer des boutons très tendance et moderne, et tout cela avec une simplicité remarquable. Pour ça, il suffit d’instancier la classe et de se servir de la méthode createButton(). Il faut lui fournir 4 paramètres minimum : la position x, la position y, la hauteur du bouton, la largeur du bouton.

Tout fonctionne à merveille jusqu’à la mise à jour de cet outil qui change complètement la façon dont fonctionne ButtonConstructor. Maintenant, ce n’est plus createButton() qui crée des boutons, mais buildButton(), et il ne prend plus les 4 paramètres, mais seulement un nouvel objet de type ButtonInfo, qui contient nos fameux 4 paramètres.

Problème, il faut changer tous les appels de cette méthode vers la nouvelle version. Il y en a un sacré paquet et vous n’avez pas le temps pour ça. En plus, vous utilisez la version en ligne de l’outil et la mise à jour a été directement appliquée sur votre application qui ne fonctionne plus très bien. Pas moyen de télécharger l’ancienne version, comment faire ? Utilisons le pattern Adapter.

Le but est de rendre le plus rapidement possible votre application en état de marche pour ça, voici des bouts de code avant application du pattern :

// ...

// Utilisation avant la mise à jour de l'outil
$buttonConstructor = new ButtonConstructor();

// ...

// Un des nombreux appels de la méthode
if(createButton(50,50,150,300)){
	echo "bouton crée avec succès !";
}

// ...

Implémentons notre pattern, pour ça on va créer une classe spécial en suivant le pattern Adapter.

class ButtonConstructorAdapter {
	private $buttonConstructor;

    function __construct(ButtonConstructor $buttonConstructor) {
		$this->buttonConstructor = $buttonConstructor;
	}
	
	function createButton($x, $y, $hauteur, $largeur){
		return buildButton(new buttonInfo($x, $y, $hauteur, $largeur));
	}

}

// ...

// Utilisation après la mise à jour de l'outil et l'implémentation du pattern Adapter
$buttonConstructor = new ButtonConstructorAdapter(new ButtonConstructor());

// ...

// Un des nombreux appels de la méthode ==> Aucun changement !
if(createButton(50,50,150,300)){
	echo "bouton crée avec succès !";
}

// ...

Une classe à ajouter, une modification à faire dans la construction de notre outil et tout refonctionne !

Ce design pattern est un autre outil à mettre dans un coin de votre tête si jamais des imprévus de ce type-là arrivent 8-).

A++

Partager l’article :
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.