Команда (англ. Command) — шаблон проєктування, відноситься до класу шаблонів поведінки. Також відомий як Дія (англ. Action), Транзакція (англ. Transaction).
Призначення
Інкапсулює запит у формі об'єкта, дозволяючи тим самим задавати параметри клієнтів для обробки відповідних запитів, ставити запити у чергу або протоколювати їх, а також підтримувати скасовування операцій.
Мотивація
Створення структури, в якій клас-відправник і клас-отримувач не залежать один від одного напряму. Організація зворотного виклику до класу, який містить у собі клас-відправник.
Застосовність
Слід використовувати шаблон Команда коли:
- треба параметризувати об'єкти дією. У процедурній мові таку параметризацію можна виразити за допомогою функції зворотнього виклику, тобто такою функцією, яка реєструється, щоби бути викликаною пізніше. Команди є об'єктно-орієнтованою альтернативою функціям зворотньогоо виклику;
- визначати, ставити у чергу та виконувати запити у різний час. Термін життя об'єкта Команда не обов'язково залежить від терміну життя початкового запиту. Якщо отримувача вдається реалізувати таким чином, щоб він не залежав від адресного простору, то об'єкт-команду можна передати іншому процесу, який займеться його виконанням;
- потрібна підтримка скасовування операцій. Операція Execute об'єкта Команда може зберегти стан, що необхідний для скасування дій, виконаних Командою. У цьому разі у інтерфейсі класу Command повинна бути додаткова операція Unexecute, котра скасовує дії, виконанні попереднім викликом операції Execute. Виконані команди зберігаються у списку історії. Для реалізації довільної кількості рівней скасування та повтору команд треба обходити цей список відповідно в зворотньому та прямому напрямках, викликаючи під час відвідування кожного елементу операцію Unexecute або Execute;
- підтримати протоколювання змін, щоб їх можна було виконати повторно після аварійної зупинки системи. Доповнивши інтерфейс класу Command операціями зберігання та завантаження, можна вести протокол змін у внутрішній пам'яті. Для відновлення після збою треба буде завантажити збереженні команди з диску та повторно виконати їх за допомогою операції Execute;
- треба структурувати систему на основі високорівневих операцій, що побудовані з примітивних. Така структура є типовою для інформаційних систем, що підтримують транзакції. Транзакція інкапсулює множину змін даних. Шаблон Команда дозволяє моделювати транзакції. В усіх команд є спільний інтерфейс, що надає можливість працювати однаково з будь-якими транзакціями. За допомогою цього шаблону можна легко додавати у систему нові види транзакцій.
Структура
- Command — команда:
- оголошує інтерфейс для виконання операції;
- ConcreteCommand — конкретна команда:
- визначає зв'язок між об'єктом-отримувачем Receiver та дією;
- реалізує операцію Execute шляхом виклику відповідних операцій об'єкта Receiver;
- Client — клієнт:
- створює об'єкт класу ConcreteCommand та встановлює його отримувача;
- Invoker — викликач:
- звертається до команди щоб та виконала запит;
- Receiver — отримувач:
- має у своєму розпорядженні усю інформацію про способи виконання операцій, необхідних для задоволення запиту. У ролі отримувача може виступати будь-який клас.
Відносини
- клієнт створює об'єкт ConcreteCommand та встановлює для нього отримувача;
- викликач Invoker зберігає об'єкт ConcreteCommand;
- викликач надсилає запит, викликаючи операцію команди Execute. Якщо підтримується скасування виконаних дій, то ConcreteCommand перед викликом Execute зберігає інформацію про стан, достатню для виконання скасування;
- об'єкт ConcreteCommand викликає операції отримувача для виконання запиту
На діаграмі видно, як Command розриває зв'язок між викликачем та отримувачем (а також запитом, що повинен бути виконаний останнім).
Переваги
- Відокремлює класи, які викликають операцію від об'єкта, який вміє виконувати операцію
- Дозволяє створювати послідовність команд за допомогою системи черги
- Розширення для додавання нової команди є простими і можуть бути виконані без зміни існуючого коду
- Ви також можете визначити систему відкату з командним шаблоном, наприклад, у прикладі майстра, ми можемо написати метод відкату
Недоліки
- Збільшення кількості класів для кожної окремої команди
Реалізація
C++
#include <iostream> #include <vector> using namespace std; struct Command // Основа патерну { virtual void execute() = 0; }; // Уточнення struct Hello : public Command { virtual void execute() { cout << " Hello "; }; }; struct World : public Command { virtual void execute() { cout << " World!"; }; }; struct IAm : public Command { virtual void execute() { cout << " I am the command pattern!"; } }; // Місце, де застосовують команду class Macro { private: vector< Command*> commands; public: void add(Command* c) { commands.push_back(c); } void run() { vector< Command*> ::iterator it = commands.begin(); while (it != commands.end()) (*it++)->execute(); } }; void main() { Macro macro; macro.add(new Hello); macro.add(new World); macro.add(new IAm); macro.run(); }
C#
using System; using System.Linq; using System.Collections.Generic; namespace Command { // основа патерну public interface ICommand { string Name { get; } void Execute(); void UnExecute(); } // різноманітні команди class ChangeColorCommand : ICommand { // стан об'єкта до і після застосування команди private readonly ConsoleColor newColor; private readonly ConsoleColor prevColor; public ChangeColorCommand(ConsoleColor newColor) { this.newColor = newColor; this.prevColor = Console.ForegroundColor; } public string Name => $"Change foreground color to {newColor}"; public void Execute() { Console.ForegroundColor = newColor; } public void UnExecute() { Console.ForegroundColor = prevColor; } } class ChangeBackColorCommand : ICommand { private readonly ConsoleColor newColor; private readonly ConsoleColor prevColor; public ChangeBackColorCommand(ConsoleColor newColor) { this.newColor = newColor; this.prevColor = Console.BackgroundColor; } public string Name => $"Change background color to {newColor}"; public void Execute() { Console.BackgroundColor = newColor; } public void UnExecute() { Console.BackgroundColor = prevColor; } } // Місце, де застосовують команди class UndoRedoManager { // історії команд Stack<ICommand> undoStack; Stack<ICommand> redoStack; public event EventHandler StateChanged; public UndoRedoManager() { undoStack = new Stack<ICommand>(); redoStack = new Stack<ICommand>(); } public bool CanUndo => undoStack.Count > 0; public bool CanRedo => redoStack.Count > 0; public void Undo() { if (CanUndo) { ICommand command = undoStack.Pop(); command.UnExecute(); redoStack.Push(command); StateChanged?.Invoke(this, EventArgs.Empty); } } public void Redo() { if (CanRedo) { ICommand command = redoStack.Pop(); command.Execute(); undoStack.Push(command); StateChanged?.Invoke(this, EventArgs.Empty); } } // усі команди виконуються через метод Execute public void Execute(ICommand command) { command.Execute(); undoStack.Push(command); redoStack.Clear(); StateChanged?.Invoke(this, EventArgs.Empty); } public IEnumerable<string> UndoItems => undoStack.Select(c => c.Name); public IEnumerable<string> RedoItems => redoStack.Select(c => c.Name); public void Undo(int count) { for (int i = 0; i < count; ++i) Undo(); } public void Redo(int count) { for (int i = 0; i < count; ++i) Redo(); } } class Program { // напишемо фасад для легшого керування системою class Menu { // FIELDS UndoRedoManager undoRedoManager; bool exit; // CONSTRUCTORS public Menu() { undoRedoManager = new UndoRedoManager(); } // METHODS public void Run() { while (!exit) { ShowMenuItems(); int userChoice = GetInput(); Perform(userChoice); } } private void ShowMenuItems() { Console.Clear(); Console.WriteLine("Choose option"); Console.WriteLine(); Console.WriteLine("0 - Get undo commands list"); Console.WriteLine("1 - Get redo commands list"); Console.WriteLine("2 - Change foreground color"); Console.WriteLine("3 - Change background color"); Console.WriteLine("4 - Undo"); Console.WriteLine("5 - Redo"); Console.WriteLine("6 - Exit"); Console.WriteLine(); } private int GetInput() { // get user choice int userChoice; do { Console.WriteLine("Your input:"); } while (!int.TryParse(Console.ReadLine(), out userChoice)); return userChoice; } private void Perform(int userChoice) { switch (userChoice) { case 0: GetUndoCommandList(); break; case 1: GetRedoCommandList(); break; case 2: ChangeForegroundColor(); break; case 3: ChangeBackgroundColor(); break; case 4: Undo(); break; case 5: Redo(); break; case 6: Exit(); break; default: Console.WriteLine("Wrong choice"); break; } Console.WriteLine("Press enter"); Console.ReadLine(); } // ACTIONS private void GetUndoCommandList() { Console.WriteLine("Undo list:"); foreach(string commandName in undoRedoManager.UndoItems) { Console.WriteLine(commandName); } } private void GetRedoCommandList() { Console.WriteLine("Redo list:"); foreach (string commandName in undoRedoManager.RedoItems) { Console.WriteLine(commandName); } } private void ChangeForegroundColor() { // get user input ConsoleColor newForegroundColor; string color = string.Empty; do { Console.WriteLine("Write new color"); color = Console.ReadLine(); } while (!Enum.TryParse(color, out newForegroundColor)); // execute command undoRedoManager.Execute(new ChangeColorCommand(newForegroundColor)); } private void ChangeBackgroundColor() { // get user input ConsoleColor newBackgroundColor; string color = string.Empty; do { Console.WriteLine("Write new color"); color = Console.ReadLine(); } while (!Enum.TryParse(color, out newBackgroundColor)); // execute command undoRedoManager.Execute(new ChangeBackColorCommand(newBackgroundColor)); } private void Undo() { undoRedoManager.Undo(); } private void Redo() { undoRedoManager.Redo(); } private void Exit() { exit = true; } } static void Main(string[] args) { new Menu().Run(); } } }
Swift
protocol DoorCommand { func execute() -> String } class OpenCommand : DoorCommand { let doors:String required init(doors: String) { self.doors = doors } func execute() -> String { return "Opened \(doors)" } } class CloseCommand : DoorCommand { let doors:String required init(doors: String) { self.doors = doors } func execute() -> String { return "Closed \(doors)" } } class HAL9000DoorsOperations { let openCommand: DoorCommand let closeCommand: DoorCommand init(doors: String) { self.openCommand = OpenCommand(doors:doors) self.closeCommand = CloseCommand(doors:doors) } func close() -> String { return closeCommand.execute() } func open() -> String { return openCommand.execute() } } let podBayDoors = "Pod Bay Doors" let doorModule = HAL9000DoorsOperations(doors:podBayDoors) doorModule.open() doorModule.close()
Джерела
- Design Patterns: Elements of Reusable Object-Oriented Software [ 9 Листопада 2012 у Wayback Machine.]
Посилання
- . Архів оригіналу за 30 Січня 2016. Процитовано 10 Березня 2016.
Література
Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — .
Вікіпедія, Українська, Україна, книга, книги, бібліотека, стаття, читати, завантажити, безкоштовно, безкоштовно завантажити, mp3, відео, mp4, 3gp, jpg, jpeg, gif, png, малюнок, музика, пісня, фільм, книга, гра, ігри, мобільний, телефон, android, ios, apple, мобільний телефон, samsung, iphone, xiomi, xiaomi, redmi, honor, oppo, nokia, sonya, mi, ПК, web, Інтернет
Komanda angl Command shablon proyektuvannya vidnositsya do klasu shabloniv povedinki Takozh vidomij yak Diya angl Action Tranzakciya angl Transaction PriznachennyaInkapsulyuye zapit u formi ob yekta dozvolyayuchi tim samim zadavati parametri kliyentiv dlya obrobki vidpovidnih zapitiv staviti zapiti u chergu abo protokolyuvati yih a takozh pidtrimuvati skasovuvannya operacij MotivaciyaStvorennya strukturi v yakij klas vidpravnik i klas otrimuvach ne zalezhat odin vid odnogo napryamu Organizaciya zvorotnogo vikliku do klasu yakij mistit u sobi klas vidpravnik ZastosovnistSlid vikoristovuvati shablon Komanda koli treba parametrizuvati ob yekti diyeyu U procedurnij movi taku parametrizaciyu mozhna viraziti za dopomogoyu funkciyi zvorotnogo vikliku tobto takoyu funkciyeyu yaka reyestruyetsya shobi buti viklikanoyu piznishe Komandi ye ob yektno oriyentovanoyu alternativoyu funkciyam zvorotnogoo vikliku viznachati staviti u chergu ta vikonuvati zapiti u riznij chas Termin zhittya ob yekta Komanda ne obov yazkovo zalezhit vid terminu zhittya pochatkovogo zapitu Yaksho otrimuvacha vdayetsya realizuvati takim chinom shob vin ne zalezhav vid adresnogo prostoru to ob yekt komandu mozhna peredati inshomu procesu yakij zajmetsya jogo vikonannyam potribna pidtrimka skasovuvannya operacij Operaciya Execute ob yekta Komanda mozhe zberegti stan sho neobhidnij dlya skasuvannya dij vikonanih Komandoyu U comu razi u interfejsi klasu Command povinna buti dodatkova operaciya Unexecute kotra skasovuye diyi vikonanni poperednim viklikom operaciyi Execute Vikonani komandi zberigayutsya u spisku istoriyi Dlya realizaciyi dovilnoyi kilkosti rivnej skasuvannya ta povtoru komand treba obhoditi cej spisok vidpovidno v zvorotnomu ta pryamomu napryamkah viklikayuchi pid chas vidviduvannya kozhnogo elementu operaciyu Unexecute abo Execute pidtrimati protokolyuvannya zmin shob yih mozhna bulo vikonati povtorno pislya avarijnoyi zupinki sistemi Dopovnivshi interfejs klasu Command operaciyami zberigannya ta zavantazhennya mozhna vesti protokol zmin u vnutrishnij pam yati Dlya vidnovlennya pislya zboyu treba bude zavantazhiti zberezhenni komandi z disku ta povtorno vikonati yih za dopomogoyu operaciyi Execute treba strukturuvati sistemu na osnovi visokorivnevih operacij sho pobudovani z primitivnih Taka struktura ye tipovoyu dlya informacijnih sistem sho pidtrimuyut tranzakciyi Tranzakciya inkapsulyuye mnozhinu zmin danih Shablon Komanda dozvolyaye modelyuvati tranzakciyi V usih komand ye spilnij interfejs sho nadaye mozhlivist pracyuvati odnakovo z bud yakimi tranzakciyami Za dopomogoyu cogo shablonu mozhna legko dodavati u sistemu novi vidi tranzakcij StrukturaUML diagrama sho opisuye strukturu shablonu proyektuvannya Komanda Command komanda ogoloshuye interfejs dlya vikonannya operaciyi ConcreteCommand konkretna komanda viznachaye zv yazok mizh ob yektom otrimuvachem Receiver ta diyeyu realizuye operaciyu Execute shlyahom vikliku vidpovidnih operacij ob yekta Receiver Client kliyent stvoryuye ob yekt klasu ConcreteCommand ta vstanovlyuye jogo otrimuvacha Invoker viklikach zvertayetsya do komandi shob ta vikonala zapit Receiver otrimuvach maye u svoyemu rozporyadzhenni usyu informaciyu pro sposobi vikonannya operacij neobhidnih dlya zadovolennya zapitu U roli otrimuvacha mozhe vistupati bud yakij klas VidnosiniUML diagrama sho opisuye vzayemovidnosini pomizh ob yektiv shablonu proyektuvannya Komanda kliyent stvoryuye ob yekt ConcreteCommand ta vstanovlyuye dlya nogo otrimuvacha viklikach Invoker zberigaye ob yekt ConcreteCommand viklikach nadsilaye zapit viklikayuchi operaciyu komandi Execute Yaksho pidtrimuyetsya skasuvannya vikonanih dij to ConcreteCommand pered viklikom Execute zberigaye informaciyu pro stan dostatnyu dlya vikonannya skasuvannya ob yekt ConcreteCommand viklikaye operaciyi otrimuvacha dlya vikonannya zapitu Na diagrami vidno yak Command rozrivaye zv yazok mizh viklikachem ta otrimuvachem a takozh zapitom sho povinen buti vikonanij ostannim PerevagiVidokremlyuye klasi yaki viklikayut operaciyu vid ob yekta yakij vmiye vikonuvati operaciyu Dozvolyaye stvoryuvati poslidovnist komand za dopomogoyu sistemi chergi Rozshirennya dlya dodavannya novoyi komandi ye prostimi i mozhut buti vikonani bez zmini isnuyuchogo kodu Vi takozh mozhete viznachiti sistemu vidkatu z komandnim shablonom napriklad u prikladi majstra mi mozhemo napisati metod vidkatuNedolikiZbilshennya kilkosti klasiv dlya kozhnoyi okremoyi komandiRealizaciyaC Priklad realizaciyi na movi S include lt iostream gt include lt vector gt using namespace std struct Command Osnova paternu virtual void execute 0 Utochnennya struct Hello public Command virtual void execute cout lt lt Hello struct World public Command virtual void execute cout lt lt World struct IAm public Command virtual void execute cout lt lt I am the command pattern Misce de zastosovuyut komandu class Macro private vector lt Command gt commands public void add Command c commands push back c void run vector lt Command gt iterator it commands begin while it commands end it gt execute void main Macro macro macro add new Hello macro add new World macro add new IAm macro run C Priklad realizaciyi na movi S using System using System Linq using System Collections Generic namespace Command osnova paternu public interface ICommand string Name get void Execute void UnExecute riznomanitni komandi class ChangeColorCommand ICommand stan ob yekta do i pislya zastosuvannya komandi private readonly ConsoleColor newColor private readonly ConsoleColor prevColor public ChangeColorCommand ConsoleColor newColor this newColor newColor this prevColor Console ForegroundColor public string Name gt Change foreground color to newColor public void Execute Console ForegroundColor newColor public void UnExecute Console ForegroundColor prevColor class ChangeBackColorCommand ICommand private readonly ConsoleColor newColor private readonly ConsoleColor prevColor public ChangeBackColorCommand ConsoleColor newColor this newColor newColor this prevColor Console BackgroundColor public string Name gt Change background color to newColor public void Execute Console BackgroundColor newColor public void UnExecute Console BackgroundColor prevColor Misce de zastosovuyut komandi class UndoRedoManager istoriyi komand Stack lt ICommand gt undoStack Stack lt ICommand gt redoStack public event EventHandler StateChanged public UndoRedoManager undoStack new Stack lt ICommand gt redoStack new Stack lt ICommand gt public bool CanUndo gt undoStack Count gt 0 public bool CanRedo gt redoStack Count gt 0 public void Undo if CanUndo ICommand command undoStack Pop command UnExecute redoStack Push command StateChanged Invoke this EventArgs Empty public void Redo if CanRedo ICommand command redoStack Pop command Execute undoStack Push command StateChanged Invoke this EventArgs Empty usi komandi vikonuyutsya cherez metod Execute public void Execute ICommand command command Execute undoStack Push command redoStack Clear StateChanged Invoke this EventArgs Empty public IEnumerable lt string gt UndoItems gt undoStack Select c gt c Name public IEnumerable lt string gt RedoItems gt redoStack Select c gt c Name public void Undo int count for int i 0 i lt count i Undo public void Redo int count for int i 0 i lt count i Redo class Program napishemo fasad dlya legshogo keruvannya sistemoyu class Menu FIELDS UndoRedoManager undoRedoManager bool exit CONSTRUCTORS public Menu undoRedoManager new UndoRedoManager METHODS public void Run while exit ShowMenuItems int userChoice GetInput Perform userChoice private void ShowMenuItems Console Clear Console WriteLine Choose option Console WriteLine Console WriteLine 0 Get undo commands list Console WriteLine 1 Get redo commands list Console WriteLine 2 Change foreground color Console WriteLine 3 Change background color Console WriteLine 4 Undo Console WriteLine 5 Redo Console WriteLine 6 Exit Console WriteLine private int GetInput get user choice int userChoice do Console WriteLine Your input while int TryParse Console ReadLine out userChoice return userChoice private void Perform int userChoice switch userChoice case 0 GetUndoCommandList break case 1 GetRedoCommandList break case 2 ChangeForegroundColor break case 3 ChangeBackgroundColor break case 4 Undo break case 5 Redo break case 6 Exit break default Console WriteLine Wrong choice break Console WriteLine Press enter Console ReadLine ACTIONS private void GetUndoCommandList Console WriteLine Undo list foreach string commandName in undoRedoManager UndoItems Console WriteLine commandName private void GetRedoCommandList Console WriteLine Redo list foreach string commandName in undoRedoManager RedoItems Console WriteLine commandName private void ChangeForegroundColor get user input ConsoleColor newForegroundColor string color string Empty do Console WriteLine Write new color color Console ReadLine while Enum TryParse color out newForegroundColor execute command undoRedoManager Execute new ChangeColorCommand newForegroundColor private void ChangeBackgroundColor get user input ConsoleColor newBackgroundColor string color string Empty do Console WriteLine Write new color color Console ReadLine while Enum TryParse color out newBackgroundColor execute command undoRedoManager Execute new ChangeBackColorCommand newBackgroundColor private void Undo undoRedoManager Undo private void Redo undoRedoManager Redo private void Exit exit true static void Main string args new Menu Run Swift Priklad realizaciyi na movi Swift protocol DoorCommand func execute gt String class OpenCommand DoorCommand let doors String required init doors String self doors doors func execute gt String return Opened doors class CloseCommand DoorCommand let doors String required init doors String self doors doors func execute gt String return Closed doors class HAL9000DoorsOperations let openCommand DoorCommand let closeCommand DoorCommand init doors String self openCommand OpenCommand doors doors self closeCommand CloseCommand doors doors func close gt String return closeCommand execute func open gt String return openCommand execute let podBayDoors Pod Bay Doors let doorModule HAL9000DoorsOperations doors podBayDoors doorModule open doorModule close DzherelaDesign Patterns Elements of Reusable Object Oriented Software 9 Listopada 2012 u Wayback Machine Posilannya Arhiv originalu za 30 Sichnya 2016 Procitovano 10 Bereznya 2016 LiteraturaAlan Shallouej Dzhejms R Trott Shablony proektirovaniya Novyj podhod k obektno orientirovannomu analizu i proektirovaniyu Design Patterns Explained A New Perspective on Object Oriented Design M Vilyams 2002 288 s ISBN 0 201 71594 5