web-dev-qa-db-fra.com

Pourquoi devrais-je utiliser le modèle de conception de commande alors que je peux facilement appeler les méthodes requises?

J'étudie le modèle de conception de commande , et je suis assez confus avec la façon de l'utiliser. L'exemple que j'ai est lié à une classe de télécommande utilisée pour allumer et éteindre les lumières.

Pourquoi ne devrais-je pas utiliser les méthodes switchOn ()/switchOff () de la classe Light plutôt que d'avoir des classes et des méthodes distinctes qui appellent finalement les méthodes switchOn/switchOff?

Je sais que mon exemple est assez simple, mais c'est le point. Je n'ai trouvé aucun problème complexe nulle part sur Internet pour voir l'utilisation exacte du modèle de conception de commande.

Si vous êtes au courant d'un problème complexe du monde réel que vous avez résolu et qui peut être résolu en utilisant ce modèle de conception, veuillez le partager avec moi. Cela m'aide ainsi que les futurs lecteurs de ce message à mieux comprendre l'utilisation de ce modèle de conception. Merci

//Command
public interface Command {
  public void execute();
}

//Concrete Command
public class LightOnCommand implements Command {

  //Reference to the light
  Light light;

  public LightOnCommand(Light light) {
    this.light = light;
  }

  public void execute() {
    light.switchOn();        //Explicit call of selected class's method
  }
}

//Concrete Command
public class LightOffCommand implements Command {

  //Reference to the light
  Light light;

  public LightOffCommand(Light light) {
    this.light = light;
  }

  public void execute() {
    light.switchOff();
  }
}

//Receiver
public class Light {
  private boolean on;

  public void switchOn() {
    on = true;
  }

  public void switchOff() {
    on = false;
  }
}

//Invoker
public class RemoteControl {
  private Command command;

  public void setCommand(Command command) {
    this.command = command;
  }

  public void pressButton() {
    command.execute();
  }
}

//Client
public class Client {
  public static void main(String[] args) {
    RemoteControl control = new RemoteControl();
    Light light = new Light();
    Command lightsOn = new LightsOnCommand(light);
    Command lightsOff = new LightsOffCommand(light);

    //Switch on
    control.setCommand(lightsOn);
    control.pressButton();

    //Switch off
    control.setCommand(lightsOff);
    control.pressButton();
  }
}

Pourquoi ne devrais-je pas facilement utiliser du code comme celui-ci?

 Light light = new Light();
 switch(light.command) {
  case 1:
    light.switchOn();
    break;
  case 2:
    light.switchOff();
    break;
 }
45
Daniel Newtown

La principale motivation pour utiliser le modèle Command est que l'exécuteur de la commande n'a pas besoin de savoir quoi que ce soit sur la commande, les informations contextuelles dont elle a besoin ou ce qu'elle fait. Tout cela est encapsulé dans la commande.

Cela vous permet de faire des choses comme avoir une liste de commandes qui sont exécutées dans l'ordre, qui dépendent d'autres éléments, qui sont affectées à un événement déclencheur, etc.

Dans votre exemple, vous pouvez avoir d'autres classes (par exemple Air Conditioner) qui ont leurs propres commandes (par exemple Turn Thermostat Up, Turn Thermostat Down). Chacune de ces commandes peut être affectée à un bouton ou déclenchée lorsqu'une condition est remplie sans nécessiter aucune connaissance de la commande.

Donc, en résumé, le modèle encapsule tout ce qui est nécessaire pour entreprendre une action et permet à l'exécution de l'action de se produire complètement indépendamment de tout ce contexte. Si ce n'est pas une exigence pour vous, le motif n'est probablement pas utile pour votre espace problématique.

Voici un cas d'utilisation simple:

interface Command {
    void execute();
}

class Light {
    public Command turnOn();
    public Command turnOff();
}

class AirConditioner {
    public Command setThermostat(Temperature temperature);
}

class Button {
    public Button(String text, Command onPush);
}

class Scheduler {
    public void addScheduledCommand(Time timeToExecute, Command command);
}

Ensuite, vous pouvez faire des choses telles que:

new Button("Turn on light", light.turnOn());
scheduler.addScheduledCommand(new Time("15:12:07"), airCon.setThermostat(27));
scheduler.addScheduledCommand(new Time("15:13:02"), light.turnOff());

Comme vous pouvez le voir, les Button et Scheduler n'ont pas besoin de savoir quoi que ce soit sur les commandes. Scheduler est un exemple de classe pouvant contenir une collection de commandes.

Notez également que dans Java 8 interfaces fonctionnelles et références de méthode ont rendu ce type de code encore plus soigné:

