Command (Команда)

Паттерн Command (Команда) превращает запросы в объекты, позволяя откладывать выполнение операций, ставить их в очередь, отменять или повторять. Этот паттерн инкапсулирует действие и его параметры в отдельный объект, что делает систему более гибкой и расширяемой.

Когда применять Command

  • Когда нужно параметризовать объекты действиями (например, кнопки в UI).
  • Когда необходимо поддерживать операции «Отмена» и «Повтор».
  • Когда команды нужно логировать, ставить в очередь или выполнять по расписанию.
  • Когда необходимо отделить отправителя команды от её получателя.

PlantUML-диаграмма

UML диаграмма Command

// Интерфейс команды
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-приложениях, системах отмены действий, макросах и многопоточном программировании.