Patrones estructurales
Los patrones que colocan una clase delante de otra o reorganizan la forma de una estructura de clases: Proxy, Facade, Adapter, Composite y Flyweight.
Proxy
El Proxy pone una clase por delante de otra para controlar el acceso al objeto original. Sirve para interceptar la llamada y separar responsabilidades: el objeto real se ocupa de la lógica de negocio y el proxy de todo lo demás, que suele ser seguridad, registro de actividad, caché o carga diferida.
Su relación con SOLID es directa: gracias al proxy, la clase real conserva una sola responsabilidad y se le añade comportamiento sin modificarla, que es el principio de abierto y cerrado.
Dos rasgos lo caracterizan. El proxy va siempre por delante de la clase real, nunca detrás. Y hay un proxy por cada tipo concreto al que protege: es una capa de aislamiento hecha a medida de una sola clase.
Para que el proxy sea transparente, él y la clase real implementan la misma interfaz. El cliente cree que habla con el objeto de verdad y no se entera de que hay un intermediario.
Para el examen
Qué hace: controla el acceso a UNA clase interceptando las llamadas
Usos: seguridad, caché y carga diferida
Por qué es transparente: comparte interfaz con la clase real
Facade
La fachada proporciona un servicio intermedio de alto nivel por delante de un subsistema entero. Es una clase con pocos métodos, gruesos, que por dentro se apoya en muchas clases mediante agregación.
Su objetivo es minimizar el acoplamiento entre subsistemas: las clases de un subsistema que necesitan funciones de otro pasan a depender solo de la fachada. Y no se limita a centralizar las peticiones, también ofrece una interfaz de más alto nivel que la original, expresada en términos del negocio y no de las clases internas.
La ventaja de mantenimiento es evidente: si cambia el proceso de negocio, se cambia la implementación de la fachada y los clientes no se enteran.
| Proxy | Facade | |
|---|---|---|
| A cuántas clases tapa | A una sola | A varias, a un subsistema entero |
| Interfaz que ofrece | La misma que la clase real | Una nueva, de más alto nivel |
| Para qué está | Controlar el acceso | Desacoplar subsistemas y simplificar |
El Proxy es para una sola clase y respeta su interfaz; la Facade tapa a varias y crea una interfaz nueva. Esa es la diferencia que se pregunta y con la que más se falla.
Para el examen
Qué ofrece: pocos métodos de alto nivel delante de un subsistema entero
Para qué: desacoplar al cliente del subsistema
Frente al Proxy: tapa a VARIAS clases y ofrece una interfaz nueva, no la original
Adapter
El Adapter, también llamado envoltorio o wrapper, es un intermediario que le ofrece al cliente una interfaz más cómoda que la que ya está desarrollada. El problema que resuelve es de incompatibilidad: existe un servicio que hace justo lo que hace falta, pero se usa de una forma que al cliente no le sirve, o que directamente no sabe manejar.
El adaptador expone la operación en el formato que el cliente entiende y, por dentro, traduce la llamada al servicio ya existente. En la terminología del patrón, el cliente habla con el objetivo, el adaptador implementa ese objetivo y el adaptado es la clase que ya estaba escrita.
Adapter y Facade se parecen en que los dos envuelven, pero la intención es distinta: el Adapter existe porque una interfaz no encaja y hay que traducirla; la Facade existe porque un subsistema es complicado y hay que simplificarlo.
Para el examen
Otro nombre: wrapper
Qué hace: traduce una interfaz incompatible a la que el cliente sabe usar
Objetivo (target): lo que ve el cliente
Adaptado (adaptee): la clase ya escrita
Composite
El Composite ataca las estructuras de objetos poco flexibles: aquellas en las que los contenedores están cableados uno dentro de otro y añadir un nivel nuevo obliga a tocar medio sistema.
La solución es tratar igual a los elementos simples y a los que contienen a otros. Se declara un tipo común para todos y se admite que un elemento compuesto contenga elementos de ese mismo tipo, con lo que la estructura se vuelve recursiva y admite tantos niveles como haga falta.
El beneficio es de mantenimiento: si el día de mañana aparece un contenedor nuevo, se introduce minimizando el impacto, porque el resto del código sigue viendo el mismo tipo de siempre.
La firma del Composite es que el cliente no distingue entre una hoja y una rama: le envía el mismo mensaje a las dos. Si hay que preguntar si el elemento es simple o compuesto, el patrón está mal aplicado.
Para el examen
Qué consigue: tratar igual a hojas y contenedores con un tipo común
Propiedad de la estructura: se vuelve recursiva, con tantos niveles como haga falta
Flyweight
El Flyweight, o peso ligero, sirve para eliminar redundancia. Cuando hay miles de objetos que repiten la misma información, esa parte común se saca a clases que no se replican sino que se comparten entre todos.
El efecto es doble: disminuye el uso de recursos, que es su motivo principal, y protege ante cambios, porque al quedar la información centralizada en un solo sitio se modifica una vez y vale para todos.
Es el único patrón del catálogo cuyo objetivo no es el mantenimiento, sino el ahorro de recursos del sistema. Ese matiz es justo lo que se pregunta de él.
En la plataforma, el enunciado de una pregunta se comparte entre los miles de intentos que la han respondido: cada intento guarda solo lo suyo, que es la opción marcada y si acertó, y apunta al mismo objeto pregunta que todos los demás.
Para el examen
Qué hace: comparte la información común entre miles de objetos en lugar de repetirla
Su singularidad: es el único patrón GoF orientado a ahorrar recursos, no al mantenimiento
Proxy y Facade: cómo se implementan
En el Proxy, la pieza que lo hace funcionar es la interfaz. El proxy y la clase real la implementan los dos, así que el cliente puede recibir cualquiera de los dos objetos sin cambiar una línea. Esa interfaz no lleva código, solo el esqueleto, y es la que permite el polimorfismo.
El proxy no hace lógica de negocio. Su método comprueba lo que tenga que comprobar y, si procede, delega en el objeto real. Conviene distinguirlo del filtro de una aplicación web: el filtro es genérico y se aplica a cualquier petición que pase por él, mientras que el proxy es específico de una clase concreta.
public interface Corrector {
Resultado corregir(Intento intento);
}
public class CorrectorReal implements Corrector { /* lógica de negocio */ }
public class CorrectorConPermisos implements Corrector {
private final Corrector real;
private final Sesion sesion;
@Override
public Resultado corregir(Intento intento) {
if (!sesion.puedeCorregir(intento)) {
throw new AccesoDenegado();
}
return real.corregir(intento); // el trabajo lo hace el real
}
}En la Facade el mecanismo es otro. Si un cliente necesitaba encadenar tres llamadas a tres clases distintas de otro subsistema, la fachada le ofrece un único método que por dentro hace esas tres llamadas. El cliente pasa de conocer tres clases a conocer una.
Sobre cómo se obtiene el objeto fachada hay una combinación habitual, que conviene entender como decisión de diseño y no como parte obligatoria del patrón: resolverlo con un Singleton, porque la fachada se crea una sola vez, y añadir después un Factory Method si se prevé que vaya a haber versiones distintas de la fachada para distintos clientes.
Para el examen
Lo que el proxy NO hace: lógica de negocio: comprueba y delega en el objeto real
Frente al filtro web: el filtro es genérico; el proxy es de una clase concreta
Adapter y Composite: cómo se implementan
El Adapter admite dos montajes. Por herencia, el adaptador hereda de la clase ya desarrollada y expone encima el método que el cliente sabe usar. Por asociación, el adaptador guarda una referencia a la clase adaptada y le delega el trabajo.
| Por herencia | Por asociación | |
|---|---|---|
| Cómo llega al adaptado | Lo hereda | Lo guarda como atributo |
| Cuántos adaptados admite | Uno, y no puede ser una interfaz | Los que hagan falta |
| Se puede cambiar en caliente | No | Sí, basta con cambiar la referencia |
La versión por asociación es la preferible casi siempre, por lo mismo que se prefiere la composición a la herencia: no consume la única superclase disponible y no ata el adaptador a una jerarquía.
En el Composite, la clave está en la relación de agregación del elemento compuesto consigo mismo: un compuesto agrega N elementos del tipo común, y como él también es de ese tipo, puede contener otros compuestos. De ahí salen todas las combinaciones posibles y una estructura con tantos eslabones como se quiera.
public interface NodoDelTemario {
int minutosDeEstudio();
}
public class Subtema implements NodoDelTemario { // hoja
public int minutosDeEstudio() { return 20; }
}
public class Contenedor implements NodoDelTemario { // compuesto
private final List<NodoDelTemario> hijos = new ArrayList<>();
public int minutosDeEstudio() {
int total = 0;
for (NodoDelTemario hijo : hijos) {
total += hijo.minutosDeEstudio(); // sin preguntar qué es
}
return total;
}
}
// Bloque, tema y cualquier agrupación futura son Contenedor:
// añadir un nivel nuevo no obliga a tocar este código.Para el examen
Los dos montajes del Adapter: por herencia o por asociación; preferible la asociación
De dónde sale la recursividad del Composite: el compuesto agrega elementos de su mismo tipo común