Příkaz (návrhový vzor)

Aktuální verze stránky ještě nebyla zkontrolována zkušenými přispěvateli a může se výrazně lišit od verze recenzované 20. září 2019; kontroly vyžadují 8 úprav .
tým
příkaz
Typ behaviorální
Účel zpracovat příkaz jako objekt
Související šablony Linker , Keeper , Prototype , Loner
Popsáno v Návrhové vzory Ano

Příkaz je návrhový vzor chování  používaný v objektově orientovaném programování , který představuje akci. Objekt příkazu obsahuje samotnou akci a její parametry.

Účel

Vytvořte strukturu, kde třída odesílatele a třída příjemce na sobě přímo nezávisí. Organizace zpětného volání do třídy, která zahrnuje třídu odesílatele.

Popis

V objektově orientovaném programování je návrhový vzor Command vzorem chování, ve kterém se objekt používá k zapouzdření všech informací potřebných k provedení akce nebo vyvolání události v pozdější době. Tyto informace zahrnují název metody, objekt, který metodu vlastní, a hodnoty parametrů metody.

Se vzorem příkazů jsou vždy spojeny čtyři pojmy: příkazy (příkaz), příjemce příkazu (přijímač), volající příkaz (invoker) a klient (klient). Objekt Command ví o přijímači a vyvolá metodu příjemce. Hodnoty parametrů přijímače jsou uloženy v příkazu. Volající (invoker) ví, jak příkaz provést, a případně sleduje provedené příkazy. Volající (invoker) neví nic o konkrétním příkazu, ví pouze o rozhraní. Oba objekty (volající objekt a několik objektů příkazu) patří do klientského objektu. Klient rozhoduje, které příkazy a kdy provede. Pro provedení příkazu předá objekt příkazu volajícímu (invoker).

Použití objektů příkazů usnadňuje vytváření sdílených komponent, které potřebujete kdykoli delegovat nebo provádět volání metod, aniž byste museli znát metody tříd nebo parametry metod. Použití objektu volajícího (invoker) umožňuje vést záznam o provedených příkazech, aniž by klient musel o tomto účetním modelu vědět (takové účtování může být užitečné např. pro implementaci příkazu undo a redo).

Aplikace

Vzor Příkaz může být užitečný v následujících případech.

Tlačítka uživatelského rozhraní a položky nabídky

V Swing a Borland Delphi je Akce příkazovým objektem. Kromě možnosti provést požadovaný příkaz může mít akce přidruženou ikonu, klávesovou zkratku, text nápovědy a tak dále. Tlačítko nebo položku nabídky na panelu nástrojů lze plně inicializovat pouze pomocí objektu Action .

Makro záznam

Pokud jsou všechny uživatelské akce reprezentovány jako příkazové objekty, program může zaznamenat sled akcí jednoduchým uložením seznamu příkazových objektů v pořadí, v jakém jsou provedeny. Poté může „přehrát“ stejné akce provedením stejných příkazových objektů ve stejném pořadí.

Víceúrovňové operace vrácení zpět ( Zpět )

Pokud jsou všechny uživatelské akce v programu implementovány jako příkazové objekty, program může uložit zásobník naposledy provedených příkazů. Když chce uživatel zrušit příkaz, program jednoduše vysune poslední objekt příkazu a provede svou metodu undo() .

sítí

Přes síť můžete posílat příkazové objekty, které mají být provedeny na jiném počítači, jako je akce hráče v počítačové hře.

Progress bary

Předpokládejme, že program má sekvenci příkazů, které provádí v daném pořadí. Pokud má každý objekt příkazu metodu getEstimatedDuration() , program může snadno odhadnout celkovou dobu trvání procesu. Může zobrazit ukazatel průběhu, který odráží, jak blízko je program dokončení všech úkolů.

Závitové bazény

Typická třída fondu vláken pro obecné účely může mít metodu addTask() , která přidá pracovní položku do interní fronty čekajících úloh. Udržuje fond vláken, která provádějí příkazy z fronty. Prvky ve frontě jsou příkazové objekty. Tyto objekty obvykle implementují společné rozhraní, jako je java.lang.Runnable , které umožňuje fondu vláken spouštět příkazy, i když byl napsán bez znalosti konkrétních úloh, pro které bude použit.

