Vzor příkazu - Command pattern

V objektově orientovaného programování je příkaz vzor je behaviorální návrhový vzor , ve kterém je objekt slouží k zapouzdření veškeré údaje potřebné k provedení akce nebo spouštět událost v pozdější době. Tyto informace zahrnují název metody, objekt, který vlastní metodu, a hodnoty parametrů metody.

Čtyři pojmy, které jsou vždy spojené se vzorem příkazu , jsou příkaz , přijímač , vyvolávač a klient . Objekt příkazu ví o přijímači a vyvolá metodu přijímače. Hodnoty pro parametry metody přijímače jsou uloženy v příkazu. Objekt příjemce pro provedení těchto metod je také uložen v objektu příkazu agregací . Přijímač pak dělá práci, když je execute()metoda příkazu se nazývá. Objekt volajícího ví, jak provést příkaz, a volitelně provádí účetnictví o provádění příkazu. Vyvolávač neví nic o konkrétním příkazu, ví jen o příkazovém rozhraní . Objekty Invoker, objekty příkazů a objekty přijímače jsou drženy objektem klienta , klient rozhodne, které objekty přijímače přiřadí objektům příkazu a které příkazy přiřadí volajícímu. Klient se rozhodne, které příkazy se mají v kterém bodě provést. Chcete-li provést příkaz, předá objekt příkazu objektu invoker.

Použití příkazových objektů usnadňuje konstrukci obecných komponent, které potřebují delegovat, sekvenovat nebo spouštět volání metod v době jejich výběru, aniž by bylo nutné znát třídu metody nebo parametry metody. Použití objektu invoker umožňuje pohodlné vedení účetnictví o provádění příkazů a také implementaci různých režimů pro příkazy, které jsou spravovány objektem invoker, aniž by klient musel vědět o existenci účetnictví nebo režimů.

Ústřední myšlenky tohoto návrhového vzoru úzce odrážejí sémantiku prvotřídních funkcí a funkcí vyššího řádu ve funkčních programovacích jazycích . Objekt invoker je konkrétně funkcí vyššího řádu, jejíž objekt příkazu je argumentem první třídy.

Přehled

Návrhový vzor příkazu je jedním z dvaceti tří známých návrhových vzorů GoF, které popisují, jak řešit opakující se návrhové problémy při navrhování flexibilního a opakovaně použitelného objektově orientovaného softwaru, tj. Objektů, které lze snadněji implementovat, měnit, testovat a znovu použít.

Použití vzoru návrhu příkazu může vyřešit tyto problémy:

  • Je třeba se vyhnout spojování vyvolávajícího požadavku s konkrétním požadavkem. To znamená, že je třeba se vyhnout pevně připojeným požadavkům.
  • Mělo by být možné nakonfigurovat objekt (který vyvolá požadavek) s požadavkem.

Implementace (pevné zapojení) požadavku přímo do třídy je nepružná, protože spojuje třídu s konkrétním požadavkem v době kompilace, což znemožňuje specifikovat požadavek za běhu.

Použití návrhového vzoru příkazu popisuje následující řešení:

  • Definujte samostatné (příkazové) objekty, které zapouzdřují požadavek.
  • Třída deleguje požadavek na objekt příkazu namísto přímé implementace konkrétního požadavku.

To umožňuje konfigurovat třídu pomocí objektu příkazu, který se používá k provedení požadavku. Třída již není spojena s konkrétním požadavkem a nemá žádné znalosti (nezávislé) o tom, jak se požadavek provádí.

Viz také diagram tříd a sekvencí UML níže.

Struktura

Třída a sekvenční diagram UML

Ukázkový diagram třídy UML a sekvence pro návrhový vzor příkazu.

Ve výše uvedeném UML diagramu tříd je Invokertřída neimplementuje žádost přímo. Místo toho Invokerodkazuje na Commandrozhraní k provedení požadavku ( command.execute()), čímž je Invokernezávislý na tom, jak je požadavek prováděn. Tyto Command1třídy implementuje Commandrozhraní podle provedení akce na přijímači ( receiver1.action1()).

UML sekvenční diagram ukazuje, run-time interakce v: The Invokerobjekt hovory execute()na Command1objektu. Command1volá action1()na Receiver1objekt, který provádí požadavek.

Diagram tříd UML

UML diagram vzoru příkazu

Použití

