Patron de commande Python

Le patron Commande est un patron de conception comportemental qui encapsule une requête sous forme d’objet, ce qui vous permet de paramétrer des clients avec différentes requêtes. En termes simples,le patron Commande enveloppe des opérations (comme l’ouverture de fichiers, l’enregistrement de données) dans des objets indépendants,ce qui vous permet de traiter les opérations de la même manière que vous traitez les données.

Analogie dans la vie quotidienne

Imaginez le processus de commande dans un restaurant :

  • Client(client) n'a pas besoin de savoir comment le cuisinier prépare le plat
  • Serveur(invocateur) reçoit la commande mais ne cuisine pas personnellement
  • Commande(objet commande) contient les informations spécifiques sur le plat
  • Cuisinier(récepteur) réalise les opérations de cuisson effectives en fonction de la commande

Ce modèle bien divisé est une manifestation concrète du patron de conception Commande.


Pourquoi avoir besoin du patron de conception Commande ?

Limites de l'approche traditionnelle

En programmation traditionnelle, nous appelons généralement directement les méthodes d'un objet :

Exemple

class Light:
    def turn_on(self):
        print("La lumière est allumée")
   
    def turn_off(self):
        print("La lumière est éteinte")

# Appel direct
light = Light()
light.turn_on()
light.turn_off()

Cette approche présente plusieurs problèmes :

  1. Couplage fort: L'appelant doit connaître l'interface spécifique du récepteur
  2. Difficile à étendre: L'ajout de nouvelles fonctionnalités nécessite de modifier le code existant
  3. Aucune prise en charge de l'annulation/rétablissement: Les opérations ne peuvent pas être annulées après exécution

Avantages du patron de conception Commande

Le patron de conception Commande résout les problèmes ci-dessus en introduisant une couche intermédiaire (objet commande), offrant :

  • Découplage: Aucune dépendance directe entre l'appelant et le récepteur
  • Extensibilité: Facile d'ajouter de nouvelles commandes
  • Prise en charge de l'annulation/rétablissement: Peut enregistrer l'historique des opérations
  • Prise en charge des files d'attente: les commandes peuvent être placées dans une file d'attente pour une exécution différée

Composants principaux du patron de conception Command

Structure de base

Le patron de conception Command comprend quatre rôles principaux :

Rôle Responsabilité Exemple
Command(Interface de commande) Déclare l'interface pour exécuter des opérations execute(), undo()
ConcreteCommand(Commande concrète) Implémente l'interface de commande et lie le récepteur LightOnCommand, LightOffCommand
Receiver(Récepteur) Sait comment effectuer des opérations Light, TV
Invoker(Invocateur) Stocke et exécute des commandes RemoteControl

Exemple de code complet

Implémentation de base

Comprenons le patron de conception Commande à travers un exemple de contrôle de l'éclairage d'une maison intelligente :

Exemple

from abc import ABC, abstractmethod
from typing import List

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

# 2. Récepteur - Dispositif d'éclairage
class Light:
    def turn_on(self):
        print("💡 La lumière est allumée")
   
    def turn_off(self):
        print("💡 La lumière est éteinte")

# 3. Commande concrète - Commande d'allumage de la lumière
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. Commande concrète - Commande d'extinction de la lumière
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. Invocateur - Télécommande
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()

# Code client
if __name__ == "__main__":
    # Créer un appareil
    living_room_light = Light()
   
    # Créer les commandes
    light_on = LightOnCommand(living_room_light)
    light_off = LightOffCommand(living_room_light)
   
    # Créer une télécommande
    remote = RemoteControl()
   
    # Test d'allumage de la lumière
    print("=== Test d'allumage de la lumière ===")
    remote.set_command(light_on)
    remote.press_button()
   
    # Test d'extinction de la lumière
    print("\n=== Test d'extinction de la lumière ===")
    remote.set_command(light_off)
    remote.press_button()
   
    # Test d'annulation
    print("\n=== Test de l'opération d'annulation ===")
    remote.press_undo()  # Annuler l'extinction de la lumière, devrait allumer la lumière
    remote.press_undo()  # Annuler l'allumage de la lumière, devrait éteindre la lumière

Résultat de l'exécution:


Exemple d'application avancée

Télécommande universelle prenant en charge plusieurs appareils

Étendons l'exemple précédent et créons une télécommande universelle qui prend en charge plusieurs appareils :

Exemple

# Nouvel appareil - TV
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} TV est allumée")
   
    def turn_off(self):
        self.is_on = False
        print(f"📺 {self.location} TV est éteinte")
   
    def set_volume(self, volume: int):
        self.volume = volume
        print(f"📺 {self.location} Volume de la TV réglé sur {volume}")

# Commandes liées à la TV
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)

# Commande macro - exécuter plusieurs commandes en une seule pression
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):
        # Annuler dans l'ordre inverse
        for command in reversed(self.commands):
            command.undo()

# Télécommande améliorée
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()

