Kommandomønster - Command pattern

I objektorienteret programmering er kommandomønsteret et adfærdsmæssigt designmønster, hvor et objekt bruges til at indkapsle al information, der er nødvendig for at udføre en handling eller udløse en begivenhed på et senere tidspunkt. Disse oplysninger inkluderer metodens navn, det objekt, der ejer metoden, og værdierne for metodeparametrene.

Fire udtryk, der altid er knyttet til kommandomønsteret, er kommando , modtager , påkalder og klient . Et kommandoobjekt kender til modtageren og påberåber sig en metode til modtageren. Værdier for parametre for modtagermetoden gemmes i kommandoen. Modtagerobjektet til at udføre disse metoder lagres også i kommandoobjektet ved sammenlægning . Den modtager derefter gør arbejdet, når execute()metoden i kommandoen kaldes. Et invoker- objekt ved, hvordan man udfører en kommando, og foretager eventuelt bogføring om kommandoudførelsen. Den har kaldt ved ikke noget om en konkret kommando, den kender kun om kommando -interface . Har kaldt objekt (er), kommando objekter og modtager genstande holdes af en klient objekt, den kunden bestemmer, hvilken modtager objekter det tildeler kommandoen objekter, og hvilke kommandoer det tildeler har kaldt. Klienten beslutter, hvilke kommandoer der skal udføres på hvilke punkter. For at udføre en kommando, sender den kommandoobjektet til invoker-objektet.

Brug af kommandoobjekter gør det lettere at konstruere generelle komponenter, der skal delegere, sekvensere eller udføre metodeopkald på et tidspunkt efter eget valg uden behov for at kende metodenes klasse eller metodeparametrene. Brug af et invoker-objekt gør det muligt at udføre bogføring om kommandoudførelser bekvemt samt implementere forskellige tilstande for kommandoer, der styres af invoker-objektet uden behov for klienten at være opmærksom på eksistensen af ​​bogføring eller tilstande.

De centrale ideer i dette designmønster spejler tæt semantikken i førsteklasses funktioner og højere ordensfunktioner i funktionelle programmeringssprog . Specifikt er invoker-objektet en højere ordensfunktion, hvor kommandoobjektet er et førsteklasses argument.

Oversigt

Kommando-designmønsteret er et af de 23 velkendte GoF-designmønstre, der beskriver, hvordan man løser tilbagevendende designproblemer for at designe fleksibel og genanvendelig objektorienteret software, dvs. objekter, der er lettere at implementere, ændre, teste og genbruge.

Brug af kommandodesignmønsteret kan løse disse problemer:

  • Kobling af opkalderen til en anmodning til en bestemt anmodning bør undgås. Det vil sige, hard-wired anmodninger bør undgås.
  • Det skal være muligt at konfigurere et objekt (der påberåber en anmodning) med en anmodning.

Implementering (hard-wiring) af en anmodning direkte i en klasse er ufleksibel, fordi den parrer klassen til en bestemt anmodning ved kompileringstid, hvilket gør det umuligt at specificere en anmodning i løbetid.

Brug af kommandodesignmønsteret beskriver følgende løsning:

  • Definer separate (kommando) objekter, der indkapsler en anmodning.
  • En klasse delegerer en anmodning til et kommandoobjekt i stedet for at implementere en bestemt anmodning direkte.

Dette gør det muligt for en at konfigurere en klasse med et kommandoobjekt, der bruges til at udføre en anmodning. Klassen er ikke længere koblet til en bestemt anmodning og har ingen viden (er uafhængig) af, hvordan anmodningen udføres.

Se også UML-klassen og sekvensdiagrammet nedenfor.

Struktur

UML klasse og sekvensdiagram

Et eksempel på en UML-klasse og et sekvensdiagram til kommandomønsteret.

I ovenstående UML klasse diagram , det Invokerbetyder klasse ikke gennemføre en anmodning direkte. InvokerHenviser i stedet til Commandgrænsefladen til at udføre en anmodning ( command.execute()), hvilket gør det Invokeruafhængigt af, hvordan anmodningen udføres. De Command1klasse implementerer den Commandgrænseflade ved at udføre en handling på en modtager ( receiver1.action1()).

Den UML sekvensdiagram viser runtime-interaktioner: De Invokerobjekt opkald execute()på et Command1objekt. Command1kalder action1()på et Receiver1objekt, der udfører anmodningen.

