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

  1. Patrones Creacionales: Se centran en la creación de objetos.
    • Ejemplos: Singleton, Factory, Abstract Factory, Builder, Prototype.
  2. Patrones Estructurales: Se centran en la composición de clases y objetos.
    • Ejemplos: Adapter, Composite, Proxy, Flyweight, Facade, Bridge, Decorator.
  3. 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

Módulo 2: Flujo de Control

Módulo 3: Programación Orientada a Objetos

Módulo 4: Programación Orientada a Objetos Avanzada

Módulo 5: Estructuras de Datos y Colecciones

Módulo 6: Manejo de Excepciones

Módulo 7: Entrada/Salida de Archivos

Módulo 8: Multihilo y Concurrencia

Módulo 9: Redes

Módulo 10: Temas Avanzados

Módulo 11: Frameworks y Librerías de Java

Módulo 12: Construcción de Aplicaciones del Mundo Real

© Copyright 2024. Todos los derechos reservados