Le modèle de commande en Java

Le modèle de commande en Java

1. Vue d'ensemble

Le modèle de commande est un modèle de conception comportementale et fait partie de la liste formelle des modèles de conception deGoF. En termes simples, le modèle a l'intention de encapsulate in an object all the data required for performing a given action (command),, y compris la méthode à appeler, les arguments de la méthode et l'objet auquel appartient la méthode.

Ce modèle nous permet dedecouple objects that produce the commands from their consumers, c'est pourquoi le modèle est communément appelé modèle producteur-consommateur.

Dans ce didacticiel, nous allons apprendre à implémenter le modèle de commande en Java en utilisant à la fois des approches orientées objet et fonctionnelles objet, et nous verrons dans quels cas d'utilisation cela peut être utile.

2. Implémentation orientée objet

Dans une implémentation classique, le modèle de commande nécessiteimplementing four components: the Command, the Receiver, the Invoker, and the Client.

Pour comprendre le fonctionnement du modèle et le rôle joué par chaque composant, créons un exemple de base.

Supposons que nous souhaitons développer une application de fichier texte. Dans un tel cas, nous devrions implémenter toutes les fonctionnalités requises pour effectuer certaines opérations liées aux fichiers texte, telles que l'ouverture, l'écriture, la sauvegarde d'un fichier texte, etc.

Nous devrions donc décomposer l’application en quatre composantes mentionnées ci-dessus.

2.1. Classes de commande

Une commande est un objet dont le rôle est destore all the information required for executing an action, y compris la méthode à appeler, les arguments de méthode et l'objet (appelé récepteur) qui implémente la méthode.

Pour avoir une idée plus précise du fonctionnement des objets de commande, commençons à développer une couche de commande simple qui comprend une seule interface et deux implémentations:

@FunctionalInterface
public interface TextFileOperation {
    String execute();
}
public class OpenTextFileOperation implements TextFileOperation {

    private TextFile textFile;

    // constructors

    @Override
    public String execute() {
        return textFile.open();
    }
}
public class SaveTextFileOperation implements TextFileOperation {

    // same field and constructor as above

    @Override
    public String execute() {
        return textFile.save();
    }
}

Dans ce cas, l'interfaceTextFileOperation définit l'API des objets de commande, et les deux implémentations,OpenTextFileOperation etSaveTextFileOperation, exécutent les actions concrètes. Le premier ouvre un fichier texte, tandis que le second enregistre un fichier texte.

Il est clair de voir la fonctionnalité d'un objet de commande: les commandesTextFileOperationencapsulate all the information required pour ouvrir et enregistrer un fichier texte, y compris l'objet récepteur, les méthodes à appeler et les arguments (dans ce cas, non des arguments sont nécessaires, mais ils pourraient l'être).

Il vaut la peine de souligner quethe component that performs the file operations is the receiver (the TextFile instance).

2.2. La classe des récepteurs

Un récepteur est un objet queperforms a set of cohesive actions. C’est le composant qui exécute l’action réelle lorsque la méthodeexecute() de la commande est appelée.

Dans ce cas, nous devons définir une classe de récepteur, dont le rôle est de modéliser les objetsTextFile:

public class TextFile {

    private String name;

    // constructor

    public String open() {
        return "Opening file " + name;
    }

    public String save() {
        return "Saving file " + name;
    }

    // additional text file methods (editing, writing, copying, pasting)
}

2.3. La classe Invoker

Un invocateur est un objet queknows how to execute a given command but doesn’t know how the command has been implemented. Il ne connaît que l'interface de la commande.

Dans certains cas, l'invocateur stocke et met également les commandes en file d'attente, en plus de les exécuter. Ceci est utile pour implémenter des fonctionnalités supplémentaires, telles que l'enregistrement de macros ou les fonctionnalités d'annulation et de rétablissement.

Dans notre exemple, il devient évident qu'il doit y avoir un composant supplémentaire chargé d'appeler les objets de commande et de les exécuter via la méthode des commandes 'execute(). This is exactly where the invoker class comes into play.

Examinons une implémentation de base de notre invocateur:

public class TextFileOperationExecutor {

    private final List textFileOperations
     = new ArrayList<>();

    public String executeOperation(TextFileOperation textFileOperation) {
        textFileOperations.add(textFileOperation);
        return textFileOperation.execute();
    }
}

La classeTextFileOperationExecutor est juste unthin layer of abstraction that decouples the command objects from their consumers et appelle la méthode encapsulée dans les objets de commandeTextFileOperation.

Dans ce cas, la classe stocke également les objets de commande dans unList. Bien sûr, ceci n’est pas obligatoire dans l’implémentation du modèle, sauf si nous devons ajouter un contrôle supplémentaire au processus d’exécution des opérations.

2.4. La classe client

Un client est un objet quicontrols the command execution process en spécifiant quelles commandes exécuter et à quelles étapes du processus pour les exécuter.

Donc, si nous voulons être orthodoxes avec la définition formelle du modèle, nous devons créer une classe client en utilisant la méthode typiquemain:

public static void main(String[] args) {
    TextFileOperationExecutor textFileOperationExecutor
      = new TextFileOperationExecutor();
    textFileOperationExecutor.executeOperation(
      new OpenTextFileOperation(new TextFile("file1.txt"))));
    textFileOperationExecutor.executeOperation(
      new SaveTextFileOperation(new TextFile("file2.txt"))));
}

3. Implémentation fonctionnelle objet

Jusqu'à présent, nous avons utilisé une approche orientée objet pour implémenter le modèle de commande, ce qui est très bien.

À partir de Java 8, nous pouvons utiliser une approche fonctionnelle objet, basée sur des expressions lambda et des références de méthodes, àmake the code a little bit more compact and less verbose.

3.1. Utilisation d'expressions Lambda

Comme l'interfaceTextFileOperation est unfunctional interface, nous pouvonspass command objects in the form of lambda expressions to the invoker, sans avoir à créer explicitement les instancesTextFileOperation:

TextFileOperationExecutor textFileOperationExecutor
 = new TextFileOperationExecutor();
textFileOperationExecutor.executeOperation(() -> "Opening file file1.txt");
textFileOperationExecutor.executeOperation(() -> "Saving file file1.txt");

La mise en œuvre semble maintenant beaucoup plus simple et concise, car nous avonsreduced the amount of boilerplate code.

Même dans ce cas, la question reste posée: cette approche est-elle meilleure par rapport à l'approche orientée objet?

Eh bien, c’est délicat. Si nous supposons qu'un code plus compact signifie un meilleur code dans la plupart des cas, c'est bien le cas.

As a rule of thumb, we should evaluate on a per-use-case basis when to resort to lambda expressions.

3.2. Utilisation des références de méthode

De même, nous pouvons utiliser des références de méthode pourpassing command objects to the invoker:

TextFileOperationExecutor textFileOperationExecutor
 = new TextFileOperationExecutor();
TextFile textFile = new TextFile("file1.txt");
textFileOperationExecutor.executeOperation(textFile::open);
textFileOperationExecutor.executeOperation(textFile::save);

Dans ce cas, l'implémentation esta little bit more verbose than the one that uses lambdas, car nous devions encore créer les instancesTextFile.

4. Conclusion

Dans cet article, nous avons appris les concepts clés du modèle de commande et comment l'implémenter en Java à l'aide d'une approche orientée objet et d'une combinaison d'expressions lambda et de références de méthode.

Comme d'habitude, tous les exemples de code présentés dans ce didacticiel sont disponiblesover on GitHub.