UML klassediagram

UML-diagram over kommandomønsteret

Anvendelser

GUI-knapper og menupunkter
I Swing og Borland Delphi programmering er en Actionet kommandoobjekt. Ud over muligheden for at udføre den ønskede kommando kan en handling have et tilknyttet ikon, tastaturgenvej, værktøjstipstekst osv. En værktøjslinjeknap eller en menupunktkomponent kan initialiseres fuldstændigt ved kun at bruge Action- objektet.
Makro -optagelse
Hvis alle brugerhandlinger er repræsenteret af kommandoobjekter, kan et program optage en sekvens af handlinger ved blot at føre en liste over kommandoobjekterne, når de udføres. Det kan derefter "afspille" de samme handlinger ved at udføre de samme kommandoobjekter igen i rækkefølge. Hvis programmet integrerer en scriptmotor, kan hvert kommandoobjekt implementere en toScript () -metode, og brugerhandlinger kan derefter let registreres som scripts.
Mobil kode
Ved hjælp af sprog som Java, hvor kode kan streames / slurpes fra et sted til et andet via URLClassloaders og Codebases, kan kommandoerne muliggøre, at ny adfærd kan leveres til fjerntliggende steder (EJB Command, Master Worker).
Fortryd flere niveauer
Hvis alle brugerhandlinger i et program implementeres som kommandoobjekter, kan programmet gemme en stak med de senest udførte kommandoer. Når brugeren ønsker at fortryde en kommando, springer programmet simpelthen det nyeste kommandoobjekt og udfører dets fortrydelsesmetode () .
Netværk
Det er muligt at sende hele kommandoobjekter over hele netværket, der skal udføres på de andre maskiner, for eksempel spillerhandlinger i computerspil.
Parallel behandling
Hvor kommandoerne skrives som opgaver til en delt ressource og udføres af mange tråde parallelt (muligvis på eksterne maskiner; denne variant kaldes ofte Master / Worker-mønster)
Fremskridt barer
Antag at et program har en række kommandoer, som det udfører i rækkefølge. Hvis hvert kommandoobjekt har en getEstimatedDuration () -metode, kan programmet let estimere den samlede varighed. Det kan vise en statuslinje, der meningsfuldt afspejler, hvor tæt programmet er på at fuldføre alle opgaverne.
Trådpuljer
En typisk trådpuljeklasse til generelle formål kan have en offentlig addTask () -metode, der tilføjer et arbejdselement til en intern kø af opgaver, der venter på at blive udført. Det opretholder en pulje af tråde, der udfører kommandoer fra køen. Elementerne i køen er kommandoobjekter. Typisk implementerer disse objekter en fælles grænseflade som java.lang.Runnable, der gør det muligt for trådpuljen at udføre kommandoen, selvom trådpuljeklassen selv blev skrevet uden kendskab til de specifikke opgaver, som den ville blive brugt til.
Transaktionel adfærd
Svarende til fortrydelse kan en databasemotor eller softwareinstallatør føre en liste over operationer, der er eller vil blive udført. Skulle en af dem mislykkes, kan alle andre vendes eller kasseres (normalt kaldet tilbagerulning ). For eksempel, hvis to databasetabeller, der refererer til hinanden, skal opdateres, og den anden opdatering mislykkes, kan transaktionen rulles tilbage, så den første tabel ikke nu indeholder en ugyldig reference.
Troldmænd
Ofte præsenterer en guide flere sider med konfiguration for en enkelt handling, der kun sker, når brugeren klikker på knappen "Udfør" på den sidste side. I disse tilfælde er en naturlig måde at adskille brugergrænsefladekode fra applikationskode på at implementere guiden ved hjælp af et kommandoobjekt. Kommandoobjektet oprettes, når guiden vises første gang. Hver guideside gemmer sine GUI-ændringer i kommandoobjektet, så objektet udfyldes, når brugeren skrider frem. "Afslut" udløser simpelthen et opkald til at udføre () . På denne måde fungerer kommandoklassen.

Terminologi

