Los patrones de diseño son soluciones probadas y estándar para problemas comunes de diseño de software. Proporcionan un enfoque reutilizable y estructurado para resolver problemas específicos durante el desarrollo de software orientado a objetos.
A continuación, se presentan algunos patrones de diseño comunes y cómo se aplican en el desarrollo de software:
- Singleton: Este patrón garantiza que una clase tenga una sola instancia y proporciona un punto de acceso global a esa instancia. Es útil cuando se necesita una única instancia compartida en toda la aplicación, como un objeto de registro o un objeto de configuración.
Ejemplo de Uso del Patrón Singleton en Java:
public class Singleton { private static Singleton instance; // Constructor privado para evitar la instanciación externa private Singleton() {} // Método estático para obtener la instancia única public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
En este ejemplo, la clase Singleton
garantiza que solo exista una instancia de la clase en toda la aplicación. El método getInstance()
proporciona un punto de acceso global a esa instancia única.
- Factory: El patrón Factory se utiliza para encapsular la creación de objetos y ocultar los detalles de implementación. Define una interfaz para crear un objeto, pero permite a las subclases decidir qué clase concreta instanciar. Esto facilita la creación de objetos sin exponer la lógica de creación.
Ejemplo:
interface Animal { void makeSound(); } class Dog implements Animal { public void makeSound() { System.out.println("Woof"); } } class Cat implements Animal { public void makeSound() { System.out.println("Meow"); } } class AnimalFactory { public static Animal createAnimal(String type) { if (type.equalsIgnoreCase("dog")) { return new Dog(); } else if (type.equalsIgnoreCase("cat")) { return new Cat(); } else { return null; } } }
En este ejemplo, AnimalFactory
encapsula la creación de objetos Animal
. Dependiendo del tipo pasado como argumento a createAnimal()
, devuelve una instancia de Dog
o Cat
.
- Builder: Este patrón se utiliza para construir objetos complejos paso a paso. Permite la creación de diferentes representaciones de un objeto utilizando el mismo proceso de construcción. Es útil cuando se necesita crear objetos con muchas opciones de configuración.
Ejemplo:
class Pizza { private String dough = ""; private String sauce = ""; private String topping = ""; public void setDough(String dough) { this.dough = dough; } public void setSauce(String sauce) { this.sauce = sauce; } public void setTopping(String topping) { this.topping = topping; } } class PizzaBuilder { private Pizza pizza; public PizzaBuilder() { pizza = new Pizza(); } public PizzaBuilder addDough(String dough) { pizza.setDough(dough); return this; } public PizzaBuilder addSauce(String sauce) { pizza.setSauce(sauce); return this; } public PizzaBuilder addTopping(String topping) { pizza.setTopping(topping); return this; } public Pizza build() { return pizza; } }
En este ejemplo, PizzaBuilder
facilita la creación de objetos Pizza
permitiendo configurar opcionalmente la masa, la salsa y los ingredientes antes de construir la pizza.
- Observer: El patrón Observer, establece una relación de dependencia uno a muchos entre objetos, de modo que cuando un objeto cambia de estado, todos sus dependientes son notificados y actualizados automáticamente. Es útil para implementar la propagación de cambios en tiempo real, como en los sistemas de eventos o notificaciones.
Ejemplo:
import java.util.ArrayList; import java.util.List; interface Observer { void update(String message); } class MessagePublisher { private List<Observer> observers = new ArrayList<>(); public void attach(Observer observer) { observers.add(observer); } public void notifyObservers(String message) { for (Observer observer : observers) { observer.update(message); } } } class MessageSubscriber implements Observer { private String name; public MessageSubscriber(String name) { this.name = name; } public void update(String message) { System.out.println(name + " received message: " + message); } }
En este ejemplo, MessagePublisher
es el sujeto que envía mensajes a los observadores (MessageSubscriber
). Los observadores se registran con el sujeto y se notifican automáticamente cuando hay un nuevo mensaje.
- Strategy: Este patrón define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables. Permite que el algoritmo varíe independientemente de los clientes que lo utilizan. Es útil cuando se necesita cambiar dinámicamente el comportamiento de un objeto en tiempo de ejecución.
Ejemplo:
interface PaymentStrategy { void pay(int amount); } class CreditCardPayment implements PaymentStrategy { private String cardNumber; private String cvv; private String expiryDate; public CreditCardPayment(String cardNumber, String cvv, String expiryDate) { this.cardNumber = cardNumber; this.cvv = cvv; this.expiryDate = expiryDate; } public void pay(int amount) { System.out.println(amount + " paid with credit/debit card"); } } class PayPalPayment implements PaymentStrategy { private String email; private String password; public PayPalPayment(String email, String password) { this.email = email; this.password = password; } public void pay(int amount) { System.out.println(amount + " paid using PayPal"); } } class ShoppingCart { private PaymentStrategy paymentStrategy; public void setPaymentStrategy(PaymentStrategy paymentStrategy) { this.paymentStrategy = paymentStrategy; } public void checkout(int amount) { paymentStrategy.pay(amount); } }
En este ejemplo, ShoppingCart
utiliza una estrategia de pago (PaymentStrategy
) para realizar pagos. Dependiendo del método de pago seleccionado por el cliente, se utiliza una estrategia de pago específica, ya sea mediante tarjeta de crédito o PayPal.
- Composite: El patrón Composite se utiliza para tratar objetos compuestos y simples de la misma manera. Define una estructura de árbol donde los nodos individuales y las composiciones de nodos se tratan uniformemente. Es útil para representar jerarquías de objetos, como estructuras de árbol o menús.
Ejemplo:
import java.util.ArrayList; import java.util.List; // Componente interface Component { void operation(); } // Hoja class Leaf implements Component { private String name; public Leaf(String name) { this.name = name; } public void operation() { System.out.println("Leaf " + name + " operation"); } } // Compuesto class Composite implements Component { private List<Component> components = new ArrayList<>(); public void addComponent(Component component) { components.add(component); } public void removeComponent(Component component) { components.remove(component); } public void operation() { for (Component component : components) { component.operation(); } } } public class Main { public static void main(String[] args) { // Crear componentes Component leaf1 = new Leaf("Leaf 1"); Component leaf2 = new Leaf("Leaf 2"); Component leaf3 = new Leaf("Leaf 3"); // Crear composite Composite composite = new Composite(); composite.addComponent(leaf1); composite.addComponent(leaf2); // Agregar hojas al composite Composite composite2 = new Composite(); composite2.addComponent(leaf3); composite.addComponent(composite2); // Llamar a la operación en el composite composite.operation(); } }
En este ejemplo, tenemos tres clases:
Component
: Define la interfaz para los objetos en la composición.Leaf
: Representa las hojas de la estructura, es decir, los objetos finales que no tienen hijos.Composite
: Representa los componentes que tienen hijos, es decir, la estructura compuesta por otros componentes.
En el método main
, creamos un árbol de componentes donde un Composite
puede contener otros componentes (ya sean Leaf
o Composite
). Al llamar al método operation()
en el Composite
raíz, se invocará recursivamente el método operation()
en todos los componentes contenidos, permitiendo que la operación se propague a través de la estructura compuesta.
Al comprender y aplicar estos patrones, los desarrolladores pueden mejorar la modularidad, la flexibilidad y la reutilización del código en sus proyectos de desarrollo de software.
Ingeniero en Informática, Investigador, me encanta crear cosas o arreglarlas y darles una nueva vida. Escritor y poeta. Más de 20 APPs publicadas y un libro en Amazon.