Команда (шаблон проєктування) — Вікіпедія
Команда (англ. 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[1][ред. | ред. код]
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.]
Посилання[ред. | ред. код]
- ↑ Design Patterns implemented in Swift 2. Архів оригіналу за 30 Січня 2016. Процитовано 10 Березня 2016.
Література[ред. | ред. код]
Алан Шаллоуей, Джеймс Р. Тротт. Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М. : «Вильямс», 2002. — 288 с. — ISBN 0-201-71594-5.