Den terminologi, der bruges til at beskrive kommandomønsterimplementeringer, er ikke konsistent og kan derfor være forvirrende. Dette er resultatet af tvetydighed , brugen af synonymer og implementeringer, der kan tilsløre det oprindelige mønster ved at gå langt ud over det.

  1. Tvetydighed.
    1. Udtrykket kommando er tvetydigt. For eksempel flytte op, flytte op kan henvise til en enkelt (flyt op) kommando, der skal udføres to gange, eller det kan henvise til to kommandoer, som hver tilfældigvis gør det samme (flyt op). Hvis den tidligere kommando føjes to gange til en fortrydelsesstabel, refererer begge elementer på stakken til den samme kommandoforekomst. Dette kan være passende, når en kommando altid kan fortrydes på samme måde (f.eks. Flytte ned). Både Gang of Four og Java-eksemplet nedenfor bruger denne fortolkning af udtrykket kommando . På den anden side, hvis sidstnævnte kommandoer føjes til en fortrydelsesstabel, henviser stakken til to separate objekter. Dette kan være passende, når hvert objekt på stakken skal indeholde oplysninger, der gør det muligt at fortryde kommandoen. For at fortryde en slet markeringskommando kan objektet muligvis indeholde en kopi af den slettede tekst, så den kan genindsættes, hvis slet markeringskommandoen skal fortrydes. Bemærk, at brug af et separat objekt til hver påkaldelse af en kommando også er et eksempel på ansvarsmønstret .
    2. Udtrykket udføre er også tvetydigt. Det kan referere til at køre koden identificeret ved kommandoobjektets eksekveringsmetode . I Microsofts Windows Presentation Foundation anses en kommando imidlertid for at være udført, når kommandos eksekveringsmetode er påberåbt, men det betyder ikke nødvendigvis, at applikationskoden er kørt. Dette sker først efter yderligere behandling af begivenheder.
  2. Synonymer og homonymer .
    1. Client, Source, Invoker : knappen, værktøjslinjeknappen eller det menupunkt, der klikkes på, og genvejstasten trykkes ned af brugeren.
    2. Kommandoobjekt, Rutet kommandoobjekt, Handlingsobjekt : et enkelt objekt (f.eks. Er der kun ét CopyCommand-objekt), der kender genvejstaster, knapbilleder, kommandotekst osv. Relateret til kommandoen. Et kilde / invoker-objekt kalder Command / Action-objektets execute / performAction-metode. Kommando / handling-objektet underretter de relevante kilde- / invoker-objekter, når tilgængeligheden af ​​en kommando / handling er ændret. Dette gør det muligt for knapper og menupunkter at blive inaktive (nedtonede), når en kommando / handling ikke kan udføres / udføres.
    3. Modtager, målobjekt : objektet, der skal kopieres, indsættes, flyttes osv. Modtagerobjektet ejer metoden, der kaldes ved kommandos udførelsesmetode . Modtageren er typisk også målobjektet. For eksempel, hvis modtagerobjektet er en markør, og metoden kaldes moveUp , ville man forvente, at markøren er målet for moveUp-handlingen. På den anden side, hvis koden er defineret af selve kommandoobjektet, vil målobjektet være et helt andet objekt.
    4. Kommandoobjekt, rutede begivenhedsargumenter, begivenhedsobjekt : objektet, der sendes fra kilden til kommando- / handlingsobjektet, til målobjektet til den kode, der udfører arbejdet. Hvert klik på en knap eller genvejstast resulterer i et nyt kommando / begivenhedsobjekt. Nogle implementeringer tilføjer mere information til kommandoen / begivenhedsobjektet, når det sendes fra et objekt (f.eks. CopyCommand) til et andet (f.eks. Dokumentsektion). Andre implementeringer placerer kommando / begivenhedsobjekter i andre begivenhedsobjekter (som et felt i en større boks), når de bevæger sig langs linjen for at undgå navngivning af konflikter. (Se også ansvarsmønster .)
    5. Handler, ExecutedRoutedEventHandler, metode, funktion : den faktiske kode, der kopierer, indsætter, flytter osv. I nogle implementeringer er handlerkoden en del af kommando- / handlingsobjektet. I andre implementeringer er koden en del af modtageren / målobjektet, og i endnu andre implementeringer holdes handlerkoden adskilt fra de andre objekter.
    6. Command Manager, Undo Manager, Scheduler, Queue, Dispatcher, Invoker : et objekt, der placerer kommando / begivenhedsobjekter på en fortryd stak eller gentag stak, eller som holder på kommando / begivenhedsobjekter, indtil andre objekter er klar til at handle på dem, eller der dirigerer kommando- / hændelsesobjekterne til det relevante modtager- / målobjekt eller handler-kode.
  3. Implementeringer der går langt ud over det originale kommandomønster.
    1. Microsofts Windows Presentation Foundation (WPF) introducerer rutede kommandoer, der kombinerer kommandomønsteret med hændelsesbehandling. Som et resultat indeholder kommandoobjektet ikke længere en henvisning til målobjektet eller en henvisning til applikationskoden. I stedet resulterer en påkaldelse af kommandoobjektets eksekveringskommando i en såkaldt Executed Routed Event, der under begivenhedens tunneling eller bobling kan støde på et såkaldt bindingsobjekt, der identificerer målet og applikationskoden, som udføres på det tidspunkt.