Tlačítka GUI a položky nabídky
V programování Swing a Borland DelphiAction je an příkazový objekt. Kromě schopnosti provést požadovaný příkaz může mít akce přidruženou ikonu, klávesovou zkratku, text popisu nástroje atd. Tlačítko panelu nástrojů nebo komponentu položky nabídky lze úplně inicializovat pouze pomocí objektu Action .
Makro záznam
Pokud jsou všechny akce uživatele reprezentovány objekty příkazů, program může zaznamenat posloupnost akcí jednoduše tak, že ponechá seznam objektů příkazů při jejich provádění. Poté může "přehrávat" stejné akce opakovaným spouštěním stejných příkazových objektů. Pokud program vloží skriptovací modul, může každý objekt příkazu implementovat metodu toScript () a akce uživatelů lze poté snadno zaznamenat jako skripty.
Mobilní kód
Pomocí jazyků, jako je Java, kde lze kód streamovat / rozdělovat z jednoho místa na druhé pomocí URLClassloaders a Codebases, mohou příkazy umožnit doručování nového chování do vzdálených umístění (příkaz EJB, Master Worker).
Víceúrovňové vrácení zpět
Pokud jsou všechny akce uživatele v programu implementovány jako objekty příkazů, může program uchovat hromadu naposledy spuštěných příkazů. Pokud chce uživatel zrušit příkaz, program jednoduše vyskočí na nejnovější příkazový objekt a provede jeho metodu undo () .
Síťování
Je možné posílat celé objekty příkazů po síti, které mají být provedeny na ostatních strojích, například akce hráčů v počítačových hrách.
Paralelní zpracování
Kde jsou příkazy zapisovány jako úkoly do sdíleného prostředku a prováděny paralelně mnoha vlákny (možná na vzdálených počítačích; tato varianta se často označuje jako vzor Master / Worker)
Indikátory průběhu
Předpokládejme, že program má sled příkazů, které provádí v pořadí. Pokud má každý příkazový objekt metodu getEstimatedDuration () , může program snadno odhadnout celkovou dobu trvání. Může zobrazit indikátor průběhu, který smysluplně odráží, jak blízko je program k dokončení všech úkolů.
Bazény závitů
Typická, obecná třída fondu vláken může mít veřejnou metodu addTask (), která přidá pracovní položku do interní fronty úkolů čekajících na dokončení. Udržuje fond vláken, která spouští příkazy z fronty. Položky ve frontě jsou objekty příkazů. Tyto objekty obvykle implementují společné rozhraní, jako je java.lang.Runnable, které umožňuje fondu vláken vykonat příkaz, přestože samotná třída fondu vláken byla napsána bez znalosti konkrétních úkolů, pro které by byla použita.
Transakční chování
Podobně jako zpět může databázový stroj nebo instalační program softwaru vést seznam operací, které byly nebo budou provedeny. Pokud by jeden z nich selhal, všechny ostatní mohou být obráceny nebo vyřazeny (obvykle se tomu říká rollback ). Například pokud musí být aktualizovány dvě databázové tabulky, které na sebe odkazují, a druhá aktualizace selže, lze transakci vrátit zpět, takže první tabulka nyní neobsahuje neplatný odkaz.
Čarodějové
Průvodce často představuje několik stránek konfigurace pro jednu akci, ke které dojde, pouze když uživatel klikne na tlačítko „Dokončit“ na poslední stránce. V těchto případech je přirozeným způsobem, jak oddělit kód uživatelského rozhraní od kódu aplikace, implementovat průvodce pomocí objektu příkazu. Objekt příkazu je vytvořen při prvním zobrazení průvodce. Každá stránka průvodce ukládá své změny grafického uživatelského rozhraní do objektu příkazu, takže objekt je naplněn postupem uživatele. „Dokončit“ jednoduše spustí volání execute () . Tímto způsobem bude třída příkazů fungovat.

Terminologie

