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
