Open In Colab

3.4. Patrones de comportamiento#

Los patrones de comportamiento son un conjunto de patrones de diseño en ingeniería de software que se utilizan para gestionar algoritmos, relaciones y responsabilidades entre objetos. Estos patrones se centran en cómo los objetos interactúan entre sí y cómo se distribuyen las responsabilidades entre ellos.

3.4.1. Chain of Responsibility#

El patrón Chain of Responsibility es un patrón de diseño comportamental que se utiliza para evitar acoplar el remitente de una solicitud con su receptor al dar a más de un objeto la oportunidad de manejar la solicitud. El patrón consiste en una cadena de objetos receptores (o manejadores) y un único objeto remitente. Cada objeto receptor contiene una referencia al siguiente objeto en la cadena. Si un objeto no puede manejar la solicitud, la pasa al siguiente objeto en la cadena.

classDiagram class Handler { + set_next(handler) + handle(request) } class ConcreteHandler1 { - _next_handler: Handler + __init__() + set_next(handler) + handle(request) } class ConcreteHandler2 { - _next_handler: Handler + __init__() + set_next(handler) + handle(request) } Handler <|-- ConcreteHandler1 Handler <|-- ConcreteHandler2
from abc import ABC, abstractmethod

class Handler(ABC):
    @abstractmethod
    def set_next(self, handler):
        pass
    
    @abstractmethod
    def handle(self, request):
        pass

class ConcreteHandler1(Handler):
    def __init__(self):
        self._next_handler = None
    
    def set_next(self, handler):
        self._next_handler = handler
    
    def handle(self, request):
        if request == "request1":
            return f"ConcreteHandler1: Handling {request}"
        elif self._next_handler:
            return self._next_handler.handle(request)
        else:
            return f"ConcreteHandler1: Cannot handle {request}"

class ConcreteHandler2(Handler):
    def __init__(self):
        self._next_handler = None
    
    def set_next(self, handler):
        self._next_handler = handler
    
    def handle(self, request):
        if request == "request2":
            return f"ConcreteHandler2: Handling {request}"
        elif self._next_handler:
            return self._next_handler.handle(request)
        else:
            return f"ConcreteHandler2: Cannot handle {request}"
# Uso del patrón Chain of Responsibility
handler1 = ConcreteHandler1()
handler2 = ConcreteHandler2()

handler1.set_next(handler2)

requests = ["request1", "request2", "request3"]

for request in requests:
    result = handler1.handle(request)
    print(result)
ConcreteHandler1: Handling request1
ConcreteHandler2: Handling request2
ConcreteHandler2: Cannot handle request3

En este ejemplo:

  • Handler es una clase abstracta que define la interfaz para manejar solicitudes y establecer el siguiente manejador en la cadena.

  • ConcreteHandler1 y ConcreteHandler2 son implementaciones concretas de Handler que manejan solicitudes específicas o pasan las solicitudes al siguiente manejador en la cadena si no pueden manejarlas.

  • Cada ConcreteHandler tiene un método set_next() para establecer el siguiente manejador en la cadena y un método handle() para manejar la solicitud o pasarla al siguiente manejador.

  • En el bucle for, se pasa cada solicitud a través de la cadena de responsabilidad, y cada manejador intenta manejar la solicitud o la pasa al siguiente manejador si no puede manejarla.

Este patrón permite que varios objetos tengan la oportunidad de manejar una solicitud sin conocer los detalles de implementación de los otros objetos, lo que promueve la flexibilidad y la extensibilidad en el diseño del software.

3.4.2. Command#

El patrón Command es un patrón de diseño comportamental que encapsula una solicitud como un objeto, permitiendo así parametrizar clientes con solicitudes, encolar solicitudes, y realizar operaciones reversibles. Este patrón desacopla el objeto que realiza la solicitud (cliente) del objeto que sabe cómo realizar la solicitud (invocador), permitiendo mayor flexibilidad en el diseño del sistema.

classDiagram class Command { + execute() } class LightOnCommand { - _light: Light + __init__(light) + execute() } class Light { + turn_on() } class RemoteControl { - _command: Command + __init__() + set_command(command) + press_button() } Command <|-- LightOnCommand
from abc import ABC, abstractmethod

# Interfaz Command
class Command(ABC):
    @abstractmethod
    def execute(self):
        pass

# ConcreteCommand
class LightOnCommand(Command):
    def __init__(self, light):
        self._light = light
    
    def execute(self):
        self._light.turn_on()

# Receiver
class Light:
    def turn_on(self):
        print("Light is turned on")

# Invoker
class RemoteControl:
    def __init__(self):
        self._command = None
    
    def set_command(self, command):
        self._command = command
    
    def press_button(self):
        if self._command:
            self._command.execute()
# Cliente
light = Light()
light_on_command = LightOnCommand(light)

remote_control = RemoteControl()
remote_control.set_command(light_on_command)
remote_control.press_button()  # Salida: Light is turned on
Light is turned on

