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.
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
yConcreteHandler2
son implementaciones concretas deHandler
que manejan solicitudes específicas o pasan las solicitudes al siguiente manejador en la cadena si no pueden manejarlas.Cada
ConcreteHandler
tiene un métodoset_next()
para establecer el siguiente manejador en la cadena y un métodohandle()
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.
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étodoexecute()
.LightOnCommand
es una implementación concreta deCommand
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 objetoCommand
y puede ejecutar el métodoexecute()
delCommand
.El cliente crea una instancia de
Light
,LightOnCommand
, yRemoteControl
, configura elRemoteControl
con elLightOnCommand
, y finalmente presiona el botón en elRemoteControl
.
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.
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étodointerpret()
que todas las expresiones deben implementar.NumberExpression
es una expresión terminal que interpreta un número.AddExpression
ySubtractExpression
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.