Transakce

Podobně jako u operace „zpět“ může systém správy databází (DBMS) nebo instalační program softwaru uložit seznam operací, které byly nebo budou provedeny. Pokud jeden z nich selže, všechny ostatní mohou být zrušeny nebo zahozeny (běžně nazývané rollback). Pokud je například potřeba aktualizovat dvě související databázové tabulky a druhá aktualizace selže, systém může transakci vrátit zpět, aby první tabulka neobsahovala neplatný odkaz.

Mistři

Často průvodce (průvodce nastavením nebo cokoli jiného) předloží několik konfiguračních stránek pro jednu akci, která se stane pouze tehdy, když uživatel klikne na tlačítko "Dokončit" na poslední stránce. V těchto případech je přirozeným způsobem oddělení kódu uživatelského rozhraní od kódu aplikace implementace průvodce pomocí objektu příkazu. Objekt příkazu se vytvoří při prvním zobrazení průvodce. Každá stránka průvodce ukládá své změny do objektu příkazu, takže objekt je naplněn, když uživatel naviguje. Tlačítko "Hotovo" jednoduše spustí metodu execute() ke spuštění.

Příklady

Příklad C++

Zdrojový text v C++ # include < iostream > # include < vector > # include < string > using namespace std ; class Document { vector < string > data ; public : Document () { data . rezerva ( 100 ); // alespoň na 100 řádků } void Vložte ( int line , const string & str ) { if ( line <= data . size () ) data . vložit ( data . begin () + řádek , str ); else cout << "Chyba!" << endl ; } void Odebrat ( int line ) { if ( ! ( line > data . size () ) ) data . vymazat ( data.begin ( ) + řádek ) ; else cout << "Chyba!" << endl ; } string & operator [] ( int x ) { return data [ x ]; } void Zobrazit () { for ( int i = 0 ; i < data . velikost (); ++ i ) { cout << i + 1 << "." << data [ i ] << endl ; } } }; class Příkaz { protected : Dokument * doc ; public : virtual ~ Příkaz () {} virtual void Proveď () = 0 ; virtuální void unExecute () = 0 ; void setDocument ( Dokument * _doc ) { doc = _doc ; } }; class InsertCommand : public Command { int line ; řetězec 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 { vector < Příkaz *> HotovoPříkazy ; Dokument doc ; Příkaz * příkaz ; public : void Insert ( int line , string str ) { command = new InsertCommand ( line , str ); příkaz -> setDocument ( & doc ); příkaz -> Execute (); HotovoPříkazy . push_back ( příkaz ); } void Zpět () { if ( HotovoPříkazy . velikost () == 0 ) { cout << "Není co vrátit!" << endl ; } else { příkaz = HotovoPříkazy . zpět (); HotovoPříkazy . pop_back (); příkaz -> unexecute (); // Nezapomeňte smazat příkaz!!! příkaz smazat ; } } void Zobrazit () { doc . ukázat (); } }; int main () { char s = '1' ; int řádek , řádek_b ; řetězec str ; Invoker inv ; while ( s != 'e' ) { cout << "Co dělat: \n1.Přidat řádek\n2.Vrátit zpět poslední příkaz" << endl ; cin >> s ; switch ( s ) { case '1' : cout << "Jaký řádek vložit: " ; cin >> řádek ; --řádek ; _ cout << "Co vložit: " ; cin >> str ; inv . vložit ( řádek , str ); zlomit ; případ '2' : inv . Zpět (); zlomit ; } cout << "$$$DOCUMENT$$$" << endl ; inv . ukázat (); cout << "$$$DOCUMENT$$$" << endl ; } }

Příklad Pythonu