En este ejemplo:

  • Command es una interfaz que define el método execute().

  • LightOnCommand es una implementación concreta de Command que conoce cómo ejecutar la acción de encender una luz.

  • Light es el receptor que contiene la lógica para llevar a cabo la acción de encender la luz.

  • RemoteControl es el invocador que contiene una referencia al objeto Command y puede ejecutar el método execute() del Command.

  • El cliente crea una instancia de Light, LightOnCommand, y RemoteControl, configura el RemoteControl con el LightOnCommand, y finalmente presiona el botón en el RemoteControl.

Este patrón permite desacoplar el objeto que realiza la solicitud (cliente) del objeto que conoce cómo realizar la solicitud (invocador), lo que proporciona flexibilidad y extensibilidad en el diseño del sistema. Además, permite encolar solicitudes, deshacer acciones y rehacerlas.

3.4.3. Interpreter#

El patrón Interpreter es un patrón de diseño comportamental que se utiliza para interpretar un lenguaje, proporcionando una manera de evaluar el lenguaje de expresión gramatical o sintácticamente. Este patrón define una gramática para un lenguaje y proporciona un intérprete para interpretar sentencias en ese lenguaje.

classDiagram class Context { - _variables: dict + __init__() + set_variable(variable, value) + get_variable(variable) } class Expression { + interpret(context) } class NumberExpression { - _value: int + __init__(value) + interpret(context) } class AddExpression { - _left: Expression - _right: Expression + __init__(left, right) + interpret(context) } class SubtractExpression { - _left: Expression - _right: Expression + __init__(left, right) + interpret(context) } Context o-- Expression Expression <|-- NumberExpression Expression <|-- AddExpression Expression <|-- SubtractExpression
from abc import ABC, abstractmethod

# Contexto
class Context:
    def __init__(self):
        self._variables = {}
    
    def set_variable(self, variable, value):
        self._variables[variable] = value
    
    def get_variable(self, variable):
        return self._variables.get(variable, 0)

# Expresión abstracta
class Expression(ABC):
    @abstractmethod
    def interpret(self, context):
        pass

# Expresión Terminal
class NumberExpression(Expression):
    def __init__(self, value):
        self._value = value
    
    def interpret(self, context):
        return self._value

# Expresión No Terminal
class AddExpression(Expression):
    def __init__(self, left, right):
        self._left = left
        self._right = right
    
    def interpret(self, context):
        return self._left.interpret(context) + self._right.interpret(context)

# Expresión No Terminal
class SubtractExpression(Expression):
    def __init__(self, left, right):
        self._left = left
        self._right = right
    
    def interpret(self, context):
        return self._left.interpret(context) - self._right.interpret(context)
# Uso del patrón Interpreter
# Crear el contexto
context = Context()
context.set_variable("x", 10)
context.set_variable("y", 5)
    
# Crear las expresiones
expression = AddExpression(NumberExpression(context.get_variable("x")), 
                           SubtractExpression(NumberExpression(context.get_variable("y")), 
                                             NumberExpression(3)))
    
# Interpretar la expresión
result = expression.interpret(context)
print("Result:", result)  # Salida: 12
Result: 12

En este ejemplo:

  • Context es el contexto que contiene información global para las expresiones.

  • Expression es la interfaz para las expresiones. Define el método interpret() que todas las expresiones deben implementar.

  • NumberExpression es una expresión terminal que interpreta un número.

  • AddExpression y SubtractExpression son expresiones no terminales que interpretan las operaciones de suma y resta, respectivamente.

  • El cliente crea un contexto, establece las variables necesarias, crea las expresiones y finalmente interpreta la expresión con respecto al contexto.

Este patrón es útil cuando se tiene que implementar un lenguaje específico, como un lenguaje de consulta o un lenguaje de expresión. Permite evaluar sentencias o expresiones gramaticales de manera eficiente y modular.

3.4.4. Otros Patrones#

Observer (Observador):

El patrón Observer define una dependencia uno a muchos entre objetos, de manera que cuando un objeto cambia su estado, todos los objetos que dependen de él son notificados y actualizados automáticamente.

Strategy (Estrategia):

El patrón Strategy define una familia de algoritmos, encapsula cada uno de ellos y los hace intercambiables. Permite que el algoritmo varíe independientemente de los clientes que lo utilizan.

Iterator (Iterador):

El patrón Iterator proporciona una forma de acceder secuencialmente a los elementos de una colección sin exponer su representación subyacente.

State (Estado):

El patrón State permite que un objeto altere su comportamiento cuando su estado interno cambia. El objeto parecerá cambiar de clase.

Memento:

El patrón Memento permite capturar y restaurar el estado interno de un objeto sin violar la encapsulación.

Visitor (Visitante):

El patrón Visitor define una nueva operación para una colección de objetos sin cambiar las clases de los objetos en sí.

Template Method (Método Plantilla):

El patrón Template Method define el esqueleto de un algoritmo en una operación, dejando que las subclases redefinan ciertos pasos del algoritmo sin cambiar su estructura general.