Menu
Coddy logo textTech

Patrón Command

Parte de la sección Programación orientada a objetos del Journey de Python de Coddy. Lección 49 de 64.

El patrón Command encapsula una solicitud como un objeto, lo que te permite poner operaciones en cola, registrar solicitudes y admitir la funcionalidad de deshacer. Separa el objeto que llama a la operación del que la ejecuta.

Aquí tienes clases de comandos sencillas:

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 de comandos:

# 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ñade compatibilidad con 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 se pueden almacenar, transferir y ejecutar 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 Command fundamental en command.py: el componente esencial del patrón Command. Este ejercicio se centra específicamente en la encapsulación: almacenar datos de forma privada y exponerlos de manera segura mediante una propiedad.

Modifica únicamente 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 mediante una propiedad de solo lectura (name)
  • Implementar un método display_info() que muestre el nombre del comando

Nota: Este desafío abarca únicamente la clase base Command: el Invoker, el Receiver y la funcionalidad de deshacer se introducirán en lecciones posteriores. Completar este paso te proporciona los fundamentos de encapsulación sobre los que 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 límite (entradas vacías, caracteres especiales, nombres largos)
  • La protección de la propiedad de solo lectura (intentar obj.name = ... debería generar AttributeError)
  • La presencia del atributo privado _name

Pruébalo tú mismo

# Importa la clase Command desde 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 Programación orientada a objetos

Practica por tu cuenta: Compilador de Python online