@FunctionalInterface
interface Command {
    void execute();
}

public Light {
    public void turnOn();
}

new Button("Turn On Light", light::turnOn);   

Désormais, les méthodes qui sont transformées en commandes n'ont même plus besoin de connaître les commandes - tant qu'elles ont la signature correcte, vous pouvez créer tranquillement un objet de commande anonyme en référençant la méthode.

41
sprinter

Concentrons-nous sur l'aspect non-implémentation de la conception de la commande et sur les principales raisons d'utiliser le modèle de conception de commande regroupé en deux grandes catégories:

  • Masquage de l'implémentation réelle de l'exécution de la commande
  • Autoriser les méthodes à construire autour de la commande, alias extensions de commande

Masquer l'implémentation

Dans la plupart des programmes, vous voudrez cacher l'implémentation de sorte que lorsque vous regardez le problème le plus élevé, il se compose d'un sous-ensemble compréhensible de commandes/code. C'est à dire. Vous n'avez pas besoin/ne voulez pas connaître les détails sanglants de la façon dont une lumière est allumée ou une voiture démarrée. Si votre objectif est de démarrer la voiture, vous n'avez pas besoin de comprendre comment fonctionne le moteur, comment il a besoin de carburant pour entrer dans le moteur, comment les soupapes fonctionnent, ...

Indiquer l'action, pas comment elle se fait

Une commande vous donne ce genre de vue. Vous comprendrez immédiatement ce que fait la commande TurnLightOn ou StartCar. En utilisant une commande, vous cacherez les détails de la façon dont quelque chose est fait, tout en indiquant clairement l'action à exécuter.

Autoriser le changement des détails intérieurs

De plus, disons que vous reconstruisez plus tard votre classe Light entière, ou une classe Car, ce qui vous oblige à instancier plusieurs objets différents, et peut-être avez-vous besoin d'autre chose avant de faire votre opération souhaitée . Dans ce cas, si vous aviez implémenté la méthode d'accès direct, dans de nombreux endroits, vous devrez la modifier dans tous les endroits où vous l'avez préalablement codée. En utilisant une commande, vous pouvez changer les détails intérieurs de la façon de faire des choses sans changer l'appel de la commande.

Extensions de commande possibles

L'utilisation d'une interface de commande vous offre une couche supplémentaire entre le code utilisant la commande et le code effectuant l'action réelle de la commande. Cela peut permettre plusieurs bons scénarios.

Extension de sécurité ou exposition d'interface

À l'aide d'une interface de commande, vous pouvez également limiter l'accès à vos objets, vous permettant de définir un autre niveau de sécurité. Il pourrait être judicieux d'avoir un module/bibliothèque avec un accès assez ouvert afin que vous puissiez gérer facilement les cas spéciaux internes.

De l'extérieur, cependant, vous souhaiterez peut-être restreindre l'accès à la lumière afin qu'elle ne soit allumée ou éteinte. L'utilisation de commandes vous donne la possibilité de limiter l'interface vers une classe .

De plus, si vous le souhaitez, vous pouvez créer un système d'accès utilisateur dédié autour des commandes. Cela laisserait toute votre logique métier ouverte et accessible et sans restrictions, mais vous pourriez toujours restreindre facilement l'accès au niveau de la commande pour imposer un accès approprié.

Interface commune pour exécuter des trucs

Lors de la construction d'un système suffisamment grand, les commandes offrent un moyen efficace de faire le pont entre différents modules/bibliothèques. Au lieu de devoir examiner chaque détail d'implémentation d'une classe donnée, vous pouvez voir quelles commandes accèdent à la classe.

Et puisque vous omettez les détails d'implémentation à la commande elle-même, vous pouvez utiliser une méthode courante pour instancier la commande, l'exécuter et consulter le résultat. Cela permet un codage plus facile, au lieu d'avoir besoin de lire comment instancier cette classe Light ou Car particulière, et d'en déterminer le résultat.

Ordonnancement des commandes

En utilisant des commandes, vous pouvez également faire des choses comme le séquencement des commandes. En effet, peu importe que vous exécutiez la commande TurnOnLight ou StartCar, vous pouvez exécuter des séquences de thèses telles qu'elles sont exécutées de la même manière. Ce qui à son tour peut permettre l'exécution de chaînes de commandes, ce qui pourrait être utile dans plusieurs situations.

Vous pouvez créer des macros, exécuter des ensembles de commandes qui, selon vous, sont regroupées. C'est à dire. la séquence de commandes: UnlockDoor, EnterHouse, TurnOnLight. Une séquence naturelle de commandes, mais peu susceptible d'être transformée en méthode car elle utilise différents objets et actions.

