Design Pattern : Adaptateur

[toc]
Je me suis intéressé récemment au design pattern Adaptateur.
Voici les sources que j’ai ou à ce propos :

Wikipedia

 

Mechant Blog

J’ai eu un peu de de mal à sortir de mon raisonnement pour arriver à comprendre ce que voulait expliquer Nicolas.
D’après ce que j’avais cru comprendre de son modèle : il développe un jeu de courses de voitures. Il parle de types de voiture classiques et de voitures volantes.
De ce que j’avais compris (un peu vite), une Voiture Volante était obligée de proposer une méthode changerPneu() sous prétexte qu’elle implémente une interface IVoiture, qui de par son contrat, impose cette méthode.

Je n’arrivais pas à comprendre qu’il faille obliger une VoitureVolante à faire cela. Ca n’avait aucun sens : une voiture volante n’a pas de pneu, tout au plus des réacteurs. Elle n’a pas donc pas la moindre raison d’avoir ce besoin de changer un pneu.
Bref je trouvais ce raisonnement vraiment aberrant.
Il s’agissait pour moi ni plus ni moins qu’une erreur assumée de conception « faute de mieux ».

Le syndrôme du Bélier

Il faillait que je fonce tête baissée dans mon mode de raisonnement pour aller jusqu’au bout et en voir les limites, pour enfin m’ouvrir à son mode de pensée.
« La taxonomie est pas bonne, une voiture volante n’est pas une voiture, il faut que Voiture et VoitureVolante implémentent une interface plus générique que IVoiture, ou que les 2 prennent des chemins taxonomiques différents. »

Evolutivité taxonomique

Mais je commençais à me faire des noeuds au cerveau car songeant à l’évolution du jeu, je me disais : mais comment faire pour que le jeu soit évolutif, qu’il accepte de nouveaux véhicules, des buggys, des skateboards, des voitures solailres, des véhicules fictifs
tout ca sans revoir en profondeur à chaque fois l’éventail des véhicules ???

Le cas du CIRAD

Le pire dans cet imbroglio c’est que j’ai déjà travaillé par le passé sur ce genre de problématique auprès du CIRAD, et j’avais trouvé le mécanisme pour la résoudre, sauf que je ne savais pas que ce mécanisme était un vrai Design Pattern.
A l’époque mon problème était le suivant :

  1. je dois exécuter des recherches documentaires grâce à des robots (objets métier écrits en PHP4) qui ont chacun leur propre manière de rechercher de l’information  : moissonneur documentaire (OAI), web-scraper HTML, flux RSS, indexer Mnogosearch, base Mysql, fichiers texte.
  2. Je dois fournir les critères de recherche à chaque objet de manière uniforme
  3. Je dois paralléliser les recherches pour que le processus soit non-bloquant (multi trahd, asynchronicité)
  4. Je dois sérializer et stocker les résultats de recherche dans un fichier tampon, dans un format le plus universel / ouvert / exploitable possible (processus ETL)
  5. Je dois présenter les résultats dans un formaliste HTML standard pour l’internaute.
  6. De façon récursive, le meta moteur de recherche doit être lui-même interrogeable par un meta moteur homologue (le client web devient service web)
  7. A terme le meta moteur doit être capable d’accueillir n’importe quel nouveau robot de recherche.

Enfin je comprends !

Et puis j’ai fin par comprendre ce que signifie substitution dans « Substitution de Liskov ».
Ce que je n’avais pas compris c’est que :

  1. Nicolas le rédacteur faisait allusion non pas à la classe d’origine VoitureVolante, mais à un adaptateur de VoitureVolante
  2. l’interface IVoiture n’est pas du tout le supertype de Voiture et VoitureVolante  : c’est l’interface qu’attend le JEU de courses qui doit les manipuler. Et change toute la façon de penser !

En fait l’Adaptateur est une solution géniale :
moi le Jeu je dois manipuler une flotte de véhicule à typologie hétérogène :
voiture terrestre, voiture volante, voiture solaire, scooter, skateboard, buggy, engins de l’espace. Bref de tout.
Pour être sur de pouvoir les manipuler correctement, je dois m’assurer que chacun des véhicule qui me sera fourni possède obligatoirement les facultés que j’attend d’eux.
Si l’un d’eux ne les a pas, il est refusé d’emblée.

Critères d’entrée

T’as pas de mocassins ? tu rentres pas !

Et c’est là qu’intervient le IVehicule : en gros l’idée c’est :
« toi qui veut configurer mes paramètres de Jeu : tu te débrouille comme tu veux, mais les véhicules que tu me files, quels qu’ils soient, ils doivent tous pouvoir

  • avancer,
  • reculer,
  • besoin de refaire le plein,
  • des pneus à changer,
  • une vitesse à réguler,
  • une carrosserie skinnable,
  • des accessoires à rajouter »

or tous ces véhicule ne sont pas du tout égaux entre eux. C’est hyper hétérogène :

  • une voiture a des roues et un réservoir et peut avancer/reculer et un volant,
  • un skateboard à des roulettes et pas de réservoir et pas de volant, peut freiner mais sans freiner mécaniques
  • une voiture volante a un réservoir et un volant, des réacteurs à la place des pneus, peut avancer mais pas reculer
  • etc, …

