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 pass
Problema: 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 pass
Ahora, 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 pass
Solució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 pass
Principio 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.radius
Problema: 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.radius
Ahora, 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 pass
Solució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 pass
Principio 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): pass
Solució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