Patrón de Comando en Python

El patrón de comando es un patrón de diseño de comportamiento que encapsula una solicitud en un objeto, lo que te permite parametrizar clientes con diferentes solicitudes. En términos simples,El patrón de comando envuelve operaciones (como abrir archivos, guardar datos) en objetos independientes, lo que te permite manejar operaciones como si fueran datos.

Analogía en la vida cotidiana

Imagina el proceso de pedido en un restaurante:

  • Cliente(el cliente) no necesita saber cómo cocina el chef
  • Camarero(el invocador) recibe el pedido pero no cocina personalmente
  • Pedido(el objeto de comando) contiene la información específica del plato
  • Chef(el receptor) ejecuta las operaciones de cocina específicas según el pedido

Este patrón con una división clara del trabajo es la manifestación del patrón de comando en la vida real.


¿Por qué se necesita el patrón de comando?

Limitaciones del enfoque tradicional

En la programación tradicional, generalmente llamamos directamente a los métodos de un objeto:

Ejemplo

class Light:
    def turn_on(self):
        print("La luz está encendida")
   
    def turn_off(self):
        print("La luz está apagada")

# Llamada directa
light = Light()
light.turn_on()
light.turn_off()

Esta forma tiene varios problemas:

  1. Acoplamiento fuerte: el invocador necesita conocer la interfaz específica del receptor
  2. Difícil de extender: agregar nuevas funciones requiere modificar el código existente
  3. No admite deshacer/rehacer: después de ejecutar la operación, no se puede revertir

Ventajas del patrón de comando

El patrón de comando resuelve los problemas anteriores al introducir una capa intermedia (el objeto de comando) y ofrece:

  • Desacoplamiento: no hay dependencia directa entre el invocador y el receptor
  • Extensibilidad: es fácil agregar nuevos comandos
  • Soporte para deshacer/rehacer: se puede registrar el historial de operaciones
  • Soporte de colas: se pueden poner los comandos en una cola para ejecutarlos de forma diferida

Componentes principales del patrón de comando

Estructura básica

El patrón de comando incluye cuatro roles principales:

Rol Responsabilidad Ejemplo
Command(Interfaz de comando) Declara la interfaz para ejecutar operaciones execute(), undo()
ConcreteCommand(Comando concreto) Implementa la interfaz de comando y se vincula al receptor LightOnCommand, LightOffCommand
Receiver(Receptor) Sabe cómo ejecutar la operación Light, TV
Invoker(Invocador) Almacena y ejecuta comandos RemoteControl

Ejemplo de código completo

Implementación básica

Entendamos el patrón de comando mediante un ejemplo de control de luces en un hogar inteligente:

Ejemplo

from abc import ABC, abstractmethod
from typing import List

# 1. Interfaz de comando
class Command(ABC):
    @abstractmethod
    def execute(self):
        pass
   
    @abstractmethod
    def undo(self):
        pass

# 2. Receptor - Dispositivo de luz
class Light:
    def turn_on(self):
        print("💡 Luz encendida")
   
    def turn_off(self):
        print("💡 Luz apagada")

# 3. Comando concreto - Comando para encender la luz
class LightOnCommand(Command):
    def __init__(self, light: Light):
        self.light = light
   
    def execute(self):
        self.light.turn_on()
   
    def undo(self):
        self.light.turn_off()

# 4. Comando concreto - Comando para apagar la luz
class LightOffCommand(Command):
    def __init__(self, light: Light):
        self.light = light
   
    def execute(self):
        self.light.turn_off()
   
    def undo(self):
        self.light.turn_on()

# 5. Invocador - Control remoto
class RemoteControl:
    def __init__(self):
        self.command = None
        self.history: List[Command] = []
   
    def set_command(self, command: Command):
        self.command = command
   
    def press_button(self):
        if self.command:
            self.command.execute()
            self.history.append(self.command)
   
    def press_undo(self):
        if self.history:
            last_command = self.history.pop()
            last_command.undo()

