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
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ón | Cómo funciona | Precio |
|---|---|---|
| Inicialización temprana | Se crea la instancia al cargar la clase, no al pedirla | Se construye aunque nadie la use |
| Método sincronizado | Se serializa la entrada al método de acceso | Penaliza todas las llamadas, no solo la primera |
| Clase interna de soporte | Se apoya en que la máquina virtual carga las clases una sola vez y de forma segura | Ninguno 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.
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 Method | Abstract Factory | |
|---|---|---|
| Qué crea | Un producto | Una familia de productos relacionados |
| Cuántos métodos fábrica | Uno | Uno por cada producto de la familia |
| Qué garantiza | Que el cliente no conozca la clase concreta | Ademá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.
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