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) # TrueExplicación
- Clase Singleton: La clase
Singletontiene un atributo de clase_instanceque 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_instanceesNone, se crea una nueva instancia; de lo contrario, se devuelve la instancia existente. - Uso: Al crear dos objetos
singleton1ysingleton2, 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) # TrueSolució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) # TrueEjemplo 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 interfazoperationque deben implementar los productos concretos. - Clases Concretas
ConcreteProductAyConcreteProductB: Implementan la interfazoperation. - Clase Abstracta
Creator: Define el métodofactory_methodque debe ser implementado por las subclases y un métodosome_operationque utiliza el producto creado. - Clases Concretas
ConcreteCreatorAyConcreteCreatorB: Implementan el métodofactory_methodpara crear instancias deConcreteProductAyConcreteProductB, 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 interfazupdateque deben implementar los observadores concretos. - Clases Concretas
ConcreteObserverAyConcreteObserverB: Implementan la interfazupdatepara 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 = 200Solució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 = 200Conclusió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