Dénominateur commun

Bref comment passer au Jeu un type objret qui soit le dénominateur commun à tout ce gros plat de spaghetti qui n’a pas fini de grossir ? ca parait impossible la comme ca.
c’est le problème du mouton 5 pattes.
Si on rend l’anneau de Sauron (la classe mère qui les gouverne tous) suffisamment compatible avec tous les véhicules descendant, elle devient trop générique et n’a plus aucun intérêt.

Ne pas forcer la Nature

L’erreur c’est de vouloir forcer les choses, faire rentrer le cube dans la pyramide à coups de marteau.
Arrêtons d’essayer de rendre tous ces véhicules absoluement compatibles pour passer
un type pseudo correct au Jey
Chacun reste à sa place avec la typologie qui lui est propre, et les moutons seront bien gardés.

Se poser la bonne question

La question à se poser est : comment envisager une collaboration saine entre les différents véhicules et le Jeu censé les accueillir ?

Et bien tout simplement le Vehicule doit se conformer aux exigences du Jeu pour être accepté.
On utilise un Adaptateur qui va garantir cette conformité : les véhicules embarquent tous la même API. Qu’ils soient des objets genuine ou des objets « pas dans le moule rendus compatible au travers d’un Adaptateur dédié.
Ainsi le Jeu peut manipuler tous les véhicules avec à chaque fois la même API, sans exception. C’est l’API de IVehicule.

L’Adaptateur ressemble à ceci :

interface IVehiculeAdaptateur
{
	function avancer();
	function reculer();
	function changerPneu(Vehicule.PositionPneu position);
}

class VoitureVolanteAdaptateur extends Adaptateur implements IVehiculeAdaptateur
{
	private VoitureVolante voiture = null;

	public function VoitureVolanteAdaptateur(VoitureVolante voiture)
	{
                // on récup l'objet originel dans une var interne
		this.voiture = voiture;
	}
	public void function avancer()
	{
		// actions propres au moteur et aux réacteurs de la voiture volante
	}
	public void function reculer()
	{
		// une voiture volante ne sait pas reculer. Elle ne fait donc rien.
                //Dans le cas d'une voiture citadine on appelerait ici this.voiture.reculer()
		return ;
	}
	public function changerPneu(Voiture.PositionPneu position)
	{
		// cette méthode est un wrapper : elle exécute la vraie méthode qui se cache derrière la méthode d'interface.
		// le Jeu n'a pas a savoir que c'est un wrapper.
                // l'enum indique la position du pneu (ou reacteur), il ne fournit pas l'objet lui meme. On évite un cran de complexité.
                // sinon on peut tres bien substituer le pneu par le reacteur et inversement du moment que l'objet passé est celui attendu par la méthode métier
		this.voiture.changerReacteur(position);
	}
}

Le jeu peut itérer sur chaque véhicule pour changer les pneus après 50 tours de pistes.
Ca se passe comme ceci :

class JeuCourse
{
	private ArrayList = new ArrayList;

	public function addVehicule(IVehicule vehicule)
	{
		this.vehiculesPool.add(vehicule);
	}
	public function addVehicules(ArrayList vehicules)
	{
		this.vehiculesPool.mergeWith(vehicules);
	}
	private function checkEtatVehicules()
	{
		foreach (vehicule in this.vehiculesPool)
		{
			this.checkCarburant(vehicule);
			this.checkPneus(vehicule);
		}
	}
	private function checkPneus(IVehicule vehicule)
	{
		if (vehicule.nbLaps() >= Vehicule.MAX_LAPS){
			vehicule.changerPneus();
		}

	}
	private function checkCarburant(IVehicule vehicule)
	{
		if (vehicule.reservoir.isSurReserve()){
			vehicule.faireLePlein();
		}
	}
}

Surveillance de l’usure des pneus et du ravitaillement

Plusieurs modèles de gestion s’offfent à nous :

1. Le jeu de course est chargé de vérifier l’état des pneus de chaque véhicule.
Les véhicule sont pas suffisamment autonomes. C’est lourdingue, le jeu déjà bien d’autres choses à faire que de jouer à la nounou. En plus ca confère au Jeu des responsabilités
qui ne sont pas les siennes. On viole le Single Responsability Principle.

2. Chaque véhicule est intelligent et gère lui même son état. On entre dans le principe
de la programmation événementielle. Le véhicule s’abonne à un évenement sous la forme d’un signal qui lui indique le nombre de tours effectué à chaque tour. Le véhicule compare le nombre de tours effectués avec le nombre de tours max préconisé par le fabricant de pneus. Et voila le véhicule embarque de l’intelligence métier.
Le jeu envoie un signal : les abonnés savent ce qu’il ont à faire en fonction.
Chacun gère son job. Les interactions entre objets sont réduire au minimum indispensable.

3. Le véhicule ne compte plus sur le nombre de tours de piste. Il se fie à un capteur d’usure des pneus. Mais ca devient trop complexe. De plus les industriels savent très bien combien de tours de pistes un pneu peut supporter. Ainsi la solution 2 est la meilleure.

Developpez.com

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s