En esta sección, exploraremos ejemplos prácticos de cómo aplicar algunos de los patrones de diseño más comunes en situaciones reales de desarrollo de software. Cada ejemplo incluirá una explicación detallada, un bloque de código y ejercicios prácticos para reforzar los conceptos aprendidos.
Ejemplo 1: Singleton
Descripción
El patrón Singleton asegura que una clase tenga solo una instancia y proporciona un punto de acceso global a ella. Es útil en situaciones donde se necesita un control centralizado, como en el caso de un registro de configuración o una conexión a una base de datos.
Código de Ejemplo
class Singleton: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super(Singleton, cls).__new__(cls) return cls._instance # Uso del Singleton singleton1 = Singleton() singleton2 = Singleton() print(singleton1 is singleton2) # True
Explicación
- Clase Singleton: La clase
Singleton
tiene un atributo de clase_instance
que almacena la única instancia de la clase. - Método
__new__
: El método__new__
se sobrescribe para controlar la creación de nuevas instancias. Si_instance
esNone
, se crea una nueva instancia; de lo contrario, se devuelve la instancia existente. - Uso: Al crear dos objetos
singleton1
ysingleton2
, ambos apuntan a la misma instancia, lo que se verifica consingleton1 is singleton2
.
Ejercicio Práctico
Implementa un Singleton para una clase Logger
que solo permite una instancia para registrar mensajes.
class Logger: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super(Logger, cls).__new__(cls) return cls._instance def log(self, message): print(f"Log: {message}") # Uso del Logger logger1 = Logger() logger2 = Logger() logger1.log("Este es un mensaje de prueba.") logger2.log("Este es otro mensaje de prueba.") print(logger1 is logger2) # True
Solución
class Logger: _instance = None def __new__(cls): if cls._instance is None: cls._instance = super(Logger, cls).__new__(cls) return cls._instance def log(self, message): print(f"Log: {message}") # Uso del Logger logger1 = Logger() logger2 = Logger() logger1.log("Este es un mensaje de prueba.") logger2.log("Este es otro mensaje de prueba.") print(logger1 is logger2) # True
Ejemplo 2: Factory Method
Descripción
El patrón Factory Method define una interfaz para crear un objeto, pero permite que las subclases alteren el tipo de objetos que se crearán. Es útil cuando una clase no puede anticipar el tipo de objetos que debe crear.
Código de Ejemplo
from abc import ABC, abstractmethod class Product(ABC): @abstractmethod def operation(self): pass class ConcreteProductA(Product): def operation(self): return "Resultado de ConcreteProductA" class ConcreteProductB(Product): def operation(self): return "Resultado de ConcreteProductB" class Creator(ABC): @abstractmethod def factory_method(self): pass def some_operation(self): product = self.factory_method() return f"Creator: El mismo código del creador ha trabajado con {product.operation()}" class ConcreteCreatorA(Creator): def factory_method(self): return ConcreteProductA() class ConcreteCreatorB(Creator): def factory_method(self): return ConcreteProductB() # Uso del Factory Method creator_a = ConcreteCreatorA() print(creator_a.some_operation()) creator_b = ConcreteCreatorB() print(creator_b.some_operation())
Explicación
- Clase Abstracta
Product
: Define la interfazoperation
que deben implementar los productos concretos. - Clases Concretas
ConcreteProductA
yConcreteProductB
: Implementan la interfazoperation
. - Clase Abstracta
Creator
: Define el métodofactory_method
que debe ser implementado por las subclases y un métodosome_operation
que utiliza el producto creado. - Clases Concretas
ConcreteCreatorA
yConcreteCreatorB
: Implementan el métodofactory_method
para crear instancias deConcreteProductA
yConcreteProductB
, respectivamente.
Ejercicio Práctico
Implementa un Factory Method para una clase Transport
con subclases Car
y Bike
.
from abc import ABC, abstractmethod class Transport(ABC): @abstractmethod def deliver(self): pass class Car(Transport): def deliver(self): return "Entrega por Carro" class Bike(Transport): def deliver(self): return "Entrega por Bicicleta" class TransportCreator(ABC): @abstractmethod def factory_method(self): pass def plan_delivery(self): transport = self.factory_method() return f"Plan de entrega: {transport.deliver()}" class CarCreator(TransportCreator): def factory_method(self): return Car() class BikeCreator(TransportCreator): def factory_method(self): return Bike() # Uso del Factory Method car_creator = CarCreator() print(car_creator.plan_delivery()) bike_creator = BikeCreator() print(bike_creator.plan_delivery())
Solución
from abc import ABC, abstractmethod class Transport(ABC): @abstractmethod def deliver(self): pass class Car(Transport): def deliver(self): return "Entrega por Carro" class Bike(Transport): def deliver(self): return "Entrega por Bicicleta" class TransportCreator(ABC): @abstractmethod def factory_method(self): pass def plan_delivery(self): transport = self.factory_method() return f"Plan de entrega: {transport.deliver()}" class CarCreator(TransportCreator): def factory_method(self): return Car() class BikeCreator(TransportCreator): def factory_method(self): return Bike() # Uso del Factory Method car_creator = CarCreator() print(car_creator.plan_delivery()) bike_creator = BikeCreator() print(bike_creator.plan_delivery())
Ejemplo 3: Observer
Descripción
El patrón Observer define una dependencia de uno a muchos entre objetos, de manera que cuando un objeto cambia de estado, todos sus dependientes son notificados y actualizados automáticamente. Es útil en situaciones donde un cambio en un objeto debe reflejarse en otros objetos.
Código de Ejemplo
class Subject: def __init__(self): self._observers = [] def attach(self, observer): self._observers.append(observer) def detach(self, observer): self._observers.remove(observer) def notify(self): for observer in self._observers: observer.update(self) class Observer: def update(self, subject): pass class ConcreteObserverA(Observer): def update(self, subject): print("ConcreteObserverA: Reacted to the event") class ConcreteObserverB(Observer): def update(self, subject): print("ConcreteObserverB: Reacted to the event") # Uso del Observer subject = Subject() observer_a = ConcreteObserverA() observer_b = ConcreteObserverB() subject.attach(observer_a) subject.attach(observer_b) subject.notify()
Explicación
- Clase
Subject
: Mantiene una lista de observadores y proporciona métodos para agregar (attach
), eliminar (detach
) y notificar (notify
) a los observadores. - Clase
Observer
: Define la interfazupdate
que deben implementar los observadores concretos. - Clases Concretas
ConcreteObserverA
yConcreteObserverB
: Implementan la interfazupdate
para reaccionar a las notificaciones del sujeto.
Ejercicio Práctico
Implementa un Observer para una clase Stock
que notifica a los observadores cuando el precio cambia.
class Stock: def __init__(self): self._observers = [] self._price = 0 def attach(self, observer): self._observers.append(observer) def detach(self, observer): self._observers.remove(observer) def notify(self): for observer in self._observers: observer.update(self) @property def price(self): return self._price @price.setter def price(self, value): self._price = value self.notify() class StockObserver: def update(self, stock): pass class ConcreteStockObserver(StockObserver): def update(self, stock): print(f"El precio del stock ha cambiado a {stock.price}") # Uso del Observer stock = Stock() observer = ConcreteStockObserver() stock.attach(observer) stock.price = 100 stock.price = 200
Solución
class Stock: def __init__(self): self._observers = [] self._price = 0 def attach(self, observer): self._observers.append(observer) def detach(self, observer): self._observers.remove(observer) def notify(self): for observer in self._observers: observer.update(self) @property def price(self): return self._price @price.setter def price(self, value): self._price = value self.notify() class StockObserver: def update(self, stock): pass class ConcreteStockObserver(StockObserver): def update(self, stock): print(f"El precio del stock ha cambiado a {stock.price}") # Uso del Observer stock = Stock() observer = ConcreteStockObserver() stock.attach(observer) stock.price = 100 stock.price = 200
Conclusión
En esta sección, hemos explorado ejemplos prácticos de algunos patrones de diseño comunes: Singleton, Factory Method y Observer. Cada ejemplo incluyó una explicación detallada, un bloque de código y ejercicios prácticos para reforzar los conceptos aprendidos. Estos patrones son herramientas poderosas que pueden ayudar a resolver problemas comunes en el desarrollo de software de manera eficiente y reutilizable.
Curso de Patrones de Diseño de Software
Módulo 1: Introducción a los Patrones de Diseño
- ¿Qué son los Patrones de Diseño?
- Historia y Origen de los Patrones de Diseño
- Clasificación de los Patrones de Diseño
- Ventajas y Desventajas de Usar Patrones de Diseño
Módulo 2: Patrones Creacionales
- Introducción a los Patrones Creacionales
- Singleton
- Factory Method
- Abstract Factory
- Builder
- Prototype
Módulo 3: Patrones Estructurales
Módulo 4: Patrones de Comportamiento
- Introducción a los Patrones de Comportamiento
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
Módulo 5: Aplicación de Patrones de Diseño
- Cómo Seleccionar el Patrón Adecuado
- Ejemplos Prácticos de Uso de Patrones
- Patrones de Diseño en Proyectos Reales
- Refactorización Usando Patrones de Diseño
Módulo 6: Patrones de Diseño Avanzados
- Patrones de Diseño en Arquitecturas Modernas
- Patrones de Diseño en Microservicios
- Patrones de Diseño en Sistemas Distribuidos
- Patrones de Diseño en Desarrollo Ágil