En este módulo, exploraremos los principios fundamentales que guían el diseño de software robusto, mantenible y escalable. Estos principios son esenciales para cualquier arquitecto de sistemas y desarrollador de software que desee crear aplicaciones de alta calidad.
Objetivos del Módulo
- Comprender los principios básicos de diseño de software.
- Aprender a aplicar estos principios en el desarrollo de software.
- Identificar y evitar errores comunes en el diseño de software.
Contenido
Principio de Responsabilidad Única (SRP)
El Principio de Responsabilidad Única establece que una clase debe tener una, y solo una, razón para cambiar. Esto significa que cada clase debe tener una única responsabilidad o propósito.
Ejemplo
class ReportGenerator:
def generate_report(self, data):
# Genera el reporte
pass
def save_to_file(self, filename):
# Guarda el reporte en un archivo
passProblema: La clase ReportGenerator tiene dos responsabilidades: generar el reporte y guardarlo en un archivo.
Solución:
class ReportGenerator:
def generate_report(self, data):
# Genera el reporte
pass
class ReportSaver:
def save_to_file(self, report, filename):
# Guarda el reporte en un archivo
passAhora, cada clase tiene una única responsabilidad.
Ejercicio
Refactoriza la siguiente clase para que cumpla con el Principio de Responsabilidad Única:
class UserManager:
def create_user(self, user_data):
# Crea un nuevo usuario
pass
def send_welcome_email(self, user_email):
# Envía un correo de bienvenida
passSolución:
class UserManager:
def create_user(self, user_data):
# Crea un nuevo usuario
pass
class EmailService:
def send_welcome_email(self, user_email):
# Envía un correo de bienvenida
passPrincipio de Abierto/Cerrado (OCP)
El Principio de Abierto/Cerrado establece que las entidades de software (clases, módulos, funciones, etc.) deben estar abiertas para extensión, pero cerradas para modificación. Esto significa que el comportamiento de una entidad debe poder extenderse sin modificar su código fuente.
Ejemplo
class Rectangle:
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle:
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radiusProblema: Si queremos agregar una nueva forma, debemos modificar el código existente.
Solución:
from abc import ABC, abstractmethod
class Shape(ABC):
@abstractmethod
def area(self):
pass
class Rectangle(Shape):
def __init__(self, width, height):
self.width = width
self.height = height
def area(self):
return self.width * self.height
class Circle(Shape):
def __init__(self, radius):
self.radius = radius
def area(self):
return 3.14 * self.radius * self.radiusAhora, podemos agregar nuevas formas sin modificar las clases existentes.
Ejercicio
Refactoriza el siguiente código para que cumpla con el Principio de Abierto/Cerrado:
class PaymentProcessor:
def process_credit_card(self, amount):
# Procesa el pago con tarjeta de crédito
pass
def process_paypal(self, amount):
# Procesa el pago con PayPal
passSolución:
from abc import ABC, abstractmethod
class PaymentProcessor(ABC):
@abstractmethod
def process_payment(self, amount):
pass
class CreditCardProcessor(PaymentProcessor):
def process_payment(self, amount):
# Procesa el pago con tarjeta de crédito
pass
class PayPalProcessor(PaymentProcessor):
def process_payment(self, amount):
# Procesa el pago con PayPal
passPrincipio de Sustitución de Liskov (LSP)
El Principio de Sustitución de Liskov establece que los objetos de una clase derivada deben ser sustituibles por objetos de la clase base sin alterar el comportamiento del programa.
Ejemplo
class Bird:
def fly(self):
pass
class Sparrow(Bird):
def fly(self):
print("Sparrow flying")
class Ostrich(Bird):
def fly(self):
raise Exception("Ostrich can't fly")Problema: Ostrich no puede volar, lo que rompe el comportamiento esperado de la clase base Bird.
Solución:
class Bird(ABC):
@abstractmethod
def move(self):
pass
class Sparrow(Bird):
def move(self):
print("Sparrow flying")
class Ostrich(Bird):
def move(self):
print("Ostrich running")Ahora, Ostrich y Sparrow pueden ser sustituidos sin alterar el comportamiento del programa.
Ejercicio
Refactoriza el siguiente código para que cumpla con el Principio de Sustitución de Liskov:
class Vehicle:
def start_engine(self):
pass
class Car(Vehicle):
def start_engine(self):
print("Car engine started")
class Bicycle(Vehicle):
def start_engine(self):
raise Exception("Bicycle doesn't have an engine")Solución:
class Vehicle(ABC):
@abstractmethod
def move(self):
pass
class Car(Vehicle):
def move(self):
print("Car engine started")
class Bicycle(Vehicle):
def move(self):
print("Bicycle moving")Principio de Segregación de Interfaces (ISP)
El Principio de Segregación de Interfaces establece que los clientes no deben estar obligados a depender de interfaces que no utilizan. Es mejor tener interfaces específicas y pequeñas en lugar de una interfaz grande y general.
Ejemplo
Problema: No todos los trabajadores necesitan la función eat.
Solución:
class Workable(ABC):
@abstractmethod
def work(self):
pass
class Eatable(ABC):
@abstractmethod
def eat(self):
pass
class Worker(Workable, Eatable):
def work(self):
print("Working")
def eat(self):
print("Eating")
class Robot(Workable):
def work(self):
print("Working")Ahora, Robot no necesita implementar la función eat.
Ejercicio
Refactoriza el siguiente código para que cumpla con el Principio de Segregación de Interfaces:
class Printer:
def print_document(self, document):
pass
def scan_document(self, document):
pass
def fax_document(self, document):
passSolución:
class Printable(ABC):
@abstractmethod
def print_document(self, document):
pass
class Scannable(ABC):
@abstractmethod
def scan_document(self, document):
pass
class Faxable(ABC):
@abstractmethod
def fax_document(self, document):
pass
class MultiFunctionPrinter(Printable, Scannable, Faxable):
def print_document(self, document):
print("Printing document")
def scan_document(self, document):
print("Scanning document")
def fax_document(self, document):
print("Faxing document")Principio de Inversión de Dependencias (DIP)
El Principio de Inversión de Dependencias establece que los módulos de alto nivel no deben depender de módulos de bajo nivel. Ambos deben depender de abstracciones. Además, las abstracciones no deben depender de detalles. Los detalles deben depender de abstracciones.
Ejemplo
class LightBulb:
def turn_on(self):
print("LightBulb turned on")
class Switch:
def __init__(self, bulb):
self.bulb = bulb
def operate(self):
self.bulb.turn_on()Problema: Switch depende directamente de LightBulb.
Solución:
class Switchable(ABC):
@abstractmethod
def turn_on(self):
pass
class LightBulb(Switchable):
def turn_on(self):
print("LightBulb turned on")
class Switch:
def __init__(self, device: Switchable):
self.device = device
def operate(self):
self.device.turn_on()Ahora, Switch depende de la abstracción Switchable.
Ejercicio
Refactoriza el siguiente código para que cumpla con el Principio de Inversión de Dependencias:
class MySQLDatabase:
def connect(self):
pass
class Application:
def __init__(self):
self.database = MySQLDatabase()
def start(self):
self.database.connect()Solución:
class Database(ABC):
@abstractmethod
def connect(self):
pass
class MySQLDatabase(Database):
def connect(self):
print("Connecting to MySQL")
class Application:
def __init__(self, database: Database):
self.database = database
def start(self):
self.database.connect()Conclusión
En este módulo, hemos cubierto los principios fundamentales de diseño de software que son esenciales para crear sistemas robustos y mantenibles. Al aplicar estos principios, puedes mejorar la calidad de tu código y facilitar su mantenimiento y escalabilidad. Asegúrate de practicar estos principios en tus proyectos para internalizarlos y aplicarlos de manera efectiva.
Arquitecturas de Sistemas: Principios y Prácticas para Diseñar Arquitecturas Tecnológicas Robustas y Escalables
Módulo 1: Introducción a las Arquitecturas de Sistemas
- Conceptos Básicos de Arquitectura de Sistemas
- Importancia de una Buena Arquitectura
- Tipos de Arquitecturas de Sistemas
Módulo 2: Principios de Diseño de Arquitecturas
Módulo 3: Componentes de una Arquitectura de Sistemas
Módulo 4: Escalabilidad y Rendimiento
Módulo 5: Seguridad en Arquitecturas de Sistemas
Módulo 6: Herramientas y Tecnologías
Módulo 7: Casos de Estudio y Ejemplos Prácticos
- Caso de Estudio: Arquitectura de un Sistema de Comercio Electrónico
- Caso de Estudio: Arquitectura de una Aplicación de Redes Sociales
- Ejercicios Prácticos
