Design Patterns - Abstract Factory

Propósito

El patrón Adapter convierte la interfaz de una clase en otra, que es la que esperan los clientes. Permite que clases con interfaces incompatibles trabajen juntas.

Problema

El problema que resuelve es la incompatibilidad de interfaces. Surge cuando queremos usar una clase existente, pero su interfaz no se corresponde con la que necesitamos.

Solución

La solución que propone el Adapter es:

  • Creación de un intermediario (Adapter): Implementa la interfaz que se requiere y envuelve la clase existente, traduciendo las llamadas a su interfaz.
  • Compatibilidad sin cambios en el código existente: Permite que el código existente funcione mediante nuevas interfaces, sin necesidad de modificarlo.

Estructura

adapter

Participantes

  • Target: Define la interfaz específica que usa el cliente, la que espera y entiende.
  • Adapter: Implementa la interfaz Target y encapsula una instancia de Adaptee. Adapta la interfaz de Adaptee a la interfaz Target.
  • Adaptee: La clase existente que necesita ser adaptada.
  • Client: Usa la interfaz Target.

Cuándo Usarlo

Este patrón es recomendable cuando:

  • queremos usar una clase existente cuya interfaz no se ajusta a la que necesitamos.
  • debemos crear una clase reutilizable que coopera con clases no relacionadas o imprevistas, es decir, clases que no necesariamente tienen interfaces compatibles.

Ventajas

Reutilización de Código Permite reutilizar código existente, incluso si sus interfaces no coinciden con las que se necesitan.

Separación de Código: El código de la aplicación se mantiene separado del código del adaptador, mejorando así la modularidad y separación de responsabilidades.

Flexibilidad: Se pueden agregar o eliminar adaptadores sin alterar el código existente.

Desventajas

Introduce complejidad: Agrega clases y objetos, lo que puede complicar el diseño.

Rendimiento: Es un caso para sistemas muy críticos, puede haber una pequeña baja en el rendimiento por la capa adicional de abstracción.

Ejemplo: Invocar una API externa

Debemos invocar un servicio externo que retorna el detalle de los impuestos a calculados en el mes, la respuesta viene en formato XML. Eso lo debemos enviar al sistema interno de contabilidad, pero en formato JSON.

Problema

El problema es que hay una incompatibilidad en la comunicación, por un lado recibimos un XML y por el otro debemos enviar un JSON. Necesitamos hacer que dos sistemas incompatibles trabajen juntos.

Solución planteada

Implementamos un Adapter, vamos a cambiar el formato de los datos de XML a JSON, para enviarlos al servicio interno de contabilidad.

Tenemos la clase XMLDataProvider, que contiene la respuesta que nos da el proveedor, los datos están en formato XML. Definimos la interfaz JSONDataProvider, lo que el cliente espera usar, datos en formato JSON, y finalmente creamos el Adapter, la clase XMLToJSONAdapter, que hace la conversión de XML a JSON.

adapter

Código Java

Codificamos en Java lo que preparamos en el diagrama.

Tenemos la clase XMLDataProvider, que se comunica con el sistema externo y retorna los datos en XML:


    public class XMLDataProvider {
        public String getXMLData() {
            //retorna el resultado de la invocación a la API al sistema externo
            return "<result>"
                    + "<month>March</month>"
                    + "<total>100</total>"
                    + "</result>";
        }
    }
              

Definimos la interfaz JSONDataProvider:


    public interface JSONDataProvider {
        String getJSONData();
    }
              

Creamos el Adapter, que realiza la conversión de XML a JSON:


    public class XMLToJSONAdapter implements JSONDataProvider {
        private XMLDataProvider xmlDataProvider;
    
        public XMLToJSONAdapter(XMLDataProvider xmlDataProvider) {
            this.xmlDataProvider = xmlDataProvider;
        }
    
        public String getJSONData() {
            String xmlStr = xmlDataProvider.getXMLData();
            //Lógica para convertir XML a JSON
            ...
            //Retorna el String en formato JSON
            return "{'result': {'month': 'March', 'total': 100}}";
        }
    }
              

El código cliente, obtiene los datos del proveedor e invoca al servicio interno de contabilidad pasandole los datos en formato JSON:


    class Client {
        public static void main(String[] args) {
            XMLDataProvider xmlProvider = new XMLDataProvider();
            JSONDataProvider dataProvider = new XMLToJSONAdapter(xmlProvider);
            //Llamamos al servicio interno que recibe el JSON
            sendTaxReport(dataProvider.getJSONData());
        }
    }
              

Mapeo (del ejemplo a Participantes)

Los participantes que vimos antes son: Adaptee, Target, Adapter, Client:

  • XMLDataProvider (Adaptee): Esta clase simula un servicio externo que provee datos en formato XML. Podría estar haciendo una llamada a un servicio web o leyendo de un archivo XML.
  • JSONDataProvider (Target): Define la interfaz para el formato de datos esperado por el sistema de contabilidad, en este caso, JSON.
  • XMLToJSONAdapter (Adapter): Implementa JSONDataProvider y usa XMLDataProvider para obtener datos en formato XML, que luego convierte a JSON.
  • Client (Client): Obtiene datos en JSON del XMLToJSONAdapter,que internamente realiza la conversión de XML a JSON..

Conclusiones

Este es un caso clásico y efectivo del patrón Adapter, que facilitó la integración de dos sistemas con formatos de datos incompatibles, un problema muy común en la integración de servicios y sistemas.

Patrones relacionados

  • Bridge

Tiene una estructura similar pero un propósito diferente: separar la interfaz de su implementación, mientras que el Adapter está pensado para cambiar la interfaz de un objeto existente.

  • Decorator

Decora otro objeto sin cambiar su interfaz, es más transparente que un Adapter.

  • Proxy

Define un representante o sustituto de un objeto sin cambiar su interfaz.