Introduction▲
Les magiciens sont des personnes extraordinaires. Ils arrivent à faire des choses qui nous paraissaient impossibles pour nous, simples spectateurs. Mettre en lévitation un éléphant rose ne leur pose aucun souci. Vous êtes jaloux ? Ne vous en faites pas, vous allez avoir l'occasion de vous rattraper. Nous aussi nous allons apprendre à créer l'illusion de la réalité : faire passer une interface pour quelque chose qu'elle n'est pas. Cela permettra à des classes de collaborer alors qu'elles n'auraient pas pu le faire du fait d'interfaces incompatibles.
I. Problématique▲
Pour mieux comprendre l'intérêt du pattern Adaptateur, nous allons commencer par un petit exemple.
La société Carrouf dont vous êtes le directeur souhaite produire un chargeur universel de batteries pour téléphone portable pouvant délivrer jusqu'à 10 volts en sortie. Les ingénieurs de Carrouf ont produit un premier prototype de chargeur:
public
class
Chargeur
{
// le portable branché sur le chargeur
private
IChargeable telephone;
// le voltage en sortie du chargeur
private
const
int
voltage =
10
;
// branchement d'un portable pour le charger
public
void
brancherPortable
(
IChargeable portable)
{
Console.
WriteLine
(
"branchement d'un portable"
);
this
.
telephone =
portable;
this
.
telephone.
Recharger
(
voltage);
}
}
Celui-ci est capable de se brancher sur tout téléphone implémentant l'interface IChargeable via la méthode brancherPortable et de le recharger en produisant 10 volts en sortie.
Voici le code de l'interface Ichargeable :
public
interface
IChargeable
{
///
<
summary
>
/// méthode appelée si l'on veut recharger le portable
///
<
/summary
>
///
<
param
name
=
"volts"
>
voltage en sortie du chargeur
<
/param
>
void
Recharger
(
int
volts);
}
Vous avez aussi créé plusieurs téléphones portables de test et implémentant l'interface IChargeabledont voici un exemple :
public
class
PortableDeTest :
IChargeable
{
public
void
Recharger
(
int
volts)
{
Console.
WriteLine
(
"Portable de test en charge"
);
Console.
WriteLine
(
"voltage: {0}"
,
volts.
ToString
(
));
}
}
Les tests effectués en laboratoire sont concluants. Le chargeur fonctionne à la perfection avec tous les téléphones implémentant l'interface IChargeable.
Cependant les problèmes apparaissent lors de tests avec les téléphones portables actuellement sur le marché (par exemple avec la marque SonneEricSonne ou encore SamSaoule). En effet, chaque fabricant de téléphones propose son propre système de chargement. Les interfaces sont non seulement différentes, mais le voltage utilisé varie aussi d'un téléphone à l'autre (allant de 10 à 5 volts). Le chargeur universel ne l'est alors plus du tout. Il devient donc impossible d'utiliser notre chargeur avec les téléphones existants. Malheur, la faillite sonne à la porte de la société !
Vous avez donc convoqué les différents responsables pour une réunion de crise afin de trouver une solution au problème.
Nouveau venu dans la boite, Thomas Leblond propose une première solution : « Nous avons besoin que les téléphones implémentent l'interface IChargeable, mais nous ne pouvons pas les modifier nous-mêmes. Nous n'en possédons pas le code source. Il nous suffit donc de proposer notre interface IChargeable aux différents constructeurs et de leur demander de l'implémenter dans leurs téléphones. »
« Et puis quoi encore ? » lui rétorqua le chef de projet Didier Valse. « Les constructeurs ont leur propre système et n'ont pas l'intention de le changer. Tu imagines s'ils devaient prendre en compte les demandes des centaines de constructeurs de chargeurs et autres périphériques, ils ne s'en sortiraient pas. Et puis que faire des téléphones déjà vendus ? Ils n'implémenteraient pas IChargeable et ne pourraient donc pas être utilisés par notre chargeur ! »
« Ce n'est donc pas au fabricant du portable à modifier son code, mais à nous de nous y adapter. Adapter, voilà un mot intéressant. Ce qu'il nous faudrait c'est en fait quelque chose qui peut adapter le code des téléphones pour que notre chargeur puisse l'utiliser, un adaptateur en somme. »
« D'accord, mais comme faire ? »demanda Thomas.
« Prenons un exemple courant (c'est le cas de le dire) : les prises électriques. Si vous voyagez en Angleterre par exemple et tentez de brancher une fiche électrique française sur la prise murale anglaise, vous risquez d'avoir quelques problèmes, les interfaces n'étant pas compatibles. Alors que faites-vous ? Vous descendez au magasin de bricolage du quartier pour acheter un adaptateur. Un côté de l'adaptateur s'adapte à l'interface de la prise murale anglaise tandis que l'autre s'adapte à la fiche française. Au milieu s'opèrent des transformations afin que les deux côtés puissent communiquer. »
« Nous allons donc créer un adaptateur pour chaque portable que l'on voudra utiliser avec notre chargeur. Nous aurons donc un chargeur unique dont le code ne changera pas auquel nous pourrons brancher n'importe quel téléphone en utilisant le bon adaptateur. »
Bien joué Didier.
Bon, voyons maintenant comment cela va se traduire au niveau du code.
II. Implémentation en C#▲
Rappelons qu'un design pattern est une méthode de conception et est donc indépendant (en général) du langage de programmation. Nous allons coder ici en C#, mais la méthode est bien sûr tout à fait adaptable à d'autres langages.
Occupons-nous d'abord du côté s'interfaçant avec le chargeur. Ce dernier ne manipule que des objets de type IChargeable. L'adaptateur va devoir implémenter l'interface IChargeable afin que le chargeur puisse l'utiliser.
Pour le côté s'adaptant au portable, l'adaptateur devra posséder une référence sur le portable qu'il est censé adapter. C'est en effet l'adaptateur qui manipulera directement l'instance du portable et non pas le chargeur.
II-A. Les portables à adapter▲
Étudions deux exemples de téléphones portables : SonneEricSonne et SamSaoule. Voici les classes les représentants :
public
class
PortableSonneEricSonne
{
// ne se recharge qu'avec du 10 volts
public
void
ChargerBatteries
(
int
volts)
{
Console.
WriteLine
(
"Portable SonneEricSonne en charge"
);
Console.
WriteLine
(
"voltage: {0}"
,
volts.
ToString
(
));
}
}
public
class
PortableSamSaoule
{
// ne se recharge qu'avec du 5 volts
public
void
ChargerPortable
(
int
volts)
{
Console.
WriteLine
(
"Portable SamSaoule en charge"
);
Console.
WriteLine
(
"voltage: {0}"
,
volts.
ToString
(
));
}
}
Les portables possèdent des méthodes différentes pour le rechargement. De plus le voltage à utiliser diffère. Les adaptateurs vont devoir prendre tout cela en considération.
II-B. Les adaptateurs▲
Nous allons donc construire un adaptateur pour chaque téléphone.
Voici tout d'abord l'adaptateur pour téléphones SonneEricSonne :
public
class
AdaptateurSonneEricSonne :
IChargeable
{
// référence sur le portable adapté
private
PortableSonneEricSonne telephone;
public
AdaptateurSonneEricSonne
(
PortableSonneEricSonne portable)
{
this
.
telephone =
portable;
}
public
void
Recharger
(
int
volts)
{
this
.
telephone.
ChargerBatteries
(
volts);
}
}
L'adaptateur implémente l'interface IChargeable afin de pouvoir être manipulé par le chargeur. Nous utilisons le constructeur de l'adaptateur afin de lui donner une référence sur le téléphone qu'il est censé adapter. Nous gardons cette référence via un champ privé. Il ne reste plus qu'à saisir le code de la méthode Recharger. Le chargeur appelle cette méthode en passant en paramètre le voltage. L'adaptateur se charge d'envoyer cet appel au téléphone dont il s'occupe (lui seul sait comment manipuler un téléphone SonneEricSonne).
Passons maintenant à l'adaptateur pour téléphones SamSaoule.
public
class
AdaptateurSamSaoule :
IChargeable
{
// référence sur le portable adapté
private
PortableSamSaoule telephone;
public
AdaptateurSamSaoule
(
PortableSamSaoule portable)
{
this
.
telephone =
portable;
}
//le portable SamSaoule n'a besoin que de 5 volts
public
void
Recharger
(
int
volts)
{
//on modifie le voltage
int
nouveauVoltage =
volts >
5
?
5
:
volts ;
this
.
telephone.
ChargerPortable
(
nouveauVoltage);
}
}
Rien d'extraordinaire par rapport à l'adaptateur précédent. La nouveauté se situe au niveau de la méthode Recharger. Ici l'adaptateur ne se contente pas simplement de traduire les appels du chargeur vers le portable. Vous vous souvenez sans doute que les portables SamSaoule n'ont besoin que de 5 volts. Et bien c'est l'adaptateur qui va se charger d'effectuer la transformation. L'adaptateur encapsule toutes les différences entre le chargeur et le portable.
Le chargeur n'a donc aucun lien avec le portable, seul l'adaptateur connait les spécificités de ce dernier. D'ailleurs, si vous réfléchissez, vous verrez qu'en fait le chargeur ne sait même pas que c'est un téléphone portable qu'il est en train de charger. La société Carrouf l'a bien compris. Elle s'est donc empressée de créer des adaptateurs pour toute sorte d'appareils (piles rechargeables, baladeurs MP3, etc.) tout en gardant le même chargeur de départ.
Maintenant que les adaptateurs sont créés, il ne reste plus qu'à les tester. Voici donc un petit programme en mode console :
static
void
Main
(
string
[]
args)
{
//on crée le chargeur
Chargeur chargeur =
new
Chargeur
(
);
/******************** Portable SonneEricSonne***********************/
//on crée le portable et son adaptateur
PortableSonneEricSonne portableSonne =
new
PortableSonneEricSonne
(
);
AdaptateurSonneEricSonne adapateurSonne =
new
AdaptateurSonneEricSonne
(
portableSonne);
//on donne le portable à charger, mais en utilisant son adaptateur
chargeur.
brancherPortable
(
adapateurSonne);
Console.
WriteLine
(
);
/********************* Portable SamSaoule***************************/
//on crée le portable et son adaptateur
PortableSamSaoule portableSam =
new
PortableSamSaoule
(
);
AdaptateurSamSaoule adapateurSam =
new
AdaptateurSamSaoule
(
portableSam);
//on donne le portable à charger, mais en utilisant son adaptateur
chargeur.
brancherPortable
(
adapateurSam);
Console.
ReadLine
(
);
}
Nous créons tout d'abord un nouvel objet chargeur. Puis nous instancions un portable de type PortableSonneEricSonne que nous passons en paramètre d'un adaptateur de type AdaptateurSonneEricSonne. Enfin nous branchons le portable (par l'intermédiaire de l'adaptateur) au chargeur. Nous effectuons la même chose pour un téléphone de marque SamSaoule.
Voici la sortie produite:
Comme vous le voyez, les adaptateurs ont parfaitement fonctionné. Nous avons pu recharger des téléphones de marques différentes grâce aux adaptateurs. De plus, l'adaptateur pour téléphone SamSaoule a bien adapté le voltage en le ramenant à 5.
III. Le pattern Adaptateur : résumé▲
Le pattern Adaptateur permet de transformer une interface d'une classe en une autre conforme à celle attendue par le client. L'adaptateur permet à des classes de collaborer alors qu'elles n'auraient pas pu le faire du fait d'interfaces incompatibles.
Bien que l'on utilise généralement le pattern Adaptateur pour convertir une interface en une autre, il peut arriver qu'un adaptateur ait besoin de plusieurs adaptés afin d'implémenter complètement l'interface cible.
IV. Conclusion▲
Ici s'achève notre découverte du pattern Adaptateur. À partir de maintenant lorsque vous devrez utiliser une classe existante et que son interface ne correspond pas à celle que vous attendez, vous saurez quoi faire.
V. Liens▲
Le pattern singleton par Ronald VASSEUR
La rubrique Conception de developpez.com
Les critiques de livres sur les design patterns de developpez.com
Remerciements▲
J'adresse ici tous mes remerciements à l'équipe de rédaction de « developpez.com » pour le temps qu'ils ont bien voulu passer à la correction et à l'amélioration de cet article.
Contact▲
Si vous constatez une erreur dans le tutoriel, dans les sources, dans la programmation ou pour toutes informations, n'hésitez pas à me contacter par le forum.