Saltar al contenido

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.

ProxyFacade
A cuántas clases tapaA una solaA varias, a un subsistema entero
Interfaz que ofreceLa misma que la clase realUna nueva, de más alto nivel
Para qué estáControlar el accesoDesacoplar 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.

java
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 herenciaPor asociación
Cómo llega al adaptadoLo heredaLo guarda como atributo
Cuántos adaptados admiteUno, y no puede ser una interfazLos que hagan falta
Se puede cambiar en calienteNoSí, 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.

java
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