Saltar al contenido

Patrones creacionales

Los patrones que se ocupan de crear objetos cuando crearlos no es trivial: Singleton, Factory Method, Abstract Factory y Builder.

Singleton

El Singleton resuelve un problema muy concreto: que de una clase se genere un solo objeto, y que ese objeto lo compartan todos. Es la forma ordenada de tener un objeto global sin recurrir a variables globales.

La solución tiene dos piezas que van siempre juntas: el constructor se declara privado, para que nadie de fuera pueda instanciar la clase, y se ofrece un método estático que devuelve la instancia. Estático porque hay que poder llamarlo sin tener todavía ningún objeto.

Los casos de uso habituales son los objetos de configuración, los servlets y el origen de datos de una aplicación: cosas caras de construir, iguales para todo el mundo y que no tiene sentido duplicar.

Constructor privado y método estático de acceso. Si en un examen aparece un Singleton con el constructor público, está mal: sin constructor privado nada impide hacer un objeto nuevo por la vía normal.

Para el examen

  • Familia: creacional

  • Qué garantiza: una sola instancia compartida

  • Cómo: constructor privado y método estático de acceso

Singleton: implementación y seguridad entre hilos

java
public class CatalogoDeCursos {

    private static CatalogoDeCursos instancia;

    private CatalogoDeCursos() {
        // privado: nadie puede hacer new desde fuera
    }

    public static CatalogoDeCursos getInstancia() {
        if (instancia == null) {
            instancia = new CatalogoDeCursos();   // solo la primera vez
        }
        return instancia;
    }
}

// Uso: siempre se recibe el mismo objeto.
CatalogoDeCursos catalogo = CatalogoDeCursos.getInstancia();

Esa versión se llama inicialización perezosa: el objeto no se crea hasta que alguien lo pide por primera vez. Es la que suele aparecer en los apuntes y tiene un defecto serio.

El defecto es que no es segura entre hilos. Si dos hilos entran a la vez en el método y los dos encuentran el atributo todavía a nulo, los dos crean su objeto y uno pisa al otro: se acaban de fabricar dos instancias de una clase que prometía tener una. En un servidor web, que atiende peticiones en paralelo, esto no es un caso raro.

SoluciónCómo funcionaPrecio
Inicialización tempranaSe crea la instancia al cargar la clase, no al pedirlaSe construye aunque nadie la use
Método sincronizadoSe serializa la entrada al método de accesoPenaliza todas las llamadas, no solo la primera
Clase interna de soporteSe apoya en que la máquina virtual carga las clases una sola vez y de forma seguraNinguno relevante; es la opción recomendada

Para el examen

  • Problema del Singleton perezoso: sin proteger no es seguro entre hilos

  • Qué puede pasar: dos hilos crean dos instancias

  • Las tres salidas: inicialización temprana, método sincronizado o clase interna de soporte

Factory Method

El Factory Method se usa cuando hay que crear objetos de una familia con muchos tipos distintos. La complejidad no está en construir cada objeto, sino en la jerarquía: hay muchas clases hijas y el cliente no debería tener que saber cuál le toca en cada caso.

En lugar de que el cliente instancie la clase concreta, se le pide el objeto a un método fábrica. Ese método instancia por dentro la clase concreta que corresponda y la devuelve declarada con el tipo abstracto. Así el cliente solo conoce la abstracción y el patrón funciona apoyado en el polimorfismo.

La frase que resume el patrón es: creo lo concreto y devuelvo lo abstracto. Si se enuncia al revés, se pierde el sentido: el trabajo sucio de elegir la clase concreta es justo lo que la fábrica esconde.

Es también la aplicación directa de la inversión de dependencias: el código de negocio pasa a depender del tipo abstracto y deja de mencionar ninguna implementación concreta.

Para el examen

  • Qué hace: el método fábrica crea lo concreto y lo devuelve con el tipo abstracto

  • En qué se apoya: en el polimorfismo

  • Qué gana el cliente: nunca nombra la clase concreta

Factory Method: la jerarquía de creadores

La estructura del patrón tiene dos jerarquías en paralelo. Por un lado la de los productos: un tipo abstracto y sus implementaciones concretas. Por otro la de los creadores: una clase creadora abstracta que declara el método fábrica, y creadores concretos que deciden qué producto se fabrica.

