Kommando (designmönster)

Den aktuella versionen av sidan har ännu inte granskats av erfarna bidragsgivare och kan skilja sig väsentligt från versionen som granskades den 20 september 2019; kontroller kräver 8 redigeringar .
Team
kommando
Sorts beteendemässiga
Ändamål för att bearbeta kommandot som ett objekt
Relaterade mallar Linker , Keeper , Prototyp , Loner
Beskrivs i Design Patterns Ja

Ett kommando är ett beteendedesignmönster  som används i objektorienterad programmering som representerar en handling. Kommandoobjektet innehåller själva åtgärden och dess parametrar.

Syfte

Skapa en struktur där avsändarklassen och mottagarklassen inte är direkt beroende av varandra. Organisera en återuppringning till en klass som inkluderar avsändarklassen.

Beskrivning

I objektorienterad programmering är kommandodesignmönstret ett beteendemönster där ett objekt används för att kapsla in all information som behövs för att utföra en åtgärd eller ta upp en händelse vid ett senare tillfälle. Denna information inkluderar namnet på metoden, objektet som äger metoden och värdena för metodens parametrar.

Fyra termer är alltid associerade med kommandomönstret: kommandon (kommando), kommandomottagare (mottagare), kommandoanropare (anropare) och klient (klient). Kommandoobjektet känner till mottagaren och anropar mottagarens metod. Mottagarens parametervärden lagras i kommandot. Den som ringer (anroparen) vet hur man utför kommandot och håller eventuellt reda på de utförda kommandona. Den som ringer (anroparen) vet ingenting om ett visst kommando, den vet bara om gränssnittet. Båda objekten (det anropande objektet och flera kommandoobjekt) tillhör klientobjektet. Klienten bestämmer vilka kommandon som ska utföras och när. För att utföra ett kommando skickar den kommandoobjektet till den som ringer (anroparen).

Att använda kommandoobjekt gör det enkelt att bygga delade komponenter som du behöver för att delegera eller göra metodanrop när som helst utan att behöva känna till klassmetoderna eller metodparametrarna. Genom att använda caller-objektet (invoker) kan du hålla ett register över utförda kommandon utan att kunden behöver känna till denna redovisningsmodell (en sådan redovisning kan vara användbar, till exempel för att implementera kommandon ångra och gör om).

Applikation

Kommandomönstret kan vara användbart i följande fall.

Användargränssnittsknappar och menyalternativ

I Swing och Borland Delphi är en Action ett kommandoobjekt. Förutom att kunna utföra önskat kommando kan en åtgärd ha en tillhörande ikon, kortkommando, verktygstipstext och så vidare. En knapp eller ett menyalternativ i verktygsfältet kan initieras helt med endast ett åtgärdsobjekt .

Makroinspelning

Om alla användaråtgärder representeras som kommandoobjekt kan programmet spela in en sekvens av åtgärder genom att helt enkelt lagra en lista med kommandoobjekt i den ordning som de utförs. Den kan sedan "spela om" samma åtgärder genom att utföra samma kommandoobjekt i samma sekvens.

Ångra på flera nivåer ( Ångra )

Om alla användaråtgärder i programmet implementeras som kommandoobjekt, kan programmet spara en stapel av de senast utförda kommandona. När användaren vill avbryta ett kommando, öppnar programmet helt enkelt det sista kommandoobjektet och kör dess undo()- metod .

nätverk

Du kan skicka kommandoobjekt över nätverket för att köras på en annan maskin, till exempel en spelaråtgärd i ett datorspel.

Framstegsindikatorer

Anta att ett program har en sekvens av kommandon som det kör i ordning. Om varje kommandoobjekt har en getEstimatedDuration()- metod kan programmet enkelt uppskatta den totala varaktigheten av processen. Det kan visa en förloppsindikator som visar hur nära programmet är att slutföra alla uppgifter.

Trådpooler