Zdrojový kód v Pythonu z abc import ABCMeta , abstraktní metoda třída Vojsko : """ Přijímač - Vojský objekt """ def move ( self , direction : str ) -> None : """ Začít se pohybovat určitým směrem """ print ( 'Čtvrť se začala pohybovat {} ' . format ( direction )) def stop ( self ) -> None : """ Stop """ print ( 'Squad zastaven' ) class Příkaz ( metaclass = ABCMeta ): """ Základní třída pro všechny příkazy """ @abstractmethod def vykonat ( self ) -> Žádné : """ Pokračujte ve vykonání příkazu """ pass @abstractmethod def unexecute ( self ) -> None : """ Unexecute command """ pass class AttackCommand ( Command ): """ Příkaz k provedení útoku je """ def __init__ ( self , jednotka : Vojsko ) -> Žádné : """ Konstruktor. :param troop: tlupa, která je spojena s příkazem " "" self .troop = tlupa def vykonat ( self ) -> None : self . vojsko . přesunout ( 'vpřed' ) def unexecute ( self ) -> None : self . vojsko . zastavit () class Příkaz k ústupu ( Příkaz ): """ Příkaz k ústupu """ def __init__ ( self , oddíl : Vojsko ) -> Žádný : """ Konstruktor. :param troop: oddíl, se kterým je spojen příkaz """ self . tlupa = tlupa def vykonat ( self ) -> None : self . vojsko . přesunout ( 'zpět' ) def unexecute ( self ) -> None : self . vojsko . zastavit () class TroopInterface : """ Invoker - rozhraní, přes které můžete vydávat příkazy konkrétnímu týmu """ def __init__ ( self , attack : AttackCommand , ústup : RetreatCommand ) -> None : """ Konstruktor. :param útok: příkaz k útoku :param ústup: příkaz k ústupu " "" self .attack_command = útok sám .retreat_command = ústup sám .current_command = Žádný # příkaz právě probíhá def attack ( self ) -> None : self . current_command = self . attack_command self . útočný_příkaz . provést () def retreat ( self ) -> None : self . current_command = self . ústup_příkaz self . příkaz k ústupu . provést () def stop ( self ) -> None : if self . aktuální_příkaz : self . aktuální_příkaz . nevykonat () sebe sama . current_command = Žádný jiný : print ( 'Jednotka se nemůže zastavit, protože se nepohybuje' ) if __name__ == '__main__' : jednotka = Rozhraní vojska () = rozhraní vojska ( AttackCommand ( vojsko ), RetreatCommand ( vojsko )) rozhraní . útočné () rozhraní . stop () rozhraní . ústup () rozhraní . zastavit ()

Příklad PHP5

