Patrones de diseño. Adapter Pattern

Introducción

El patrón Adapter (o adaptador), también conocido a veces como wrapper realiza la función, como su nombre indica de adaptar (o envolver) una determinada clase cambiando el interfaz de dicha clase y convirtiendolo en algo que se acerque más a nuestras necesidades.

En muchas ocasiones tenemos una clase que hace lo que necesitamos (o se aproxima mucho) y cumple todos los requisitos para que alguna otra clase la use pero, al no implementar un determinado interface o derivar de una determinada clase esto no es posible.

Problema

En un determinado punto del código se espera una instancia de un determinado interfaz o de una determinada clase. Deseamos utilizar una clase distinta que cumple todos los requisitos del interfaz o de la clase concreta que se espera en el código pero que, sin embargo, por las razones que sean no implementa dicho interfaz.

Estructura

Esta imagen (sacada de la wikipedia inglesa) describe bien el esquema general de un patron de diseño adapter:

ClassAdapter.png

Código de ejemplo

En ocasiones estamos utilizando una librería externa sobre la que no tenemos ningún control ni podemos realizar modificaciones. Es muy probable que nuestro código tenga definidos una serie de interfaces que, pese a ser equivalentes o representativos de los tipos de datos que contiene la librería, obviamente, no están implementados en esta.

Por ejemplo, supongamos que estamos realizando un sistema de gps (por poner un ejemplo) y estamos trabajando con dos librerías distintas; una librería GIS y una que nos proporciona la posición del GPS. Por supuesto cada una de ellas utiliza su propio formato para las localizaciones y nosotros además tenemos definido un interfaz IGeoPoint que se define como sigue:

public IGeoPoint
{
  double Longitud {get; set;}
  double Latitud {get; set;}
}

y en algún lugar tenemos una función que recibe un conjunto de IGeoPoint y realiza varias operaciones con ellos.

Tal y como está, las estructuras de datos que almacenan puntos en el GIS y en el GPS (llamemoslas GISPoint y GPSPoint) no implementan (por razones obvias) con nuestro interface sino que, por ejemplo, de la siguiente forma

public GISPoint
{
  double Long;
  double Lat;
}

public GPSPoint
{
  double angle;
  double dist;

  double GetLong(double origX, double OrigY);
  double GetLat(double origX, double origY);
  void SetLong(double long);
  void SetLat(double lat);
}

de forma que el primero es prácticamente igual pero tiene los nombres cambiados mientras que el segundo basa la posición del punto en una distancia y un angulo y permite obtener la longitud y la latitud mediante dos funciones a las que se pasa un punto de origen (este ejemplo es obviamente rebuscado puesto que NADIE implementaría los puntos de forma tan complicada).

Así pues, la funcionalidad está ahí pero nuestra función sigue sin poder utilizar directamente objetos de tipo GISPoint o GPSPoint porque no implementan adecuadamente el interfaz...

public AdpGISPoint : GISPoint, IPoint
{
  public double Longitud {
   get { return this.Long; }
   set { this.Long = value; }
  }

  public double Latitud {
   get { return this.Lat; }
   set { this.Lat = value; }
  }
}

public AdpGPSPoint : GPSPoint, IPoint
{
  public double Longitud {
    get { return this.GetLong(0,0); }
    set { this.SetLong(value); }
  }

  public double Latitud {
    get { return this.GetLat(0,0); }
    set { this.SetLat(value); }
  }
}

Ejemplos reales

Aunque no existe una situación clara y concisa donde pueda decirse esto es perfecto para un adapter pattern, en general los casos de adaptación de librerías relacionadas a interfaces existentes suele requerir el uso de adaptadores

4.66667
Average: 4.7 (6 votes)
Your rating: None