Facade, Proxy, Adapter y Decorator

En el campo de la ingeniería del software, definimos cómo patrón de diseño aquella técnica reutilizable de resolución de un problema común en el desarrollo de software, cuya efectividad ha sido comprobada mediante la resolución de problemas similares en ocasiones anteriores.

El conjunto de los patrones de diseño se suele clasificar en tres grupos:
de creación, estructurales y de comportamiento.
Sin embargo, a pesar de que esta primera distinción suele quedar clara, hay varios patrones que suelen ser comúnmente confundidos. Y por este motivo, me he propuesto aprovechar la ocasión para clarificar cuatro de los patrones estructurales que se suelen usar más habitualmente.

FACADE

El primero de los patrones de diseño que vamos a repasar en este artículo será el patrón Facade, cuya finalidad es la de proporcionar una interfaz más simple con la que interactuar en el uso de un conjunto de subsistemas más complejos.

Un ejemplo en el mundo real sería el caso del SAC (Servicio de Atención al Cliente) de cualquier empresa. Si nos paramos a pensar, por debajo de este punto de entrada (a priori simple y agradable para el usuario) se encuentran complejos departamentos (facturación, envíos, etc) con los que la interacción es más ardua y complicada. Imaginad que uno de nuestros clientes tiene que contactar con los diferentes departamentos de la empresa para resolver su problema. Evidentemente es algo que no tendría sentido alguno.

Por lo tanto, podemos decir que éste patrón lo que busca es proporcionar una nueva interfaz más simple, a diferencia del Adapter, que busca conectar interfaces ya existentes. Además, el objetivo en este caso no es añadir nueva funcionalidad (a diferencia de los patrones Mediator y Decorator) ni proporcionar la información de los subsistemas internos (Flyweight).

Los Facade suelen ser un caso de patrón Singleton, pues es habitual nesitar una única instancia de éste objeto resolutivo.

A continuación os dejo el enlace a la documentación de Laravel, framework PHP que hace muy buen uso de este patrón, demostrando lo que realmente significa eso de proporcionar una interfaz sencilla para el control de varios subsistemas más complejos:

https://laravel.com/docs/5.6/facades

PROXY

El siguiente de los patrones es el Proxy, cuyo objetivo consiste en proporcionar un nivel extra de indirección en la representación un objeto, proporcionando un wrapper para proteger el objeto real de una complejidad indebida.

Un ejemplo en el mundo real sería un cheque bancario, el cuál nos permite hacer transacciones económicas (por ejemplo realizar pagos) sin la necesidad de interactuar realmente con una cuenta bancaria y sus fondos hasta que esto no sea estrictamente necesario.

Esto nos permite, tanto ahorrarnos operaciones costosas (cómo la creación de un objeto) mientras éstas no sean imprescindibles, cómo añadir condiciones a las operaciones realizadas sobre el objeto en cuestión, por ejemplo, impidiendo su modificación.

De hecho, este patrón sirve cómo buen ejemplo de lo que es la composición y de cuáles son sus beneficios, ya que nos permite interactuar con una entidad mientras que nos abstraemos de su implementación, la cuál no tenemos porqué conocer (recordad el caso del cheque bancario).

Es importante recordar qué, a diferencia del patrón Adapter (a continuación), el patrón Proxy suele proporcionar la misma interfaz que su sujeto.

ADAPTER

Uno de los principales objetivos de un buen programador es hacer posible la reutilización de los componentes que éste desarrolla, sin embargo, la búsqueda de este objetivo suele ir acompañada de problemas y complejidades accidentales que, mediante este proceso, se añaden a la complejidad intrínseca del propio problema que el programador busca resolver.

Uno de los problemas más comunes en estos casos es la incompatibilidad entre los componentes desarrollados. Este problema habitualmente surge de un planteamiento diferente de unas necesidades similares o incluso iguales, y se acaba traduciendo en contratos (firmas de los métodos de una clase / interfaz o un conjunto de ellas) incompatibles entre sí (dificultando la reutilización).

Aquí es dónde entra en juego el patrón Adapter, cuyo objetivo es precisamente ese: la adaptación de dos interfaces distintas para que puedan funcionar conjuntamente entre ellas.

Un ejemplo en el mundo real sería un adaptador de VGA a HDMI  o incluso los adaptadores entre los diferentes tipos de HDMI (normal, mini y micro).

Por lo tanto, en este caso tampoco queremos añadir nueva funcionalidad (es la que ya teníamos originalmente: VGA & HDMI) sino simplemente queremos hacer posible el funcionamiento conjunto de los diferentes componentes.

DECORATOR

Finalmente, el patrón Decorator nos permite añadir responsabilidad a nuestros objetos de forma dinámica sin la necesidad de hacer uso de la (maldita) herencia.

Un ejemplo en el mundo real (aunque quizás no es el ideal) sería la tapa de una publicación (libro, revista, etc). Por ejemplo, cuándo vamos a comprar un libro, podemos escoger entre tapa dura y tapa blanda, y este nivel de abstracción podría quedar totalmente aislado de la implementación interna (o sea, del libro en sí) y viceversa (el libro no tiene porqué saber en qué tipo de tapa va a ser vendido). Otro ejemplo habitual en el mundo del desarrollo es el Logger, componente al que se le suele inyectar una instancia de un objeto, de forma que ésta sigue funcionando cómo originalmente, pero además queda “decorada” para escribir logs por la salida estándar.

Cómo podemos deducir de los ejemplos que hemos puesto, ésta problemática podría ser resuelta mediante herencia, sobrescribiendo la función de la clase padre para añadirle la lógica de escritura de logs.
Sin embargo, esto seguramente nos forzaría a replicar dicha lógica para cada una de nuestras implementaciones, cuándo realmente la lógica de logging podría ser totalmente agnóstica a ello.

Por lo tanto, vemos que en este caso, a diferencia de los patrones anteriores, sí que tenemos la finalidad de añadir nueva funcionalidad a nuestros objetos.

CONCLUSIONES

Cómo hemos podido ver, este tipo de patrones son fácilmente confundibles, ya que en la mayoría de casos, tanto los objetivos (reutilización, simplificación, etc) como las técnicas aplicadas para conseguirlos (composición, abstracción mediante interfaces, etc) son mayoritariamente los mismos.

Sin embargo, ha quedado demostrado que, tanto la finalidad como el enfoque son diferentes en cada caso y que, por consiguiente, hemos podido identificar características de cada uno de ellos que los diferencian entre sí.