Los patrones de diseño son soluciones probadas y reutilizables para problemas comunes en el diseño de software. Estos patrones ayudan a los desarrolladores a crear sistemas más flexibles, reutilizables y mantenibles. En este tema, exploraremos algunos de los patrones de diseño más utilizados en Java.
Introducción a los Patrones de Diseño
¿Qué son los Patrones de Diseño?
- Definición: Un patrón de diseño es una solución general reutilizable para un problema común en un contexto dado en el diseño de software.
- Propósito: Facilitar la creación de software robusto y flexible, promoviendo buenas prácticas de diseño.
Categorías de Patrones de Diseño
- Patrones Creacionales: Se centran en la creación de objetos.
- Ejemplos: Singleton, Factory, Abstract Factory, Builder, Prototype.
- Patrones Estructurales: Se centran en la composición de clases y objetos.
- Ejemplos: Adapter, Composite, Proxy, Flyweight, Facade, Bridge, Decorator.
- Patrones de Comportamiento: Se centran en la interacción y responsabilidad entre objetos.
- Ejemplos: Strategy, Observer, Command, Iterator, Mediator, Memento, State, Template Method, Visitor, Chain of Responsibility.
Patrones Creacionales
Singleton
- Propósito: Asegura que una clase tenga solo una instancia y proporciona un punto de acceso global a ella.
- Implementación:
public class Singleton { private static Singleton instance; private Singleton() { // Constructor privado para evitar instanciación } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
Factory Method
- Propósito: Define una interfaz para crear un objeto, pero permite a las subclases alterar el tipo de objetos que se crearán.
- Implementación:
public interface Product { void use(); } public class ConcreteProductA implements Product { public void use() { System.out.println("Using Product A"); } } public class ConcreteProductB implements Product { public void use() { System.out.println("Using Product B"); } } public abstract class Creator { public abstract Product factoryMethod(); public void someOperation() { Product product = factoryMethod(); product.use(); } } public class ConcreteCreatorA extends Creator { public Product factoryMethod() { return new ConcreteProductA(); } } public class ConcreteCreatorB extends Creator { public Product factoryMethod() { return new ConcreteProductB(); } }
Patrones Estructurales
Adapter
- Propósito: Permite que las interfaces incompatibles trabajen juntas.
- Implementación:
public interface Target { void request(); } public class Adaptee { public void specificRequest() { System.out.println("Specific request"); } } public class Adapter implements Target { private Adaptee adaptee; public Adapter(Adaptee adaptee) { this.adaptee = adaptee; } public void request() { adaptee.specificRequest(); } }
Decorator
- Propósito: Permite agregar responsabilidades a los objetos de manera dinámica.
- Implementación:
public interface Component { void operation(); } public class ConcreteComponent implements Component { public void operation() { System.out.println("ConcreteComponent operation"); } } public abstract class Decorator implements Component { protected Component component; public Decorator(Component component) { this.component = component; } public void operation() { component.operation(); } } public class ConcreteDecoratorA extends Decorator { public ConcreteDecoratorA(Component component) { super(component); } public void operation() { super.operation(); System.out.println("ConcreteDecoratorA operation"); } } public class ConcreteDecoratorB extends Decorator { public ConcreteDecoratorB(Component component) { super(component); } public void operation() { super.operation(); System.out.println("ConcreteDecoratorB operation"); } }
Patrones de Comportamiento
Strategy
- Propósito: Define una familia de algoritmos, encapsula cada uno y los hace intercambiables.
- Implementación:
public interface Strategy { void execute(); } public class ConcreteStrategyA implements Strategy { public void execute() { System.out.println("Strategy A"); } } public class ConcreteStrategyB implements Strategy { public void execute() { System.out.println("Strategy B"); } } public class Context { private Strategy strategy; public Context(Strategy strategy) { this.strategy = strategy; } public void setStrategy(Strategy strategy) { this.strategy = strategy; } public void executeStrategy() { strategy.execute(); } }
Observer
- Propósito: Define una dependencia uno a muchos entre objetos, de manera que cuando un objeto cambia de estado, todos sus dependientes son notificados y actualizados automáticamente.
- Implementación:
import java.util.ArrayList; import java.util.List; public interface Observer { void update(); } public class ConcreteObserver implements Observer { private String name; public ConcreteObserver(String name) { this.name = name; } public void update() { System.out.println(name + " has been notified."); } } public class Subject { private List<Observer> observers = new ArrayList<>(); public void addObserver(Observer observer) { observers.add(observer); } public void removeObserver(Observer observer) { observers.remove(observer); } public void notifyObservers() { for (Observer observer : observers) { observer.update(); } } }
Ejercicios Prácticos
Ejercicio 1: Implementar el Patrón Singleton
Descripción: Implementa una clase DatabaseConnection
utilizando el patrón Singleton para asegurar que solo una instancia de la conexión a la base de datos exista en la aplicación.
Solución:
public class DatabaseConnection { private static DatabaseConnection instance; private DatabaseConnection() { // Constructor privado para evitar instanciación } public static DatabaseConnection getInstance() { if (instance == null) { instance = new DatabaseConnection(); } return instance; } public void connect() { System.out.println("Connecting to the database..."); } }
Ejercicio 2: Implementar el Patrón Strategy
Descripción: Implementa una aplicación que utilice el patrón Strategy para realizar diferentes tipos de operaciones matemáticas (suma, resta, multiplicación).
Solución:
public interface MathOperation { int execute(int a, int b); } public class Addition implements MathOperation { public int execute(int a, int b) { return a + b; } } public class Subtraction implements MathOperation { public int execute(int a, int b) { return a - b; } } public class Multiplication implements MathOperation { public int execute(int a, int b) { return a * b; } } public class Calculator { private MathOperation operation; public Calculator(MathOperation operation) { this.operation = operation; } public void setOperation(MathOperation operation) { this.operation = operation; } public int executeOperation(int a, int b) { return operation.execute(a, b); } }
Ejercicio 3: Implementar el Patrón Observer
Descripción: Implementa una aplicación que utilice el patrón Observer para notificar a los usuarios cuando un producto está disponible en una tienda en línea.
Solución:
import java.util.ArrayList; import java.util.List; public interface Observer { void update(String product); } public class User implements Observer { private String name; public User(String name) { this.name = name; } public void update(String product) { System.out.println(name + " has been notified that " + product + " is now available."); } } public class Store { private List<Observer> observers = new ArrayList<>(); private List<String> products = new ArrayList<>(); public void addObserver(Observer observer) { observers.add(observer); } public void removeObserver(Observer observer) { observers.remove(observer); } public void addProduct(String product) { products.add(product); notifyObservers(product); } private void notifyObservers(String product) { for (Observer observer : observers) { observer.update(product); } } }
Conclusión
En esta sección, hemos explorado algunos de los patrones de diseño más comunes y útiles en Java. Estos patrones no solo ayudan a resolver problemas recurrentes en el desarrollo de software, sino que también promueven la reutilización del código y la flexibilidad en el diseño. A medida que avances en tu carrera como desarrollador, encontrarás que estos patrones son herramientas valiosas para crear aplicaciones robustas y mantenibles. En el próximo tema, aplicaremos estos patrones en el desarrollo de aplicaciones del mundo real.
Curso de Programación en Java
Módulo 1: Introducción a Java
- Introducción a Java
- Configuración del Entorno de Desarrollo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
- Operadores
Módulo 2: Flujo de Control
Módulo 3: Programación Orientada a Objetos
- Introducción a la POO
- Clases y Objetos
- Métodos
- Constructores
- Herencia
- Polimorfismo
- Encapsulamiento
- Abstracción
Módulo 4: Programación Orientada a Objetos Avanzada
Módulo 5: Estructuras de Datos y Colecciones
Módulo 6: Manejo de Excepciones
- Introducción a las Excepciones
- Bloque Try-Catch
- Throw y Throws
- Excepciones Personalizadas
- Bloque Finally
Módulo 7: Entrada/Salida de Archivos
- Lectura de Archivos
- Escritura de Archivos
- Flujos de Archivos
- BufferedReader y BufferedWriter
- Serialización
Módulo 8: Multihilo y Concurrencia
- Introducción al Multihilo
- Creación de Hilos
- Ciclo de Vida de un Hilo
- Sincronización
- Utilidades de Concurrencia
Módulo 9: Redes
- Introducción a las Redes
- Sockets
- ServerSocket
- DatagramSocket y DatagramPacket
- URL y HttpURLConnection