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 .
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 ; } }
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 ()
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 );
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)
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