En typisk trådpoolsklass för allmänt bruk kan ha en addTask()- metod som lägger till ett arbetsobjekt i en intern kö av väntande uppgifter. Den upprätthåller en pool av trådar som kör kommandon från en kö. Elementen i kön är kommandoobjekt. Vanligtvis implementerar dessa objekt ett gemensamt gränssnitt som java.lang.Runnable , vilket gör att trådpoolen kan köra kommandon även om den skrevs utan någon som helst kunskap om de specifika uppgifter som den kommer att användas för.

Transaktioner

I likhet med "ångra" -operationen kan ett databashanteringssystem (DBMS) eller programvaruinstallationsprogram lagra en lista över operationer som har utförts eller kommer att utföras. Om en av dem misslyckas, kan alla andra avbrytas eller kasseras (kallas vanligen en rollback). Till exempel, om två relaterade databastabeller behöver uppdateras och den andra uppdateringen misslyckas, kan systemet återställa transaktionen så att den första tabellen inte innehåller en ogiltig länk.

Mästare

Ofta presenterar en guide (installationsguide eller vad som helst) flera konfigurationssidor för en enda åtgärd som bara händer när användaren klickar på "Slutför"-knappen på sista sidan. I dessa fall är det naturliga sättet att separera användargränssnittskoden från applikationskoden att implementera guiden med ett kommandoobjekt. Kommandoobjektet skapas första gången guiden visas. Varje guidesida sparar sina ändringar i kommandoobjektet, så att objektet fylls i när användaren navigerar. "Klar"-knappen utlöser helt enkelt metoden execute() att köra.

Exempel

C++ exempel

Källtext i C++ # include < iostream > # include < vektor > # include < string > använder namnutrymme std ; klass Dokument { vektor < sträng > data ; public : Document () { data . reserv ( 100 ); // åtminstone för 100 rader } void Infoga ( int line , const string & str ) { if ( line <= data . size () ) data . infoga ( data . börja () + rad , str ); else cout << "Fel!" << endl ; } void Ta bort ( int line ) { if ( !( line > data . size () ) ) ) data . radera ( data.begin ( ) + rad ) ; else cout << "Fel!" << endl ; } sträng & operator [] ( int x ) { returnera data [ x ]; } void Visa () { for ( int i = 0 ; i < data . size (); ++ i ) { cout << i + 1 << ". " << data [ i ] << endl ; } } }; class Kommando { protected : Document * doc ; public : virtual ~ Kommando () {} virtual void Execute () = 0 ; virtual void unExecute () = 0 ; void setDocument ( Dokument * _doc ) { doc = _doc ; } }; class InsertCommand : public Command { int line ; sträng str ; public : InsertCommand ( int _line , const string & _str ): line ( _line ), str ( _str ) {} void Execute () { doc -> Insert ( line , str ); } void unExecute () { doc -> Remove ( line ); } }; class Invoker { vektor < Kommando *> DoneCommands ; Dokument doc ; Kommando * kommando ; public : void Insert ( int line , string str ) { command = new InsertCommand ( line , str ); kommando -> setDocument ( & doc ); kommando -> Kör (); Klarkommandon . push_back ( kommando ); } void Undo () { if ( DoneCommands . size () == 0 ) { cout << "Det finns inget att ångra!" << endl ; } else { command = DoneCommands . tillbaka (); Klarkommandon . pop_back (); kommando -> unexecute (); // Glöm inte att radera kommandot!!! radera kommando ; } } void Visa () { doc . visa (); } }; int main () { char s = '1' ; int linje , linje_b ; sträng str ; Invoker inv ; while ( s != 'e' ) { cout << "Vad man ska göra: \n1.Lägg till en rad\n2. Ångra senaste kommandot" << endl ; cin >> s ; switch ( s ) { case '1' : cout << "Vilken rad att infoga: " ; cin >> linje ; --linje ; _ cout << "Vad ska infogas: " ; cin >> str ; inv . infoga ( linje , str ); bryta ; fall '2' : inv . Ångra (); bryta ; } cout << "$$$DOCUMENT$$$" << endl ; inv . visa (); cout << "$$$DOCUMENT$$$" << endl ; } }

Python- exempel