Sérialisation des commandes

Les commandes étant assez petites par nature, permettent également de sérialiser très bien. Ceci est utile dans un contexte serveur-client ou dans un contexte de microservice programme.

Le serveur (ou programme) peut déclencher une commande, qui sérialise ensuite la commande, l'envoie via un protocole de communication (c'est-à-dire file d'attente d'événements, file d'attente de messages, http, ...) à quelqu'un qui gère réellement la commande. Pas besoin d'instancier d'abord l'objet sur le serveur, c'est-à-dire un Light qui pourrait être léger (jeu de mots voulu), ou un Car qui pourrait être une très grande structure. Vous avez juste besoin de la commande et éventuellement de quelques paramètres.

Cela pourrait être un bon endroit pour présenter le CQRS - Command Query Responsibility Separation Pattern pour d'autres études.

Suivi des commandes

L'utilisation de la couche supplémentaire de commandes dans votre système peut également permettre la journalisation ou le suivi des commandes, si cela est nécessaire. Au lieu de faire cela partout, vous pouvez rassembler le suivi/journalisation dans le module de commande.

Cela permet de modifier facilement le système de journalisation, ou d'ajouter des éléments comme le chronométrage, ou d'activer/désactiver la journalisation. Et tout est facilement maintenu dans le module de commande.

Le suivi des commandes permet également d'autoriser des actions d'annulation, car vous pouvez choisir de réitérer les commandes à partir d'un état donné. Nécessite un peu de configuration supplémentaire, mais assez facilement réalisable.


En bref, les commandes peuvent être vraiment utiles car elles permettent la connexion entre différentes parties de votre programme prochainement volumineux, car elles sont légères facilement mémorisées/documentées et masquent les détails de l'implémentation si nécessaire. De plus, les commandes permettent plusieurs extensions utiles qui, lors de la construction d'un système plus grand, seront utiles: c'est-à-dire interface commune, séquençage, sérialisation, suivi, journalisation, sécurité.

23
holroy

Les possibilités sont nombreuses, mais ce serait généralement quelque chose comme:

  • la création d'un cadre de ligne de commande qui résume l'analyse des options de l'action. Ensuite, vous pouvez enregistrer une action avec quelque chose comme opts.register("--on", new LightOnCommand()).
  • permettre aux utilisateurs de glisser-déposer une séquence d'actions à exécuter en tant que macro
  • enregistrer un rappel lorsqu'un événement est déclenché, comme on(Event.ENTER_ROOM, new LightOnCommand())

Le schéma général ici est que vous avez un morceau du code chargé de comprendre que ne action doit être entreprise sans savoir ce qu'est cette action, et un autre morceau du code sait comment faire une action mais pas quand le faire.

Par exemple, dans ce premier exemple, l'instance opts sait que lorsqu'elle voit une option de ligne de commande --on, elle doit allumer les lumières. Mais il le sait sans vraiment savoir ce que signifie "allumer les lumières". En fait, il se pourrait bien que l'instance opts provienne d'une bibliothèque tierce, donc elle ne peut pas connaître les lumières. Tout ce qu'il sait, c'est comment associer des actions (commandes) aux options de ligne de commande qu'il analyse.

12
yshavit

Tu n'es pas obligé. Les modèles de conception sont simplement directives que certaines personnes dans le passé ont trouvé utiles lors de l'écriture d'applications d'une complexité importante.

Dans votre cas, si ce que vous avez à faire est d'allumer et d'éteindre l'interrupteur d'éclairage, et pas grand chose d'autre, la deuxième option est une évidence.

Moins de code est presque toujours meilleur que plus de code.

7
jhn

L'exemple ICommand que vous donnez est assez limité et n'est utile que dans un langage de programmation qui n'a pas d'expressions lambda. Sprinter couvre bien cela dans ses réponses montrant l'utilisation des usines de commande.

La plupart des cas du modèle de commande incluent d'autres méthodes, par exemple, CanRun et/ou Undo. Cela permet à un bouton de mettre à jour son état d'activation en fonction de l'état de la commande, ou à une application d'implémenter une pile d'annulation.

Comme la plupart des modèles de conception, le modèle de commande prend tout son sens là où les choses deviennent un peu plus complexes. Il est également bien connu, donc aide à rendre votre code clairement pour la plupart des programmeurs.

5
Ian Ringrose

J'ai connu quelques avantages du modèle de commande. Mais tout d'abord, je voudrais rappeler que, comme tous les autres modèles, ce modèle vise également à augmenter la lisibilité du code et à apporter une compréhension commune à votre base de code partagée (probablement).

