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

class Worker:
    def work(self):
        pass

    def eat(self):
        pass

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

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

Módulo 8: Tendencias y Futuro de las Arquitecturas de Sistemas

© Copyright 2024. Todos los derechos reservados