Design Patterns : Adapter

Aujourd’hui nous allons voir le design patterns Adapter. Ce pattern est de type structurel et permet à des classes incompatibles entre elles de pouvoir interagir. Une comparaison simple dans la vie réelle serait l’adaptateur secteur pour les prises de courant lorsque l’on voyage. En effet, nos alimentations ne fonctionnent qu’avec les prises de courant européennes, si l’on voyage aux USA par exemple un adaptateur spécifique est nécessaire, c’est exactement le principe du Design Pattern Adapter.

Contexte

Une entreprise de prévision météorologique dispose sur le territoire de sondes permettant de relever automatiquement la température et l’humidité. Cette entreprise vient de racheter un concurrent et souhaite ajouter les sondes de son ancien concurrent à son système d’information. Voici le besoin du client :

Nous venons de racheter Meteo Inc. Corp 2.0 et nous souhaitons intégrer le relevé de leurs sondes à notre système d’information, malheureusement nos deux systèmes sont totalement incompatibles, ils travaillent en degrés Fahrenheit et nous en degrés Celsius. De plus, nous ne pouvons pas nous permettre de modifier nos propres classes ni les leurs.

Analyse

La DSI de notre client nous fournit les classes qu’ils utilisent à l’heure actuelle, une interface ISonde décrivant le contrat que doivent implémenter les sondes.

 public interface ISonde
    {
        double ObtenirTemperature();
        double ObtenirTauxHumidite();
    }

De plus et pour information le client nous fournit également la classe SondeR58 qui est une des implémentations de ISonde :

 public class SondeR58 : ISonde
    {
        public double ObtenirTemperature()
        {
            Console.WriteLine("Sonde R58 : Demande de température");
            // On utilise Random pour simuler une température aléatoire
            return new Random().NextDouble()*40;
        }

        public double ObtenirTauxHumidite()
        {
            Console.WriteLine("Sonde R58 : Demande de taux d'humidité");
            return new Random().NextDouble() * 100;
        }
    }

Maintenant le client nous montre la classe SondeMeteoIncCorp qui correspond aux sondes manipulées par l’entreprise qu’il vient de racheter.

   public class SondeMeteoIncCorp
    {
        public int GetCurrentTemperature()
        {
            Console.WriteLine("Current temperature retrieving...");
            // La température est exprimée en degré Fahrenheit
            return new Random().Next(-100, 212);
        }

        public double GetCurrentHumidity()
        {
            Console.WriteLine("Current humidity retrieving...");
            return new Random().NextDouble() * 100;
        }
    }

Quelles sont les différences ?

  • Le nom des méthodes est différent.
  • GetCurrentTemperature renvoit un entier alors que nous attendons un décimal.
  • Les degrés sont en Fahrenheit et non en Celsius.

Il nous faut désormais écrire un adaptateur pour permettre à SondeMeteoIncCorp de fonctionner comme une implémentation de ISonde. Nous écrivons donc la classe SondeMeteoIncCorpAdapter qui implémente l’interface ISonde. Voici le code :

 public class SondeMeteoIncCorpAdapter : ISonde
    {
        SondeMeteoIncCorp sonde;

        public SondeMeteoIncCorpAdapter(SondeMeteoIncCorp sonde)
        {
            this.sonde = sonde;
        }

        public double ObtenirTemperature()
        {
            double resultFahrenheit = (double)this.sonde.GetCurrentTemperature();
            // On doit transformer les Fahrenheit en Celsius
            double resultCelsius = (5 * resultFahrenheit - 5 * 32) / 9;

            return resultCelsius;
        }

        public double ObtenirTauxHumidite()
        {
            // Ici seul un re routage vers la bonne méthode est nécessaire.
            return this.sonde.GetCurrentHumidity();
        }
    }

Il ne reste maintenant plus qu’à tester tout ça dans notre méthode main :

  static void Main(string[] args)
        {
            IList Sondes = new List();

            SondeMeteoIncCorp sonde1 = new SondeMeteoIncCorp();
            SondeMeteoIncCorp sonde2 = new SondeMeteoIncCorp();

            Sondes.Add(new SondeR58());
            Sondes.Add(new SondeMeteoIncCorpAdapter(sonde1));
            Sondes.Add(new SondeMeteoIncCorpAdapter(sonde2));

            Sondes.ToList().ForEach(el =>
            {
                Console.WriteLine("Température : " + el.ObtenirTemperature());
                Console.WriteLine("Humidité : " + el.ObtenirTauxHumidite());
            });

            Console.ReadLine();

        }

Et voilà ! Désormais notre client peut intégrer les sondes du concurrent qu’il vient de racheter dans son SI et ce sans même avoir eu à modifier le code de son système ni celui du système concurrent.

Définition du Pattern Adapter:

Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.

Diagramme de classe :

Diagramme de class Design Patterns Adapter

Dans notre exemple on peut relier les rôles à nos classes comme ceci :

Target : ISonde

Adapter : SondeMeteoIncCorpAdapter

Adaptee : SondeMeteoIncCorp

Leave a Reply