Terminologie použitá k popisu implementace vzorů příkazů není konzistentní, a proto může být matoucí. To je výsledkem nejednoznačnosti , použití synonym a implementací, které mohou zakrýt původní vzor tím, že jdou daleko za něj.

  1. Dvojznačnost.
    1. Termín příkaz je dvojznačný. Například pohyb nahoru, pohyb nahoru může odkazovat na jeden příkaz (pohyb nahoru), který by měl být proveden dvakrát, nebo může odkazovat na dva příkazy, z nichž každý dělá stejnou věc (pohyb nahoru). Pokud je předchozí příkaz dvakrát přidán do zásobníku pro vrácení zpět, obě položky v zásobníku odkazují na stejnou instanci příkazu. To může být vhodné, když lze příkaz vrátit vždy stejným způsobem (např. Přejít dolů). Jak Gang čtyř, tak příklad Java níže používají tuto interpretaci termínu příkaz . Na druhou stranu, pokud jsou tyto příkazy přidány do zásobníku zpět, zásobník odkazuje na dva samostatné objekty. To může být vhodné, když každý objekt v zásobníku musí obsahovat informace, které umožňují vrácení příkazu. Chcete-li například vrátit příkaz k odstranění výběru , může objekt obsahovat kopii odstraněného textu, aby jej bylo možné znovu vložit, pokud je nutné příkaz k odstranění výběru vrátit zpět. Všimněte si, že použití samostatného objektu pro každé vyvolání příkazu je také příkladem vzoru řetězce odpovědnosti .
    2. Termín vykonat je také nejednoznačný. To může odkazovat se na spuštění kódu identifikovaného pomocí příkazu objektu spustit způsobu. V Microsoft Windows Presentation Foundation se však příkaz považuje za provedený, když byla vyvolána metoda spuštění příkazu , ale to nutně neznamená, že byl spuštěn kód aplikace. K tomu dojde až po dalším zpracování událostí.
  2. Synonyma a homonyma .
    1. Klient, Zdroj, Vyvolávač : klepnutí na tlačítko, tlačítko na panelu nástrojů nebo na položku nabídky, klávesová zkratka stisknutá uživatelem.
    2. Objekt příkazu, směrovaný objekt příkazu, objekt akce : samostatný objekt (např. Existuje pouze jeden objekt CopyCommand), který ví o klávesových zkratkách, obrázcích tlačítek, textu příkazu atd. Souvisejících s příkazem. Objekt source / invoker volá metodu execute / performAction objektu Command / Action. Objekt Command / Action upozorní příslušné objekty zdroje / vyvolávače, když se změnila dostupnost příkazu / akce. To umožňuje, aby tlačítka a položky nabídky byly neaktivní (šedé), když nelze provést / provést příkaz / akci.
    3. Přijímač, Target Object : objekt, který má být kopírován, slepený, přesouvat, atd. Objekt přijímače vlastní metodu, která se nazývá pomocí příkazu je spustit metodu. Přijímač je obvykle také cílovým objektem. Například pokud je objekt přijímače kurzor a metoda se nazývá moveUp , pak by se dalo očekávat, že kurzor je cílem akce moveUp. Na druhou stranu, pokud je kód definován samotným objektem příkazu, bude cílovým objektem úplně jiný objekt.
    4. Objekt příkazu, směrované argumenty události, objekt události : objekt, který se předává ze zdroje do objektu Command / Action, do objektu Target do kódu, který provádí práci. Každé kliknutí na tlačítko nebo klávesová zkratka má za následek nový objekt příkazu / události. Některé implementace přidávají více informací k objektu příkazu / události při jeho předávání z jednoho objektu (např. CopyCommand) do druhého (např. Sekce dokumentu). Jiné implementace umisťují objekty příkazů / událostí do jiných objektů událostí (například do pole uvnitř většího pole), když se pohybují po řádku, aby nedocházelo ke konfliktům názvů. (Viz také vzor řetězce odpovědnosti .)
    5. Handler, ExecutedRoutedEventHandler, metoda, funkce : skutečný kód, který provádí kopírování, vkládání, přesouvání atd. V některých implementacích je kód obslužné rutiny součástí objektu příkazu / akce. V jiných implementacích je kód součástí přijímače / cílového objektu a v ještě dalších implementacích je kód obslužné rutiny udržován odděleně od ostatních objektů.
    6. Správce příkazů, Správce zpět, Plánovač, Fronta, Odesílatel, Vyvolávač : objekt, který vloží objekty příkazů / událostí do zásobníku zpět nebo znovu, nebo který drží objekty příkazů / událostí, dokud na ně nejsou připraveny jiné objekty, nebo který směruje objekty příkazu / události do příslušného kódu přijímače / cílového objektu nebo obslužné rutiny.
  3. Implementace, které přesahují původní vzor příkazu.
    1. Microsoft Windows Presentation Foundation (WPF) zavádí směrované příkazy, které kombinují vzor příkazu se zpracováním události. Výsledkem je, že objekt příkazu již neobsahuje odkaz na cílový objekt ani odkaz na kód aplikace. Místo toho vyvolání příkazu spuštění objektu příkazu vede k takzvané Executed Routed Event, která se během tunelování nebo probublávání události může setkat s takzvaným vazebným objektem, který identifikuje cíl a kód aplikace, který se v daném okamžiku provede.

Příklad

Zvažte „jednoduchý“ přepínač. V tomto příkladu nakonfigurujeme přepínač pomocí dvou příkazů: zapnutí světla a vypnutí světla.

Výhodou této konkrétní implementace povelového vzoru je, že přepínač lze použít s jakýmkoli zařízením, nejen s osvětlením. Přepínač v následující implementaci C # zapíná a vypíná světlo, ale konstruktor přepínače je schopen přijmout všechny podtřídy příkazu pro jeho dva parametry. Přepínač můžete například nakonfigurovat tak, aby spouštěl 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.");
            }
        }
    }
}


Viz také

Zdroje

První publikovanou zmínkou o použití třídy Command k implementaci interaktivních systémů se zdá být článek Henryho Liebermana z roku 1985. První publikoval popis ukázkového (víceúrovnostní) undo-redo mechanismus, pomocí třídy Command se spustit a zpětný krok metody a seznam historie, vypadá, že je první (1988) vydání Bertrand Meyer knihy ‚s objektově orientovaného softwaru Konstrukce , oddíl 12.2.

Reference

externí odkazy