Källkod i Python från abc import ABCMeta , abstraktmetod klass Trupp : """ Mottagare - Truppobjekt """ def move ( self , direction : str ) -> None : """ Börja röra sig i en viss riktning """ print ( 'Squad började röra sig {} ' . format ( direction )) def stop ( self ) -> None : """ Stop """ print ( 'Squad stoped' ) class Command ( metaclass = ABCMeta ): """ Basklass för alla kommandon """ @abstractmethod def execute ( self ) -> None : """ Fortsätt för att köra kommandot """ pass @abstractmethod def unexecute ( self ) -> Ingen : """ Unexecute kommando """ passera class AttackCommand ( Kommando ): """ Kommandot för att utföra attacken är """ def __init__ ( själv , trupp : Trupp ) -> Ingen : """ Konstruktör. :param troop: truppen som kommandot " "" är associerad med self .troop = trupp def execute ( self ) -> None : self . trupp . flytta ( 'framåt' ) def unexecute ( själv ) -> Ingen : själv . trupp . sluta () class RetreatCommand ( Kommando ): """ Retreat-kommando """ def __init__ ( self , troop : Troop ) -> Ingen : """ Konstruktör. :param troop: truppen som kommandot """ self är associerat med . trupp = trupp def execute ( self ) -> None : self . trupp . flytta ( 'tillbaka' ) def unexecute ( själv ) -> Ingen : själv . trupp . sluta () class TroopInterface : """ Invoker - ett gränssnitt genom vilket du kan utfärda kommandon till en specifik grupp """ def __init__ ( själv , attack : AttackCommand , retreat : RetreatCommand ) -> Ingen : """ Konstruktör. :param attack: attack kommando :param retreat: retreat kommando " "" self .attack_command = attack self .retreat_command = retreat self .current_command = Inget # kommando körs för närvarande def attack ( själv ) -> Ingen : själv . aktuell_kommando = själv . attack_command self . attack_command . exekvera () def retreat ( själv ) -> Ingen : själv . aktuell_kommando = själv . retreat_command self . retreat_command . exekvera () def stop ( self ) -> Ingen : om själv . nuvarande_kommando : själv . aktuellt_kommando . unexecute () self . current_command = Inga andra : print ( 'Enheten kan inte stanna eftersom den inte rör sig' ) om __name__ == '__main__' : trupp = Trupp () gränssnitt = TroopInterface ( AttackCommand ( trupp ), RetreatCommand ( trupp )) gränssnitt . attack () gränssnitt . stoppa () gränssnitt . reträtt () gränssnitt . sluta ()

PHP5 exempel

