Patrón Command
Parte de la sección Object Oriented Programming del Journey de Python de Coddy — lección 49 de 64.
El patrón Command encapsula una solicitud como un objeto, lo que permite poner operaciones en cola, registrar solicitudes y admitir la funcionalidad de deshacer. Separa el objeto que invoca la operación del que la realiza.
Aquí hay clases de comando simples:
class Command:
def execute(self):
pass
class LightOnCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_on()
class LightOffCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_off()Cada comando encapsula una operación específica en un objeto receptor.
Crea el receptor que realiza el trabajo real:
class Light:
def turn_on(self):
print("Light is on")
def turn_off(self):
print("Light is off")Crea un invocador que ejecute comandos:
class RemoteControl:
def __init__(self):
self.command = None
def set_command(self, command):
self.command = command
def press_button(self):
self.command.execute()Usa el patrón command:
# Crear el receptor
light = Light()
# Crear los comandos
light_on = LightOnCommand(light)
light_off = LightOffCommand(light)
# Crear el invocador
remote = RemoteControl()
# Ejecutar diferentes comandos
remote.set_command(light_on)
remote.press_button()
remote.set_command(light_off)
remote.press_button()Añade soporte para operaciones de deshacer:
class UndoableCommand(Command):
def undo(self):
pass
class LightOnCommand(UndoableCommand):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_on()
def undo(self):
self.light.turn_off()
class SmartRemote:
def __init__(self):
self.last_command = None
def execute_command(self, command):
command.execute()
self.last_command = command
def undo(self):
if self.last_command:
self.last_command.undo()
smart_remote = SmartRemote()
smart_remote.execute_command(LightOnCommand(light))
smart_remote.undo() # Apaga la luzSalida:
Light is on
Light is off
Light is on
Light is offPunto clave: El patrón Command convierte las solicitudes en objetos que pueden almacenarse, pasarse y ejecutarse más tarde. El invocador no necesita saber cómo realizar la operación; simplemente llama a execute() en el objeto de comando. Esto permite funciones como deshacer/rehacer, poner operaciones en cola y registrar comandos.
Desafío
FácilEn este desafío, implementarás la clase base fundamental Command en command.py, el bloque de construcción esencial del Patrón Command. Este ejercicio se centra específicamente en la encapsulación: almacenar datos de forma privada y exponerlos de forma segura a través de una propiedad.
Solo modifica command.py de acuerdo con los comentarios TODO. Los comentarios TODO te guiarán para:
- Almacenar el nombre del comando como un atributo privado (
_name) - Exponerlo a través de una propiedad de solo lectura (
name) - Implementar un método
display_info()que imprima el nombre del comando
Nota: Este desafío cubre únicamente la clase base Command; el Invoker, el Receiver y la funcionalidad de deshacer se introducen en lecciones posteriores. Completar este paso te brinda la base de encapsulación sobre la cual se construye el patrón completo.
Tu implementación será probada por driver.py, que valida:
- La funcionalidad básica y el formato de salida
- Casos de borde (entradas vacías, caracteres especiales, nombres largos)
- Protección de propiedad de solo lectura (intentar
obj.name = ...debería lanzarAttributeError) - Presencia del atributo privado
_name
Hoja de referencia
El patrón Command encapsula una solicitud como un objeto, lo que permite poner en cola operaciones, registrar solicitudes y admitir la funcionalidad de deshacer.
Estructura básica de un comando:
class Command:
def execute(self):
pass
class LightOnCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_on()
class LightOffCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_off()Crea un receptor que realice el trabajo real:
class Light:
def turn_on(self):
print("Light is on")
def turn_off(self):
print("Light is off")Crea un invocador que ejecute comandos:
class RemoteControl:
def __init__(self):
self.command = None
def set_command(self, command):
self.command = command
def press_button(self):
self.command.execute()Ejemplo de uso:
# Crear receptor
light = Light()
# Crear comandos
light_on = LightOnCommand(light)
light_off = LightOffCommand(light)
# Crear invocador
remote = RemoteControl()
# Ejecutar diferentes comandos
remote.set_command(light_on)
remote.press_button()
remote.set_command(light_off)
remote.press_button()Añadir funcionalidad de deshacer:
class UndoableCommand(Command):
def undo(self):
pass
class LightOnCommand(UndoableCommand):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_on()
def undo(self):
self.light.turn_off()
class SmartRemote:
def __init__(self):
self.last_command = None
def execute_command(self, command):
command.execute()
self.last_command = command
def undo(self):
if self.last_command:
self.last_command.undo()Pruébalo tú mismo
# Importar la clase Command de command.py
from command import Command
# Manejador integral de casos de prueba
test_case = input()
if test_case == "basic_test":
obj = Command("Test Name")
obj.display_info()
elif test_case == "validation_test":
obj = Command("Validation Test")
print(f"Name: {obj.name}")
elif test_case == "empty_name_test":
obj = Command("")
obj.display_info()
print(f"Empty name handled: {'Yes' if obj.name == '' else 'No'}")
elif test_case == "property_access_test":
obj = Command("Property Test")
original_name = obj.name
try:
# Esto debería fallar ya que name es una propiedad de solo lectura
obj.name = "Modified Name"
print("Property protection failed")
except AttributeError:
print("Property protected successfully")
print(f"Name unchanged: {obj.name == original_name}")
elif test_case == "multiple_commands_test":
commands = [
Command("First Command"),
Command("Second Command"),
Command("Third Command")
]
for cmd in commands:
cmd.display_info()
elif test_case == "attribute_test":
obj = Command("Attribute Test")
has_private_name = hasattr(obj, "_name")
print(f"Has _name attribute: {has_private_name}")
elif test_case == "special_chars_test":
obj = Command("!@#$%^&*()_+{}[]|\\:;\"'<>,.?/")
obj.display_info()
elif test_case == "long_name_test":
long_name = "A" * 100
obj = Command(long_name)
obj.display_info()
print(f"Name length: {len(obj.name)}")Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Object Oriented Programming
1Fundamentos de la POO
Archivos externosIntroducción a la POOClases vs ObjetosEl parámetro selfMétodosAtributosMétodo constructor (__init__)Resumen - Calculadora simple4Herencia
Herencia básicaLa función super()Sobrescritura de métodosHerencia múltipleOrden de resolución de métodosResumen - Jerarquía de empleados7Métodos especiales
Introducción a los métodos mágicosSobrecarga de operadoresMétodos mágicos de contenedoresRepaso - Lista personalizada10Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy2Decoradores
Introducción a los decoradoresDecorador propertyDecorador de métodos estáticosDecorador de métodos de clase5Polimorfismo
Sobreescritura de métodos revisitadaDuck TypingClases y métodos abstractosDiseño de interfacesResumen - Calculadora de figuras8Conceptos avanzados de POO
Composición vs. HerenciaMixinsMétodos estáticos y de claseDecoradores de claseGestores de contexto11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Propiedades de clase
Variables de instancia vs de claseDecoradores de propiedadesAtributos privadosResumen - Gestor de cuentas bancarias