Zdrojový kód PHP5 <?php /** * Abstraktní třída "příkazy" * @abstract */ abstract class Příkaz { public abstract function Execute (); veřejná abstraktní funkce UnExecute (); } /** * Třída konkrétního "příkazu" */ class CalculatorCommand rozšiřuje Příkaz { /** * Aktuální operace příkazu * * @var string */ public $operator ; /** * Aktuální operand * * @var mixed */ public $operand ; /** * Třída, pro kterou je příkaz určen * * @var objekt třídy Kalkulačka */ public $kalkulačka ; /** * Konstruktor * * @param objekt $kalkulačka * @param string $operátor * @param smíšený $operand */ public function __construct ( $calculator , $operator , $operand ) { $this -> calculator = $calculator ; $this -> operator = $operator ; $this -> operand = $operand ; } /** * Reimplementovaná funkce parent::Execute() */ public function Execute () { $this -> kalkulačka -> Operace ( $this -> operátor , $this -> operand ); } /** * Reimplementovaný rodič::UnExecute() funkce */ public function UnExecute () { $this -> kalkulačka -> Operace ( $this -> Undo ( $this -> operátor ), $this -> operand ); } /** * Jakou akci je třeba vrátit zpět? * * @private * @param string $operator * @return string */ private function Undo ( $operator ) { //najít opak pro každou provedenou akci switch ( $operator ) { case '+' : $undo = '-' ; zlomit ; case '-' : $undo = '+' ; zlomit ; case '*' : $undo = '/' ; zlomit ; case '/' : $undo = '*' ; zlomit ; výchozí : $undo = ' ' ; zlomit ; } return $undo ; } } /** * Přijímač třídy a vykonavatel "příkazů" */ class Kalkulačka { /** * Aktuální výsledek provádění příkazu * * @private * @var int */ private $curr = 0 ; public function Operace ( $operator , $operand ) { //vyber operátor pro výpočet výsledku switch ( $operator ) { case '+' : $this -> curr += $operand ; zlomit ; case '-' : $this -> curr -= $operand ; zlomit ; case '*' : $this -> curr *= $operand ; zlomit ; case '/' : $this -> curr /= $operand ; zlomit ; } print ( "Aktuální výsledek = $this->curr (po provedení $operator c $operand )" ); } } /** * Třída, která volá příkazy */ class User { /** * Tato třída bude přijímat příkazy, které mají být vykonány * * @private * @var objekt třídy Kalkulačka */ private $calculator ; /** * Pole operací * * @private * @var pole */ private $commands = array (); /** * Aktuální příkaz v poli operací * * @private * @var int */ private $current = 0 ; public function __construct () { // vytvoří instanci třídy, která bude provádět příkazy $this -> calculator = new Calculator (); } /** * Funkce pro vrácení zrušených příkazů * * @param int $levels počet operací k vrácení */ public function Redo ( $levels ) { print ( " \n ---- Opakujte operace $levels " ); // Návratové operace pro ( $i = 0 ; $i < $úrovně ; $i ++ ) if ( $this -> aktuální < počet ( $this -> příkazy ) - 1 ) $this -> příkazy [ $this - > aktuální ++ ] -> Execute (); } /** * Funkce příkazu zpět * * @param int $levels počet operací zpět */ public function Undo ( $levels ) { print ( " \n ---- Undo $levels operations " ); // Vrácení operací pro ( $i = 0 ; $i < $levels ; $i ++ ) if ( $this -> aktuální > 0 ) $this -> příkazy [ -- $this -> aktuální ] -> UnExecute ( ); } /** * Funkce provádění příkazu * * @param string $operator * @param mixed $operand */ public function Compute ( $operator , $operand ) { // Vytvořte příkaz operace a spusťte jej $command = new CalculatorCommand ( $this - > kalkulačka , $operátor , $operand ); $command -> Execute (); // Přidání operace do pole operací a zvýšení čítače aktuální operace $this -> commands [] = $command ; $this -> aktuální ++ ; } } $user = nový uživatel (); // Libovolné příkazy $user -> Compute ( '+' , 100 ); $user -> Compute ( '-' , 50 ); $user -> Compute ( '*' , 10 ); $user -> Compute ( '/' , 2 ); // Vrátit zpět 4 příkazy $user -> Undo ( 4 ); // Vrátí 3 zrušené příkazy. $user -> Znovu ( 3 );

Příklad Java

Zdroj Java

Aby bylo možné implementovat shodu názvů operací s akcí, operace na lampě (zapnout, vypnout) se přesunou do instance třídy SwitchOnCommanda SwitchOffCommandobě třídy implementují rozhraní Command.

import java.util.HashMap ; /** Rozhraní příkazu */ rozhraní Příkaz { void vykonat (); } /** Třída Invoker */ class Switch { private final HashMap < String , Command > commandMap = new HashMap <> (); public void register ( String commandName , Command command ) { commandMap . dát ( jméno příkazu , příkaz ); } public void vykonat ( String commandName ) { Command command = commandMap . get ( commandName ); if ( příkaz == null ) { throw new IllegalStateException ( "neregistrován žádný příkaz pro " + název příkazu ); } příkaz . provést (); } } /** Třída přijímače */ class Light { public void turnOn () { System . ven . println ( "Světlo svítí" ); } public void turnOff () { System . ven . println ( "Světlo nesvítí" ); } } /** Příkaz pro zapnutí světla - ConcreteCommand #1 */ class SwitchOnCommand implementuje Příkaz { private final Light light ; public SwitchOnCommand ( světlo světla ) { this . světlo = světlo ; } @Override // Příkaz public void vykonat () { light . zapnout (); } } /** Příkaz pro vypnutí světla - ConcreteCommand #2 */ class SwitchOffCommand implementuje Příkaz { private final Light light ; public SwitchOffCommand ( light light ) { this . světlo = světlo ; } @Override // Příkaz public void vykonat () { light . odbočka (); } } public class CommandDemo { public static void main ( final String [] argumenty ) { Light lampa = new Light (); Příkaz switchOn = nový SwitchOnCommand ( kontrolka ); Příkaz vypnoutOff = nový SwitchOffCommand ( kontrolka ); Switch mySwitch = new Switch (); mySwitch . registr ( "on" , switchOn ); mySwitch . registr ( "vypnout" , vypnout ); mySwitch . provést ( "zapnuto" ); mySwitch . vykonat ( "vypnuto" ); } } Pomocí funkčního rozhraní

Počínaje Java 8 není povinné vytvářet třídy SwitchOnCommanda SwitchOffCommandmísto toho můžeme použít operátor ::, jak je znázorněno v následujícím příkladu

public class CommandDemo { public static void main ( final String [] argumenty ) { Light lampa = new Light (); Příkazový spínačOn = lampa :: zapnout ; Příkaz vypínačOff = lampa :: vypnout ; Switch mySwitch = new Switch (); mySwitch . registr ( "on" , switchOn ); mySwitch . registr ( "vypnout" , vypnout ); mySwitch . provést ( "zapnuto" ); mySwitch . vykonat ( "vypnuto" ); } }

Příklad Swift 5

Zdrojový kód ve Swift 5 protokol příkaz { func execute() } // volající class Switch { enum SwitchAction { pouzdro zapnuto, vypnuto } stav var: Řetězec? var akce: Světlo? func register(_ příkaz: Light) { sebe.činnost = příkaz } func execute(_ commandName: SwitchAction) { if commandName == .on { akce?.turnOn() } else if commandName == .off { akce?.turnOff() } } } // Přijímač třída světlo { func turnOn() { print("Světlo svítí") } func turnOff() { print("Světlo nesvítí") } } class SwitchOnCommand: Příkaz { private var light: Světlo init(_light: Light) { self.light = světlo } func execute() { light.turnOn() } } class SwitchOffCommand: Příkaz { private var light: Světlo init(_light: Light) { self.light = světlo } func execute() { light.turnOff() } } // POUŽÍVEJTE nechej invoker = Switch() nech přijímač = Light() invoker.register(receiver) invoker.execute(.on)

Ruby příklad

Zdrojový kód Ruby module EngineCommands # Abstraktní třída 'Command' třída Příkaz def vykonat end end # Třída přijímače Engine attr_reader :state def inicializovat rpm @state , @rpm = false , rpm if rpm . je? Konec celého čísla def zapnout ; @stav = true ; end def turnOff ; @state = false ; konec konec # ConcreteCommand1 class CommandTurnOn < Příkaz def initialize engine @engine = engine if engine . je? konec motoru def vykonat @engine . zapnout konec konce # ConcreteCommand2 class CommandTurnOff < Příkaz def initialize engine @engine = engine if engine . je? konec motoru def vykonat @engine . vypnout konec konec # Třída Invoker Invoker def initialize @commands = Hash . nový konec def registerCommand název_příkazu , příkaz @ příkazy [ název_příkazu ] = příkaz if příkaz . je? Příkaz a @ příkazy [ commandName ]. je? Konec NilClass def vykonatPříkaz název_příkazu @ příkaz = @příkazy [ název_příkazu ], pokud není @příkaz . je? NilClass @command . vykonat else raise TypeError . nový konec konec konec konec # Klientský modul Klient obsahuje EngineCommands invoker = Invoker . Nový motor = motor . nové ( 250 ) commandTurnOn = CommandTurnOn . new ( motor ) commandTurnOff = CommandTurnOff . nový ( motor ) vyvolávač . registerCommand "turnOn" , commandTurnOn invoker . registerPříkaz "turnOff" , příkazTurnOff vloží " \t Stav motoru před použitím příkazu: #{ engine . stav } " # => Stav motoru před použitím příkazu: false vloží " \t Stav motoru po použití příkazu 'turnOn': #{ invoker . executeCommand "turnOn" } " # => Stav motoru po použití příkazu 'turnOn': true klade " \t Stav motoru po použití příkazu 'turnOff': #{ invoker . executeCommand "turnOff" } " # => Stav motoru po použití příkazu 'turnOff': false end

Odkazy