# Test de la télécommande avancée
def test_advanced_remote():
    print("=== Test de la télécommande universelle ===")
   
    # Créer des appareils
    living_room_light = Light()
    bedroom_tv = TV("Chambre")
   
    # Créer les commandes
    light_on = LightOnCommand(living_room_light)
    light_off = LightOffCommand(living_room_light)
    tv_on = TVOnCommand(bedroom_tv)
    tv_volume_up = TVVolumeUpCommand(bedroom_tv)
   
    # Créer une commande macro - mode cinéma
    cinema_mode = MacroCommand([light_off, tv_on, tv_volume_up])
   
    # Configurer la télécommande
    remote = AdvancedRemoteControl()
    remote.set_command(0, light_on, light_off)      # Emplacement 0 : contrôle de l'éclairage
    remote.set_command(1, tv_on, TVOnCommand(bedroom_tv))  # Emplacement 1 : contrôle de la TV
    remote.set_command(2, cinema_mode, light_on)    # Emplacement 2 : mode cinéma
   
    # Tester diverses fonctions
    print("\n1. Allumer la lumière :")
    remote.press_on_button(0)
   
    print("\n2. Activer le mode cinéma :")
    remote.press_on_button(2)
   
    print("\n3. Annuler l'opération :")
    remote.press_undo()

if __name__ == "__main__":
    test_advanced_remote()

Paramètres et options du patron de commande

Extension de l'interface de commande

Dans les applications pratiques, l'interface de commande peut être étendue avec plus de fonctionnalités selon les besoins :

Exemple

class AdvancedCommand(ABC):
    @abstractmethod
    def execute(self):
        """Exécuter la commande"""
        pass
   
    @abstractmethod
    def undo(self):
        """Annuler la commande"""
        pass
   
    @abstractmethod
    def redo(self):
        """Commande Refaire"""
        pass
   
    @abstractmethod
    def can_execute(self) -> bool:
        """Vérifier si la commande peut être exécutée"""
        pass
   
    @abstractmethod
    def get_description(self) -> str:
        """Obtenir la description de la commande"""
        pass

File d'attente de commandes et exécution différée

Le patron de conception Commande prend naturellement en charge les files d'attente de commandes, ce qui est très utile dans de nombreux scénarios :

Exemple

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()

Exercices pratiques

Exercice 1 : Implémenter un système de contrôle de climatisation

Essayez d'implémenter le patron de conception Commande pour un système de contrôle de climatisation :

Exemple

# Votre code ici
class AirConditioner:
    def __init__(self):
        self.temperature = 26
        self.is_on = False
   
    def turn_on(self):
        # Implémenter la mise en marche du climatiseur
        pass
   
    def turn_off(self):
        # Implémenter l'arrêt du climatiseur
        pass
   
    def set_temperature(self, temp: int):
        # Implémenter le réglage de la température
        pass

# Implémenter les classes de commande liées au climatiseur
# AirConditionerOnCommand
# AirConditionerOffCommand  
# TemperatureUpCommand
# TemperatureDownCommand

Exercice 2 : implémenter la fonctionnalité d'annulation pour un éditeur de texte

Créez un éditeur de texte simple qui prend en charge la saisie de texte et les opérations d'annulation :

Exemple

class TextEditor:
    def __init__(self):
        self.content = ""
   
    def add_text(self, text: str):
        # Implémenter l'ajout de texte
        pass
   
    def delete_text(self, length: int):
        # Implémenter la suppression de texte
        pass

# Implémenter les commandes d’édition de texte
# AddTextCommand
# DeleteTextCommand

FAQ

Q : À quels scénarios le patron de conception Commande est-il adapté ?

A:Le patron de conception Commande est particulièrement adapté aux scénarios suivants :

  • Lorsque les opérations doivent être paramétrées (par exemple, des boutons liés à différentes fonctions)
  • Lorsque la fonctionnalité d’annulation/rétablissement est nécessaire
  • Lorsque les opérations doivent être placées dans une file d’attente pour une exécution différée
  • Lorsque les journaux d’opérations doivent être enregistrés
  • Lorsque des opérations transactionnelles doivent être prises en charge

Q : Quels sont les inconvénients du patron Commande ?

A:Les principaux inconvénients incluent :

  • Il peut créer un grand nombre de classes de commande
  • Il augmente la complexité du code
  • Il peut être trop lourd pour des opérations simples

Q : Comment choisir d'utiliser ou non le patron Commande ?

A:Tenez compte des points suivants :

  • Si vous n'avez besoin que d'appels de méthode simples, n'utilisez pas le patron Commande
  • Si une fonctionnalité d'annulation/rétablissement est nécessaire, il est fortement recommandé
  • Si le système nécessite un fort découplage, le patron Commande est un bon choix
  • Si les opérations doivent être mises en file d'attente ou journalisées, le patron Commande est très adapté

Résumé

Le patron Commande découple l'appelant du récepteur en encapsulant les opérations sous forme d'objets, offrant une puissante extensibilité. Bien qu'il ajoute une certaine complexité au code, le patron Commande est un patron de conception indispensable dans des scénarios tels que l'annulation/rétablissement, les files d'attente d'opérations et la journalisation.

Points clés :

  • Le patron de conception Commande sépare « ce qu'il faut faire » de « qui l'exécute »
  • Prend en charge des fonctionnalités avancées telles que l'annulation, le rétablissement et les files d'attente
  • Facile à étendre avec de nouvelles commandes
  • Largement utilisé dans les applications GUI, les systèmes transactionnels et le développement de jeux
Autres extensions