Menu
Coddy logo textTech

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 luz

Salida:

Light is on
Light is off
Light is on
Light is off

Punto 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.

challenge icon

Desafío

Fácil

En 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 lanzar AttributeError)
  • 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)}")
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Object Oriented Programming