Eksempel

Overvej en "enkel" switch. I dette eksempel konfigurerer vi kontakten med to kommandoer: at tænde lyset og at slukke lyset.

En fordel ved denne særlige implementering af kommandomønsteret er, at kontakten kan bruges med en hvilken som helst enhed, ikke kun et lys. Switch i den følgende C # -implementering tænder og slukker for et lys, men Switchens konstruktør er i stand til at acceptere alle underklasser af Command for sine to parametre. For eksempel kan du konfigurere kontakten til at starte en motor.

using System;

namespace CommandPattern
{    
    public interface ICommand
    {
        void Execute();
    }

    /* The Invoker class */
    public class Switch
    {
        ICommand _closedCommand;
        ICommand _openedCommand;

        public Switch(ICommand closedCommand, ICommand openedCommand)
        {
            this._closedCommand = closedCommand;
            this._openedCommand = openedCommand;
        }

        // Close the circuit / power on
        public void Close()
        {
           this._closedCommand.Execute();
        }

        // Open the circuit / power off
        public void Open()
        {
            this._openedCommand.Execute();
        }
    }

    /* An interface that defines actions that the receiver can perform */
    public interface ISwitchable
    {
        void PowerOn();
        void PowerOff();
    }

    /* The Receiver class */
    public class Light : ISwitchable
    {
        public void PowerOn()
        {
            Console.WriteLine("The light is on");
        }

        public void PowerOff()
        {
            Console.WriteLine("The light is off");
        }
    }

    /* The Command for turning on the device - ConcreteCommand #1 */
    public class CloseSwitchCommand : ICommand
    {
        private ISwitchable _switchable;

        public CloseSwitchCommand(ISwitchable switchable)
        {
            _switchable = switchable;
        }

        public void Execute()
        {
            _switchable.PowerOn();
        }
    }

    /* The Command for turning off the device - ConcreteCommand #2 */
    public class OpenSwitchCommand : ICommand
    {
        private ISwitchable _switchable;

        public OpenSwitchCommand(ISwitchable switchable)
        {
            _switchable = switchable;
        }

        public void Execute()
        {
            _switchable.PowerOff();
        }
    }

    /* The test class or client */
    internal class Program
    {
        public static void Main(string[] arguments)
        {
            string argument = arguments.Length > 0 ? arguments[0].ToUpper() : null;

            ISwitchable lamp = new Light();

            // Pass reference to the lamp instance to each command
            ICommand switchClose = new CloseSwitchCommand(lamp);
            ICommand switchOpen = new OpenSwitchCommand(lamp);

            // Pass reference to instances of the Command objects to the switch
            Switch @switch = new Switch(switchClose, switchOpen);

            if (argument == "ON")
            {
                // Switch (the Invoker) will invoke Execute() on the command object.
                @switch.Close();
            }
            else if (argument == "OFF")
            {
                // Switch (the Invoker) will invoke the Execute() on the command object.
                @switch.Open();
            }
            else
            {
                Console.WriteLine("Argument \"ON\" or \"OFF\" is required.");
            }
        }
    }
}


Se også

Kilder

Den første offentliggjorte omtale af at bruge en Command-klasse til at implementere interaktive systemer ser ud til at være en artikel fra Henry Lieberman fra 1985. Den første offentliggjorte beskrivelse af en (multiple-level) fortryd-gentag-mekanisme ved hjælp af en Command-klasse med eksekverings- og fortrydelsesmetoder og en historikliste ser ud til at være den første (1988) udgave af Bertrand Meyers bog Objektorienteret software Konstruktion , afsnit 12.2.

Referencer

eksterne links