PHP5 källkod <?php /** * Abstrakt klass "kommandon" * @abstract */ abstrakt klass Kommando { public abstract function Execute (); offentlig abstrakt funktion UnExecute (); } /** * Klassen för det konkreta "kommandot" */ klass CalculatorCommand utökar Command { /** * Aktuell kommandooperation * * @var string */ public $operator ; /** * Aktuell operand * * @var blandad */ offentlig $operand ; /** * Klassen kommandot är för * * @var objekt i klassen Calculator */ public $calculator ; /** * Konstruktör * * @param objekt $ calculator * @param sträng $operator * @param blandad $operand */ offentlig funktion __construct ( $ calculator , $operator , $operand ) { $this -> calculator = $calculator ; $this -> operator = $operator ; $this -> operand = $operand ; } /** * Återimplementerad överordnad::Execute() funktion */ public function Execute () { $this -> kalkylator -> Operation ( $this -> operator , $this -> operand ); } /** * Återimplementerad förälder::UnExecute() funktion */ public function UnExecute () { $this -> kalkylator -> Operation ( $this -> Undo ( $this -> operator ), $this -> operand ); } /** * Vilken åtgärd ska ångras? * * @private * @param string $operator * @return string */ privat funktion Ångra ( $operator ) { //hitta det omvända för varje åtgärd som utförs switch ( $operator ) { case '+' : $undo = '-' ; bryta ; case '-' : $undo = '+' ; bryta ; case '*' : $undo = '/' ; bryta ; case '/' : $undo = '*' ; bryta ; default : $undo = ' ' ; bryta ; } returnera $ångra ; } } /** * Klassmottagare och utförare av "kommandon" */ class Calculator { /** * Aktuellt resultat av kommandoexekvering * * @private * @var int */ privat $curr = 0 ; public function Operation ( $operator , $operand ) { //välj operator för att beräkna resultatomkopplare ( $ operator ) { case '+' : $this -> curr += $operand ; bryta ; case '-' : $this -> curr -= $operand ; bryta ; case '*' : $this -> curr *= $operand ; bryta ; case '/' : $this -> curr /= $operand ; bryta ; } print ( "Aktuellt resultat = $this->curr (efter exekvering av $operator c $operand )" ); } } /** * Klass som anropar kommandon */ class User { /** * Den här klassen kommer att få kommandon som ska köras * * @private * @var objekt i klassen Calculator */ private $calculator ; /** * Array of operations * * @private * @var array */ private $commands = array (); /** * Aktuellt kommando i operationsarray * * @private * @var int */ privat $current = 0 ; public function __construct () { // skapa en instans av klassen som kommer att utföra kommandon $this -> calculator = new Calculator (); } /** * Funktion för att returnera avbrutna kommandon * * @param int $levels antal operationer att returnera */ public function Gör om ( $levels ) { print ( " \n ---- Repeat $levels operations " ); // Returnera operationer för ( $i = 0 ; $i < $nivåer ; $i ++ ) if ( $this -> nuvarande < count ( $this -> kommandon ) - 1 ) $this -> kommandon [ $this - > aktuell ++ ] -> Kör (); } /** * Kommando ångra funktion * * @param int $levels antal ångra operationer */ offentlig funktion Ångra ( $levels ) { print ( " \n ---- Ångra $levels operationer " ); // Ångra operationer för ( $i = 0 ; $i < $nivåer ; $i ++ ) if ( $this -> current > 0 ) $this -> kommandon [ -- $this -> current ] -> UnExecute ( ); } /** * Kommandoexekveringsfunktion * * @param sträng $operator * @param blandad $operand */ public function Compute ( $operator , $operand ) { // Skapa ett operationskommando och kör det $command = new CalculatorCommand ( $this - > kalkylator , $operator , $operand ); $command -> Execute (); // Lägg till en operation till arrayen av operationer och öka räknaren för den aktuella operationen $this -> kommandon [] = $kommando ; $this -> nuvarande ++ ; } } $user = ny användare (); // Godtyckliga kommandon $user -> Compute ( '+' , 100 ); $user -> Compute ( '-' , 50 ); $user -> Beräkna ( '*' , 10 ); $user -> Beräkna ( '/' , 2 ); // Ångra 4 kommandon $user -> Undo ( 4 ); // Returnera 3 avbrutna kommandon. $user -> Gör om ( 3 );

Java- exempel

Java-källa

För att implementera överensstämmelsen mellan operationernas namn och åtgärden flyttas operationerna på lampan (slå på, släcka) till en instans av klasser SwitchOnCommandoch SwitchOffCommandbåda klasserna implementerar gränssnittet Command.

importera java.util.HashMap ; /** Kommandogränssnittet */ interface Kommando { void execute (); } /** Invoker-klassen */ class Switch { private final HashMap < String , Command > commandMap = new HashMap <> (); public void register ( String commandName , Command command ) { commandMap . put ( kommandonamn , kommando ); } public void exekvera ( String commandName ) { Command command = commandMap . get ( kommandonamn ); if ( kommando == null ) { throw new IllegalStateException ( "inget kommando registrerat för " + commandName ); } kommandot . exekvera (); } } /** Mottagareklassen */ class Light { public void turnOn () { System . ut . println ( "Ljuset är på" ); } public void avstängning () { System . ut . println ( "Ljuset är släckt" ); } } /** Kommandot för att tända ljuset - ConcreteCommand #1 */ class SwitchOnCommand implementerar Command { private final Light light ; public SwitchOnCommand ( Ljus ljus ) { detta . ljus = ljus ; } @Override // Kommando public void execute () { light . slå på (); } } /** Kommandot för att släcka ljuset - ConcreteCommand #2 */ class SwitchOffCommand implementerar kommandot { private final Light light ; public SwitchOffCommand ( Ljus ljus ) { detta . ljus = ljus ; } @Override // Kommando public void execute () { light . avstängning (); } } public class CommandDemo { public static void main ( final String [] arguments ) { Light lamp = new Light (); Kommando switchOn = nytt SwitchOnCommand ( lampa ); Kommando switchOff = nytt SwitchOffCommand ( lampa ); Switch mySwitch = new Switch (); mySwitch . register ( "på" , slå på ); mySwitch . register ( "av" , stängAv ); mySwitch . exekvera ( "på" ); mySwitch . exekvera ( "av" ); } } Använda det funktionella gränssnittet

Från och med Java 8 är det inte obligatoriskt att skapa klasser SwitchOnCommandoch SwitchOffCommandistället kan vi använda en operator ::som visas i följande exempel

public class CommandDemo { public static void main ( final String [] arguments ) { Light lamp = new Light (); Kommando switchOn = lamp :: turnOn ; Kommando switchOff = lamp :: avstängning ; Switch mySwitch = new Switch (); mySwitch . register ( "på" , slå på ); mySwitch . register ( "av" , stängAv ); mySwitch . exekvera ( "på" ); mySwitch . exekvera ( "av" ); } }

Swift 5 exempel

Källkod i Swift 5 protokoll Kommando { func execute() } // ringer klass Switch { enum SwitchAction { fodral på, av } var status: String? var action: Ljus? func register(_ kommando: Ljus) { self.action = kommando } func execute(_ commandName: SwitchAction) { if commandName == .on { action?.turnOn() } else if commandName == .off { action?.turnOff() } } } // Mottagare klass ljus { func turnOn() { print ("Ljuset lyser") } func turnOff() { print ("Ljuset är AV") } } class SwitchOnCommand: Kommando { privat var ljus: Ljus init(_light: Light) { själv.ljus = ljus } func execute() { light.turnOn() } } class SwitchOffCommand: Kommando { privat var ljus: Ljus init(_light: Light) { själv.ljus = ljus } func execute() { light.turnOff() } } // ANVÄNDA SIG AV låt invoker = Switch() låt mottagare = Light() invoker.register(mottagare) invoker.execute(.on)

Ruby exempel

Ruby källkod modul EngineCommands # Abstrakt klass 'Command' - klass Kommando def exekvera slutet # Mottagareklass Engine attr_reader : state def initialisera rpm @state , @rpm = false , rpm if rpm . är en? Heltalsslut _ def slå på ; @tillstånd = sant ; slut def avstängning ; @state = falskt ; slutändan _ # ConcreteCommand1 klass CommandTurnOn < Kommando def initialisera motor @engine = motor om motor . är en? motoränden _ def exekvera @engine . slå slutet # ConcreteCommand2 klass CommandTurnOff < Kommando def initialisera motor @motor = motor om motor . är en? motoränden _ def exekvera @engine . stäng av änden # Invoker- klass Invoker def initialize @commands = Hash . nytt slut def registerCommand kommandonamn , kommando @kommandon [ kommandonamn ] = kommandot if kommando . är en? Kommando och @kommandon [ kommandonamn ]. är en? Ingen klass slut def executeCommand kommandonamn @kommando = @kommandon [ kommandonamn ] om inte @kommando . är en? NilClass @kommando . exekvera annars höj TypeError . ny ände ände ände _ # Klientmodul Klienten inkluderar EngineCommands invoker = Invoker . ny motor = motor . ny ( 250 ) commandTurnOn = CommandTurnOn . ny ( motor ) commandTurnOff = CommandTurnOff . ny ( motor ) anropare . registerCommand "turnOn" , commandTurnOn invoker . registerCommand "turnOff" , kommandoTurnOff sätter " \t Engine State före användning av kommandot: #{ engine . state } " # => Engine State före användning av kommandot: false sätter " \t Engine State efter användning av kommandot 'turnOn': #{ invoker . executeCommand "turnOn" } " # => Motortillstånd efter användning av kommandot 'turnOn': true sätter " \t Engine State after use kommando 'turnOff': #{ invoker . executeCommand "turnOff" } " # => Motortillstånd efter användning kommando 'turnOff': false end

Länkar