1. Introducción
En ingeniería de software, un patrón de diseño describe una solución establecida para los problemas más comunes en el diseño de software. Representa las mejores prácticas desarrolladas durante un largo período a través de prueba y error por desarrolladores de software experimentados.
Design Patterns ganó popularidad después de que el libro Design Patterns: Elements of Reusable Object-Oriented Software fuera publicado en 1994 por Erich Gamma, John Vlissides, Ralph Johnson y Richard Helm (también conocido como Gang of Four o GoF).
En este artículo, exploraremos los patrones de diseño de creación y sus tipos. También veremos algunos ejemplos de código y discutiremos las situaciones en las que estos patrones se ajustan a nuestro diseño.
2. Patrones de diseño creacional
Los Patrones de Diseño Creacional se preocupan por la forma en que se crean los objetos. Reducen la complejidad y la inestabilidad al crear objetos de manera controlada.
El nuevo operador a menudo se considera dañino ya que esparce objetos por toda la aplicación. Con el tiempo, puede resultar un desafío cambiar una implementación porque las clases se acoplan estrechamente.
Los patrones de diseño de creación abordan este problema al desvincular al cliente por completo del proceso de inicialización real.
En este artículo, analizaremos cuatro tipos de patrones de diseño de creación:
- Singleton: garantiza que, como máximo, solo exista una instancia de un objeto en toda la aplicación
- Método de fábrica: crea objetos de varias clases relacionadas sin especificar el objeto exacto que se creará
- Abstract Factory: crea familias de objetos dependientes relacionados
- Constructor : construye objetos complejos utilizando un enfoque paso a paso
Analicemos ahora cada uno de estos patrones en detalle.
3. Patrón de diseño singleton
El patrón de diseño Singleton tiene como objetivo controlar la inicialización de los objetos de una clase en particular asegurándose de que solo exista una instancia del objeto en toda la máquina virtual Java.
Una clase Singleton también proporciona un punto de acceso global único al objeto, de modo que cada llamada posterior al punto de acceso devuelve solo ese objeto en particular.
3.1. Ejemplo de patrón singleton
Aunque GoF introdujo el patrón Singleton, se sabe que la implementación original es problemática en escenarios multiproceso.
Entonces, aquí, vamos a seguir un enfoque más óptimo que hace uso de una clase interna estática:
public class Singleton { private Singleton() {} private static class SingletonHolder { public static final Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } }
Aquí, hemos creado una clase interna estática que contiene la instancia de la clase Singleton . Crea la instancia solo cuando alguien llama al método getInstance () y no cuando se carga la clase externa.
Este es un enfoque ampliamente utilizado para una clase Singleton, ya que no requiere sincronización, es seguro para subprocesos, impone la inicialización diferida y tiene comparativamente menos repetición.
Además, tenga en cuenta que el constructor tiene el modificador de acceso privado . Este es un requisito para crear un Singleton, ya que un constructor público significaría que cualquiera podría acceder a él y comenzar a crear nuevas instancias.
Recuerde, esta no es la implementación original de GoF. Para obtener la versión original, visite este artículo vinculado de Baeldung sobre Singletons en Java.
3.2. Cuándo utilizar el patrón de diseño Singleton
- Para recursos que son costosos de crear (como objetos de conexión de base de datos)
- Es una buena práctica mantener todos los registradores como Singletons, lo que aumenta el rendimiento.
- Clases que brindan acceso a los ajustes de configuración de la aplicación
- Clases que contienen recursos a los que se accede en modo compartido
4. Patrón de diseño del método de fábrica
El patrón de diseño de fábrica o el patrón de diseño del método de fábrica es uno de los patrones de diseño más utilizados en Java.
Según GoF, este patrón “define una interfaz para crear un objeto, pero permite que las subclases decidan qué clase instanciar. El método Factory permite que una clase difiera la instanciación a subclases ”.
Este patrón delega la responsabilidad de inicializar una clase del cliente a una clase de fábrica particular creando un tipo de constructor virtual.
Para lograrlo, nos apoyamos en una fábrica que nos proporciona los objetos, ocultando los detalles de implementación reales. Se accede a los objetos creados mediante una interfaz común.
4.1. Ejemplo de patrón de diseño de método de fábrica
En este ejemplo, crearemos una interfaz Polygon que será implementada por varias clases concretas. Se utilizará una PolygonFactory para buscar objetos de esta familia:

Primero creemos la interfaz Polygon :
public interface Polygon { String getType(); }
A continuación, crearemos algunas implementaciones como Square , Triangle, etc. que implementan esta interfaz y devuelven un objeto de tipo Polygon .
Ahora podemos crear una fábrica que tome el número de lados como argumento y devuelva la implementación adecuada de esta interfaz:
public class PolygonFactory { public Polygon getPolygon(int numberOfSides) { if(numberOfSides == 3) { return new Triangle(); } if(numberOfSides == 4) { return new Square(); } if(numberOfSides == 5) { return new Pentagon(); } if(numberOfSides == 7) { return new Heptagon(); } else if(numberOfSides == 8) { return new Octagon(); } return null; } }
Observe cómo el cliente puede confiar en esta fábrica para darnos un polígono apropiado , sin tener que inicializar el objeto directamente.
4.2. Cuándo utilizar el patrón de diseño del método de fábrica
- Cuando se espera que la implementación de una interfaz o una clase abstracta cambie con frecuencia
- Cuando la implementación actual no puede acomodar cómodamente un nuevo cambio
- Cuando el proceso de inicialización es relativamente simple y el constructor solo requiere unos pocos parámetros
5. Abstract Factory Design Pattern
In the previous section, we saw how the Factory Method design pattern could be used to create objects related to a single family.
By contrast, the Abstract Factory Design Pattern is used to create families of related or dependent objects. It's also sometimes called a factory of factories.
For a detailed explanation, check out our Abstract Factory tutorial.
6. Builder Design Pattern
The Builder Design Pattern is another creational pattern designed to deal with the construction of comparatively complex objects.
When the complexity of creating object increases, the Builder pattern can separate out the instantiation process by using another object (a builder) to construct the object.
This builder can then be used to create many other similar representations using a simple step-by-step approach.
6.1. Builder Pattern Example
The original Builder Design Pattern introduced by GoF focuses on abstraction and is very good when dealing with complex objects, however, the design is a little complicated.
Joshua Bloch, in his book Effective Java, introduced an improved version of the builder pattern which is clean, highly readable (because it makes use of fluent design) and easy to use from client's perspective. In this example, we'll discuss that version.
This example has only one class, BankAccount which contains a builder as a static inner class:
public class BankAccount { private String name; private String accountNumber; private String email; private boolean newsletter; // constructors/getters public static class BankAccountBuilder { // builder code } }
Note that all the access modifiers on the fields are declared private since we don't want outer objects to access them directly.
The constructor is also private so that only the Builder assigned to this class can access it. All of the properties set in the constructor are extracted from the builder object which we supply as an argument.
We've defined BankAccountBuilder in a static inner class:
public static class BankAccountBuilder { private String name; private String accountNumber; private String email; private boolean newsletter; public BankAccountBuilder(String name, String accountNumber) { this.name = name; this.accountNumber = accountNumber; } public BankAccountBuilder withEmail(String email) { this.email = email; return this; } public BankAccountBuilder wantNewsletter(boolean newsletter) { this.newsletter = newsletter; return this; } public BankAccount build() { return new BankAccount(this); } }
Notice we've declared the same set of fields that the outer class contains. Any mandatory fields are required as arguments to the inner class's constructor while the remaining optional fields can be specified using the setter methods.
This implementation also supports the fluent design approach by having the setter methods return the builder object.
Finally, the build method calls the private constructor of the outer class and passes itself as the argument. The returned BankAccount will be instantiated with the parameters set by the BankAccountBuilder.
Let's see a quick example of the builder pattern in action:
BankAccount newAccount = new BankAccount .BankAccountBuilder("Jon", "22738022275") .withEmail("[email protected]") .wantNewsletter(true) .build();
6.2. When to Use Builder Pattern
- When the process involved in creating an object is extremely complex, with lots of mandatory and optional parameters
- When an increase in the number of constructor parameters leads to a large list of constructors
- When client expects different representations for the object that's constructed
7. Conclusion
En este artículo, aprendimos sobre patrones de diseño de creación en Java. También discutimos sus cuatro tipos diferentes, es decir, Singleton, Factory Method, Abstract Factory y Builder Pattern, sus ventajas, ejemplos y cuándo deberíamos usarlos.
Como siempre, los fragmentos de código completos están disponibles en GitHub.