Паттерн Command (Команда) превращает запросы в объекты, позволяя откладывать выполнение операций, ставить их в очередь, отменять или повторять. Этот паттерн инкапсулирует действие и его параметры в отдельный объект, что делает систему более гибкой и расширяемой.
Когда применять Command
- Когда нужно параметризовать объекты действиями (например, кнопки в UI).
- Когда необходимо поддерживать операции «Отмена» и «Повтор».
- Когда команды нужно логировать, ставить в очередь или выполнять по расписанию.
- Когда необходимо отделить отправителя команды от её получателя.
PlantUML-диаграмма
// Интерфейс команды
public interface ICommand
{
void Execute();
void Undo();
}
// Получатель — реальный исполнитель действий
public class TextEditor
{
private string _text = "";
public void Write(string text)
{
_text += text;
Console.WriteLine($"Текст: {_text}");
}
public void Erase(int count)
{
if (_text.Length >= count)
{
_text = _text.Substring(0, _text.Length - count);
Console.WriteLine($"Удалено {count} символов. Текст: {_text}");
}
}
}
// Конкретная команда — добавление текста
public class WriteCommand : ICommand
{
private TextEditor _editor;
private string _text;
public WriteCommand(TextEditor editor, string text)
{
_editor = editor;
_text = text;
}
public void Execute()
{
_editor.Write(_text);
}
public void Undo()
{
_editor.Erase(_text.Length);
}
}
// Инициатор — отправляет команды
public class CommandInvoker
{
private Stack<ICommand> _history = new Stack<ICommand>();
public void ExecuteCommand(ICommand command)
{
command.Execute();
_history.Push(command);
}
public void Undo()
{
if (_history.Count > 0)
{
var command = _history.Pop();
command.Undo();
}
}
}
// Клиентский код
class Program
{
static void Main()
{
var editor = new TextEditor();
var invoker = new CommandInvoker();
invoker.ExecuteCommand(new WriteCommand(editor, "Привет, "));
invoker.ExecuteCommand(new WriteCommand(editor, "мир!"));
invoker.Undo();
invoker.Undo();
}
}
type Command interface {
Execute()
Undo()
}
// Получатель
type TextEditor struct {
text string
}
func (e *TextEditor) Write(text string) {
e.text += text
fmt.Printf("Текст: %s\n", e.text)
}
func (e *TextEditor) Erase(count int) {
runes := []rune(e.text)
if len(runes) >= count {
e.text = string(runes[:len(runes)-count])
fmt.Printf("Удалено %d символов. Текст: %s\n", count, e.text)
}
}
type WriteCommand struct {
editor *TextEditor
text string
}
func (c WriteCommand) Execute() { c.editor.Write(c.text) }
func (c WriteCommand) Undo() { c.editor.Erase(len([]rune(c.text))) }
// Инициатор
type CommandInvoker struct {
history []Command
}
func (i *CommandInvoker) ExecuteCommand(c Command) {
c.Execute()
i.history = append(i.history, c)
}
func (i *CommandInvoker) Undo() {
if len(i.history) == 0 {
return
}
last := i.history[len(i.history)-1]
i.history = i.history[:len(i.history)-1]
last.Undo()
}
func main() {
editor := &TextEditor{}
invoker := &CommandInvoker{}
invoker.ExecuteCommand(WriteCommand{editor: editor, text: "Привет, "})
invoker.ExecuteCommand(WriteCommand{editor: editor, text: "мир!"})
invoker.Undo()
invoker.Undo()
}
from typing import Protocol
class Command(Protocol):
def execute(self) -> None: ...
def undo(self) -> None: ...
class TextEditor:
def __init__(self) -> None:
self._text = ""
def write(self, text: str) -> None:
self._text += text
print(f"Текст: {self._text}")
def erase(self, count: int) -> None:
if len(self._text) >= count:
self._text = self._text[:-count]
print(f"Удалено {count} символов. Текст: {self._text}")
class WriteCommand:
def __init__(self, editor: TextEditor, text: str) -> None:
self._editor = editor
self._text = text
def execute(self) -> None:
self._editor.write(self._text)
def undo(self) -> None:
self._editor.erase(len(self._text))
class CommandInvoker:
def __init__(self) -> None:
self._history: list[Command] = []
def execute_command(self, command: Command) -> None:
command.execute()
self._history.append(command)
def undo(self) -> None:
if self._history:
self._history.pop().undo()
editor = TextEditor()
invoker = CommandInvoker()
invoker.execute_command(WriteCommand(editor, "Привет, "))
invoker.execute_command(WriteCommand(editor, "мир!"))
invoker.undo()
invoker.undo()
interface Command {
execute(): void;
undo(): void;
}
class TextEditor {
private text = "";
write(text: string): void {
this.text += text;
console.log(`Текст: ${this.text}`);
}
erase(count: number): void {
if (this.text.length >= count) {
this.text = this.text.slice(0, this.text.length - count);
console.log(`Удалено ${count} символов. Текст: ${this.text}`);
}
}
}
class WriteCommand implements Command {
constructor(
private readonly editor: TextEditor,
private readonly text: string,
) {}
execute(): void {
this.editor.write(this.text);
}
undo(): void {
this.editor.erase(this.text.length);
}
}
class CommandInvoker {
private readonly history: Command[] = [];
executeCommand(command: Command): void {
command.execute();
this.history.push(command);
}
undo(): void {
this.history.pop()?.undo();
}
}
const editor = new TextEditor();
const invoker = new CommandInvoker();
invoker.executeCommand(new WriteCommand(editor, "Привет, "));
invoker.executeCommand(new WriteCommand(editor, "мир!"));
invoker.undo();
invoker.undo();
import java.util.ArrayDeque;
import java.util.Deque;
interface Command {
void execute();
void undo();
}
class TextEditor {
private String text = "";
void write(String text) {
this.text += text;
System.out.println("Текст: " + this.text);
}
void erase(int count) {
if (text.length() >= count) {
text = text.substring(0, text.length() - count);
System.out.println("Удалено " + count + " символов. Текст: " + text);
}
}
}
class WriteCommand implements Command {
private final TextEditor editor;
private final String text;
WriteCommand(TextEditor editor, String text) {
this.editor = editor;
this.text = text;
}
public void execute() {
editor.write(text);
}
public void undo() {
editor.erase(text.length());
}
}
class CommandInvoker {
private final Deque<Command> history = new ArrayDeque<>();
void executeCommand(Command command) {
command.execute();
history.push(command);
}
void undo() {
if (!history.isEmpty()) {
history.pop().undo();
}
}
}
public class Program {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
CommandInvoker invoker = new CommandInvoker();
invoker.executeCommand(new WriteCommand(editor, "Привет, "));
invoker.executeCommand(new WriteCommand(editor, "мир!"));
invoker.undo();
invoker.undo();
}
}
interface Command {
fun execute()
fun undo()
}
class TextEditor {
private var text = ""
fun write(part: String) {
text += part
println("Текст: $text")
}
fun erase(count: Int) {
if (text.length >= count) {
text = text.dropLast(count)
println("Удалено $count символов. Текст: $text")
}
}
}
class WriteCommand(private val editor: TextEditor, private val text: String) : Command {
override fun execute() = editor.write(text)
override fun undo() = editor.erase(text.length)
}
class CommandInvoker {
private val history = ArrayDeque<Command>()
fun executeCommand(command: Command) {
command.execute()
history.addLast(command)
}
fun undo() {
history.removeLastOrNull()?.undo()
}
}
fun main() {
val editor = TextEditor()
val invoker = CommandInvoker()
invoker.executeCommand(WriteCommand(editor, "Привет, "))
invoker.executeCommand(WriteCommand(editor, "мир!"))
invoker.undo()
invoker.undo()
}
Плюсы и минусы
| Плюсы | Минусы |
|---|---|
| Отделяет отправителя команды от её исполнителя | Увеличивает количество классов в проекте |
| Позволяет реализовать отмену, повтор и историю операций | Может усложнить простые запросы |
| Упрощает реализацию макрокоманд и очередей задач | Требует хранения контекста для отката действий |
Заключение
Command — мощный поведенческий паттерн, позволяющий инкапсулировать действия и параметры в виде объектов. Он широко используется в GUI-приложениях, системах отмены действий, макросах и многопоточном программировании.