web-dev-qa-db-fra.com

Modèle de commande: comment passer des paramètres à une commande?

Ma question est liée au modèle de commande, où nous avons l'abstraction suivante (code C #):

public interface ICommand
{
    void Execute();
}

Prenons une simple commande concrète, qui vise à supprimer une entité de notre application. Une instance Person, par exemple.

J'aurai un DeletePersonCommand, qui implémente ICommand. Cette commande nécessite la suppression de Person comme paramètre, afin de la supprimer lors de l'appel de la méthode Execute.

Quelle est la meilleure façon de gérer les commandes paramétrées? Comment passer des paramètres aux commandes, avant de les exécuter?

59
Romain Verdier

Vous devrez associer les paramètres à l'objet de commande, soit par injection constructeur ou setter (ou équivalent). Peut-être quelque chose comme ça:

public class DeletePersonCommand: ICommand
{
     private Person personToDelete;
     public DeletePersonCommand(Person personToDelete)
     {
         this.personToDelete = personToDelete;
     }

     public void Execute()
     {
        doSomethingWith(personToDelete);
     }
}
60
Blair Conrad

La transmission des données via un constructeur ou un setter fonctionne, mais nécessite que le créateur de la commande connaisse les données dont la commande a besoin ...

L'idée de "contexte" est vraiment bonne, et je travaillais sur un cadre (interne) qui en tirait parti il ​​y a quelque temps.

Si vous configurez votre contrôleur (composants d'interface utilisateur qui interagissent avec l'utilisateur, CLI interprétant les commandes utilisateur, servlet interprétant les paramètres entrants et les données de session, etc.) pour fournir un accès nommé aux données disponibles, les commandes peuvent demander directement les données qu'elles souhaitent.

J'aime vraiment la séparation que permet une configuration comme celle-ci. Pensez à la superposition comme suit:

User Interface (GUI controls, CLI, etc)
    |
[syncs with/gets data]
    V
Controller / Presentation Model
    |                    ^
[executes]               |
    V                    |
Commands --------> [gets data by name]
    |
[updates]
    V
Domain Model

Si vous faites cela "à droite", les mêmes commandes et le même modèle de présentation peuvent être utilisés avec n'importe quel type d'interface utilisateur.

En allant plus loin, le "contrôleur" ci-dessus est assez générique. Les contrôles de l'interface utilisateur doivent seulement connaître nom de la commande qu'ils invoqueront - ils (ou le contrôleur) n'ont pas besoin de savoir comment créer cette commande ou quelles données cette commande Besoins. C'est le vrai avantage ici.

Par exemple, vous pouvez conserver le nom de la commande à exécuter dans une carte. Chaque fois que le composant est "déclenché" (généralement une action exécutée), le contrôleur recherche le nom de la commande, l'instancie, appelle l'exécution et le pousse sur la pile d'annulation (si vous en utilisez un).

21
Scott Stanchfield

Il y a quelques options:

Vous pouvez transmettre des paramètres par propriétés ou par constructeur.

Une autre option pourrait être:

interface ICommand<T>
{
    void Execute(T args);
}

Et encapsulez tous les paramètres de commande dans un objet valeur.

10
Juanma

Passez la personne lorsque vous créez l'objet de commande:

ICommand command = new DeletePersonCommand(person);

de sorte que lorsque vous exécutez la commande, il sait déjà tout ce qu'il doit savoir.

class DeletePersonCommand : ICommand
{
   private Person person;
   public DeletePersonCommand(Person person)
   {
      this.person = person;
   }

   public void Execute()
   {
      RealDelete(person);
   }
}
6
jop

Ma mise en œuvre serait la suivante (en utilisant l'ICommand proposé par Juanma):

public class DeletePersonCommand: ICommand<Person>
{
    public DeletePersonCommand(IPersonService personService)
    {  
        this.personService = personService;
    }

    public void Execute(Person person)
    {
        this.personService.DeletePerson(person);
    }
}

IPersonService peut être un IPersonRepository, cela dépend de la "couche" de votre commande.

6
bloparod

Dans ce cas, ce que nous avons fait avec nos objets Command est de créer un objet Context qui est essentiellement une carte. La mappe contient des paires de valeurs de nom dans lesquelles les clés sont des constantes et les valeurs sont des paramètres utilisés par les implémentations de la commande. Particulièrement utile si vous avez une chaîne de commandes où les commandes ultérieures dépendent des changements de contexte des commandes précédentes.

Ainsi, la méthode réelle devient

void execute(Context ctx);
5
user12786

Dans le constructeur et stocké sous forme de champs.

Vous souhaiterez également éventuellement rendre vos ICommands sérialisables pour la pile d'annulation ou la persistance de fichier.

4
Frank Krueger

Sur la base du modèle dans C #/WPF, l'interface ICommand (System.Windows.Input.ICommand) est définie pour prendre un objet en tant que paramètre sur l'Execute, ainsi que la méthode CanExecute.

interface ICommand
            {
                bool CanExecute(object parameter);
                void Execute(object parameter);
            }

Cela vous permet de définir votre commande comme un champ public statique qui est une instance de votre objet de commande personnalisé qui implémente ICommand.

public static ICommand DeleteCommand = new DeleteCommandInstance();

De cette façon, l'objet pertinent, dans votre cas une personne, est transmis lors de l'appel de l'exécution. La méthode Execute peut ensuite transtyper l'objet et appeler la méthode Delete ().

public void Execute(object parameter)
            {
                person target = (person)parameter;
                target.Delete();
            } 
3
TheZenker

Vous devez créer un objet CommandArgs pour contenir les paramètres que vous souhaitez utiliser. Injectez l'objet CommandArgs à l'aide du constructeur de l'objet Command.

1
David Robbins

DeletePersonCommand peut avoir un paramètre dans son constructeur ou ses méthodes. DeletePersonCommand aura Execute () et dans l'exécution peut vérifier l'attribut qui sera passé par Getter/Setter précédemment l'appel de Execute ().

0
Patrick Desjardins

J'ajouterais tous les arguments nécessaires au constructeur de DeletePersonCommand. Ensuite, lorsque Execute() est appelée, ces paramètres passés à l'objet au moment de la construction sont utilisés.

0
Matt Dillard