J'utilise ce modèle pour passer d'une interface orientée méthode à une interface orientée commande. Cela signifie que j'encapsule des appels de méthode dans des commandes concrètes avec les données nécessaires. Cela rend le code plus lisible oui, mais plus important encore, je peux traiter les méthodes comme des objets qui vous permettent d'ajouter/supprimer/modifier facilement des commandes sans augmenter la complexité du code. Il est donc facile à gérer et à lire.

Deuxièmement, puisque vous avez vos méthodes en tant qu'objets, vous pouvez les stocker/les mettre en file d'attente pour les exécuter plus tard. Ou pour les annuler même après les avoir exécutés. C'est là que ce modèle vous aide à implémenter "Annuler".

Enfin, le modèle de commande est également utilisé pour découpler l'exécution des commandes et les commandes elles-mêmes. C'est comme si un serveur ne savait pas comment cuisiner les commandes qu'il avait reçues. Il s'en fiche. Il n'a pas besoin de savoir. S'il le savait, il travaillerait aussi comme cuisinier. Et si le restaurateur veut licencier le serveur, il finit par n'avoir pas non plus de cuisinier. Bien sûr, cette définition n'est pas spéciale ou liée uniquement au modèle de commande. C'est pourquoi nous avons besoin du découplage ou de la gestion des dépendances en général.

2
stdout

Vous pouvez faire ce que vous pensez, mais il est bon de suivre le modèle pour usability de manageability de code.

Dans un exemple réel, Light serait une interface. Vous avez différentes implémentations de Light comme LEDLight, TubeLight

Si vous exécutez la commande concrète via Inovker (RemoteControl), vous n'avez pas à vous soucier des changements de nom de méthode dans Receiver. switchOn() in Light can be changed to switchOnDevice() in future. Mais cela n'affecte pas Invoker puisque ConcreteCommand (LightsOnCommand) apportera les modifications pertinentes.

Supposons le scénario où vous publiez vos interfaces sur un service (Service A) et l'implémentation dans d'autres services (Service B).

Maintenant, le service A ne devrait pas être au courant des changements dans Receiver.

Invoker fournit un couplage lâche entre l'expéditeur et le destinataire du message.

Jetez un œil à quelques questions SE plus connexes:

en utilisant le modèle de conception de commande

Le modèle de commande semble inutilement complexe (qu'est-ce que je ne comprends pas?)

2
Ravindra babu

Les modèles de conception sont essentiellement développés pour résoudre un problème complexe ou en d'autres termes, nous pouvons dire qu'ils sont utilisés pour empêcher votre solution de devenir complexe. Ils nous offrent la flexibilité d'ajouter de nouvelles fonctionnalités à l'avenir sans apporter de modifications importantes à notre code existant. Ouvert pour extension, fermé pour modification.

Le modèle de commande encapsule essentiellement une demande en tant qu'objet et vous permet ainsi de paramétrer d'autres objets avec des demandes différentes et de prendre en charge les opérations annulables.

Quand nous voyons une télécommande qui est le meilleur exemple de modèle de commande. Dans ce cas, nous avons associé une commande concrète à chaque bouton et cette commande a une information du récepteur sur laquelle agir.

Dans votre exemple de boîtier de commutateur, supposons que si vous souhaitez associer un bouton de la télécommande au ventilateur au lieu de la lumière, vous devrez à nouveau modifier le code existant car vous devez associer un bouton à une commande. C'est l'essence même du modèle de commande où il vous offre la possibilité d'associer n'importe quelle commande à un bouton afin qu'au moment de l'exécution, vous puissiez modifier la fonctionnalité. Bien qu'en général, la télécommande du téléviseur n'ait pas cette possibilité, c'est-à-dire que vous ne pouvez pas faire fonctionner le bouton d'augmentation du volume pour réduire le volume. Mais si vous utilisez une application pour contrôler votre téléviseur, vous pouvez attribuer à n'importe quel bouton l'une des commandes disponibles.

De plus, en utilisant le modèle de commande, vous pouvez avoir un ensemble de commandes pour une fonctionnalité particulière à réaliser, c'est-à-dire une macro-commande. Par conséquent, si vous pensez à un niveau plus large, ce modèle peut vous donner la flexibilité d'étendre les fonctionnalités.

Le modèle de commande nous aide à découpler l'invocateur (télécommande) et le récepteur (lumière, ventilateur, etc.) à l'aide de l'objet de commande (LightOnCommand, FanOffCommand, etc.).

1
som