Cuando hay que cambiar la política de creación, no se toca el cliente ni los productos: se sustituye el creador concreto. Ese es el motivo de que existan dos jerarquías en lugar de un simple condicional dentro de un método estático.

java
public interface Test {
    List<Pregunta> seleccionar(Alumno alumno);
}

public abstract class GeneradorDeTest {          // creador abstracto
    protected abstract Test crearTest();          // método fábrica

    public Test preparar(Alumno alumno) {
        Test test = crearTest();                  // no sabe cuál es
        registrarEnHistorial(alumno, test);
        return test;
    }
}

public class GeneradorDeRepaso extends GeneradorDeTest {
    @Override
    protected Test crearTest() {
        return new TestDeFalladas();              // concreto dentro,
    }                                             // abstracto fuera
}

Para el examen

  • Qué monta: dos jerarquías paralelas: productos y creadores

  • Cómo se cambia la política de creación: sustituyendo el creador concreto

  • Qué no se toca: ni el cliente ni los productos

Abstract Factory

El Abstract Factory ofrece una interfaz para crear familias de objetos relacionados entre sí sin especificar sus clases concretas. La palabra clave es familia: aquí no se crea un objeto suelto, sino un conjunto de piezas que tienen que ser compatibles entre ellas.

El montaje es doble. Primero se define una serie de productos, y cada producto tiene su propia interfaz abstracta, que es la que usará el cliente. Después se define una factoría abstracta con un método por producto, y sus implementaciones concretas. Cada implementación de la factoría se encarga de instanciar los productos de su familia.

Factory MethodAbstract Factory
Qué creaUn productoUna familia de productos relacionados
Cuántos métodos fábricaUnoUno por cada producto de la familia
Qué garantizaQue el cliente no conozca la clase concretaAdemás, que las piezas creadas sean compatibles entre sí

Los dos esconden la clase concreta, pero Abstract Factory añade una garantía que Factory Method no da: que no se puedan mezclar piezas de familias distintas.

Para el examen

  • Qué crea: familias de objetos compatibles entre sí

  • Su estructura: un método por producto de la familia

  • Garantía extra frente a Factory Method: que no se mezclan piezas de familias distintas

Builder

El Builder busca solución a otro problema distinto: crear un objeto muy complejo, compuesto por muchas partes. No hay aquí una jerarquía difícil, como en el Factory Method, sino un objeto con quince piezas que hay que ir montando en orden.

El ejemplo clásico está en el propio Java: construir en memoria el árbol de un documento XML. No se hace de una sentada con un constructor gigante, sino con un constructor de documentos que va creando nodos y colgándolos unos de otros.

Factory Method resuelve «no sé qué clase concreta necesito». Builder resuelve «sé qué clase necesito, pero montarla lleva muchos pasos». Es la distinción que se pregunta.

Para el examen

  • Cuándo se usa: para objetos complejos que se montan por partes y en orden

  • Ejemplo clásico: el árbol de un documento XML

Builder: director y constructores concretos

El patrón reparte el trabajo en tres papeles. El director conoce el orden de montaje y no sabe construir nada. El constructor abstracto declara un método por cada parte del objeto. Los constructores concretos implementan esos métodos, y cada uno sabe fabricar su parte y solo la suya.

El director va pidiendo las partes y al final ensambla el resultado. Como el proceso de montaje está separado de la fabricación de las piezas, con el mismo director y distintos constructores concretos se obtienen objetos diferentes siguiendo el mismo procedimiento.

java
public interface ConstructorDeSimulacro {
    void ponerCabecera(Convocatoria c);
    void ponerPreguntas(List<Tema> temas);
    void ponerHojaDeRespuestas();
    Simulacro obtenerResultado();
}

public class DirectorDeSimulacros {
    private final ConstructorDeSimulacro constructor;

    public DirectorDeSimulacros(ConstructorDeSimulacro constructor) {
        this.constructor = constructor;
    }

    public Simulacro montar(Convocatoria c, List<Tema> temas) {
        constructor.ponerCabecera(c);        // el director marca el orden
        constructor.ponerPreguntas(temas);   // cada paso lo hace el builder
        constructor.ponerHojaDeRespuestas();
        return constructor.obtenerResultado();
    }
}

Para el examen

  • Director: conoce el orden de montaje y no fabrica nada

  • Constructores concretos: fabrican cada parte

  • Consecuencia: el mismo director con otro constructor da un producto distinto