# Código del cliente
if __name__ == "__main__":
    # Crear dispositivo
    living_room_light = Light()
   
    # Crear comando
    light_on = LightOnCommand(living_room_light)
    light_off = LightOffCommand(living_room_light)
   
    # Crear control remoto
    remote = RemoteControl()
   
    # Probar encendido de la luz
    print("=== Prueba de encendido ===")
    remote.set_command(light_on)
    remote.press_button()
   
    # Probar apagado de la luz
    print("\n=== Prueba de apagado ===")
    remote.set_command(light_off)
    remote.press_button()
   
    # Probar deshacer
    print("\n=== Prueba de operación de deshacer ===)
    remote.press_undo()  # Deshacer apagado de la luz, debería encenderse
    remote.press_undo()  # Deshacer encendido de la luz, debería apagarse

Resultado de la ejecución:


Ejemplo de aplicación avanzada

Control remoto universal compatible con múltiples dispositivos

Ampliemos el ejemplo anterior para crear un control remoto universal que admita múltiples dispositivos:

Ejemplo

# Nuevo dispositivo - Televisor
class TV:
    def __init__(self, location: str):
        self.location = location
        self.is_on = False
        self.volume = 50
   
    def turn_on(self):
        self.is_on = True
        print(f"📺 {self.location} Televisor encendido")
   
    def turn_off(self):
        self.is_on = False
        print(f"📺 {self.location} Televisor apagado")
   
    def set_volume(self, volume: int):
        self.volume = volume
        print(f"📺 {self.location} Volumen del televisor establecido en {volume}")

# Comandos relacionados con el televisor
class TVOnCommand(Command):
    def __init__(self, tv: TV):
        self.tv = tv
        self.previous_volume = 50
   
    def execute(self):
        self.previous_volume = self.tv.volume
        self.tv.turn_on()
   
    def undo(self):
        self.tv.turn_off()
        self.tv.volume = self.previous_volume

class TVVolumeUpCommand(Command):
    def __init__(self, tv: TV):
        self.tv = tv
        self.previous_volume = 50
   
    def execute(self):
        self.previous_volume = self.tv.volume
        self.tv.set_volume(min(100, self.tv.volume + 10))
   
    def undo(self):
        self.tv.set_volume(self.previous_volume)

# Macrocomando - Ejecutar múltiples comandos con un solo clic
class MacroCommand(Command):
    def __init__(self, commands: List[Command]):
        self.commands = commands
   
    def execute(self):
        for command in self.commands:
            command.execute()
   
    def undo(self):
        # Deshacer en orden inverso
        for command in reversed(self.commands):
            command.undo()

# Control remoto mejorado
class AdvancedRemoteControl:
    def __init__(self, slot_count: int = 4):
        self.on_commands: List[Command] = [None] * slot_count
        self.off_commands: List[Command] = [None] * slot_count
        self.history: List[Command] = []
   
    def set_command(self, slot: int, on_command: Command, off_command: Command):
        self.on_commands[slot] = on_command
        self.off_commands[slot] = off_command
   
    def press_on_button(self, slot: int):
        if self.on_commands[slot]:
            self.on_commands[slot].execute()
            self.history.append(self.on_commands[slot])
   
    def press_off_button(self, slot: int):
        if self.off_commands[slot]:
            self.off_commands[slot].execute()
            self.history.append(self.off_commands[slot])
   
    def press_undo(self):
        if self.history:
            last_command = self.history.pop()
            last_command.undo()

# Probar control remoto avanzado
def test_advanced_remote():
    print("=== Prueba del control remoto universal ===")
   
    # Crear dispositivo
    living_room_light = Light()
    bedroom_tv = TV("Dormitorio")
   
    # Crear comando
    light_on = LightOnCommand(living_room_light)
    light_off = LightOffCommand(living_room_light)
    tv_on = TVOnCommand(bedroom_tv)
    tv_volume_up = TVVolumeUpCommand(bedroom_tv)
   
    # Crear macrocomando - Modo cine
    cinema_mode = MacroCommand([light_off, tv_on, tv_volume_up])
   
    # Configurar control remoto
    remote = AdvancedRemoteControl()
    remote.set_command(0, light_on, light_off)      # Ranura 0: Control de luz
    remote.set_command(1, tv_on, TVOnCommand(bedroom_tv))  # Ranura 1: Control del televisor
    remote.set_command(2, cinema_mode, light_on)    # Ranura 2: Modo cine
   
    # Probar varias funciones
    print("\n1. Encender la luz:)
    remote.press_on_button(0)
   
    print("\n2. Activar modo cine:)
    remote.press_on_button(2)
   
    print("\n3. Deshacer operación:)
    remote.press_undo()

if __name__ == "__main__":
    test_advanced_remote()

Parámetros y opciones del patrón de comando

Extensión de la interfaz de comando

En aplicaciones reales, la interfaz de comando se puede extender con más funciones según sea necesario:

Ejemplo

class AdvancedCommand(ABC):
    @abstractmethod
    def execute(self):
        """Ejecutar comando"""
        pass
   
    @abstractmethod
    def undo(self):
        """Deshacer comando"""
        pass
   
    @abstractmethod
    def redo(self):
        """Rehacer comando"""
        pass
   
    @abstractmethod
    def can_execute(self) -> bool:
        """Verificar si el comando puede ejecutarse"""
        pass
   
    @abstractmethod
    def get_description(self) -> str:
        """Obtener la descripción del comando"""
        pass

Cola de comandos y ejecución diferida

El patrón de comando admite de forma natural colas de comandos, lo cual es muy útil en muchos escenarios:

Ejemplo

class CommandQueue:
    def __init__(self):
        self.queue: List[Command] = []
   
    def add_command(self, command: Command):
        self.queue.append(command)
   
    def process_commands(self):
        while self.queue:
            command = self.queue.pop(0)
            if command.can_execute():
                command.execute()
   
    def clear(self):
        self.queue.clear()

Ejercicios prácticos

Ejercicio 1: Implementar un sistema de control de aire acondicionado

Intenta implementar el patrón de comando para un sistema de control de aire acondicionado:

Ejemplo

# Tu código aquí
class AirConditioner:
    def __init__(self):
        self.temperature = 26
        self.is_on = False
   
    def turn_on(self):
        # Implementar encendido del aire acondicionado
        pass
   
    def turn_off(self):
        # Implementar apagado del aire acondicionado
        pass
   
    def set_temperature(self, temp: int):
        # Implementar ajuste de temperatura
        pass

# Implementar clases de comando relacionadas con el aire acondicionado
# AirConditionerOnCommand
# AirConditionerOffCommand  
# TemperatureUpCommand
# TemperatureDownCommand

Ejercicio 2: Implementar la función de deshacer de un editor de texto

Crea un editor de texto simple que admita ingresar texto y operaciones de deshacer:

Ejemplo

class TextEditor:
    def __init__(self):
        self.content = ""
   
    def add_text(self, text: str):
        # Implementar agregar texto
        pass
   
    def delete_text(self, length: int):
        # Implementar eliminar texto
        pass

# Implementar comandos de edición de texto
# AddTextCommand
# DeleteTextCommand

Preguntas frecuentes

Q: ¿Para qué escenarios es adecuado el patrón de comando?

A:El patrón de comando es especialmente adecuado para los siguientes escenarios:

  • Necesidad de parametrizar operaciones (por ejemplo, vincular diferentes funciones a botones)
  • Necesidad de admitir funciones de deshacer/rehacer
  • Necesidad de poner operaciones en una cola para ejecutarlas de forma diferida
  • Necesidad de llevar un registro de operaciones
  • Necesidad de admitir operaciones transaccionales

Q: ¿Cuáles son las desventajas del patrón de comando?

A:Las principales desventajas incluyen:

  • Puede crear una gran cantidad de clases de comando
  • Aumenta la complejidad del código
  • Puede resultar demasiado engorroso para operaciones simples

Q: ¿Cómo decidir si usar el patrón de comando?

A:Considera los siguientes puntos:

  • Si solo necesitas llamadas simples a métodos, no uses el patrón de comando
  • Si necesitas funciones de deshacer/rehacer, se recomienda encarecidamente su uso
  • Si el sistema necesita un alto desacoplamiento, el patrón de comando es una excelente opción
  • Si las operaciones necesitan colas o registro de logs, el patrón de comando es muy adecuado

Resumen

El patrón de comando encapsula operaciones como objetos, desacoplando al invocador del receptor y proporcionando una gran capacidad de extensión. Aunque añade cierta complejidad al código, en escenarios que requieren deshacer/rehacer, colas de operaciones, registro de registros, etc., el patrón de comando es un patrón de diseño indispensable.

Puntos clave:

  • El patrón de comando separa el "qué hacer" del "quién lo hace"
  • Soporta funciones avanzadas como deshacer, rehacer y colas
  • Fácil de extender con nuevos comandos
  • Ampliamente utilizado en aplicaciones GUI, sistemas transaccionales y desarrollo de juegos
Otras extensiones