← Назад к вопросам

Для чего нужен паттерн Command?

2.0 Middle🔥 141 комментариев
#SOLID и паттерны проектирования

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI22 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

# Паттерн Command: назначение и применение

Паттерн Command — это поведенческий паттерн проектирования, который инкапсулирует запрос на выполнение определённого действия в отдельный объект. Он позволяет параметризовать клиентов с различными запросами, ставить запросы в очередь, регистрировать запросы и поддерживать отмену операций.

Основное назначение

Паттерн Command решает следующие задачи:

  1. Инкапсуляция операций — каждое действие становится объектом
  2. Отделение отправителя от получателя — кто отправляет команду, не должен знать, как она выполняется
  3. Возможность отмены/повтора операций — команды могут быть отменены и повторены
  4. Очередь команд — команды можно накапливать и выполнять позже
  5. Логирование и аудит операций — отслеживание всех выполняемых действий
  6. Возможность отложенного выполнения — выполнить действие не сразу, а в нужный момент

Структура паттерна

// 1. Интерфейс команды
public interface Command {
    void execute();
    void undo();
}

// 2. Получатель (объект, над которым выполняется действие)
public class TextEditor {
    private StringBuilder text = new StringBuilder();
    
    public void appendText(String str) {
        text.append(str);
    }
    
    public void deleteLastChar() {
        if (text.length() > 0) {
            text.deleteCharAt(text.length() - 1);
        }
    }
    
    public String getText() {
        return text.toString();
    }
}

// 3. Конкретные команды
public class AddTextCommand implements Command {
    private TextEditor editor;
    private String textToAdd;
    
    public AddTextCommand(TextEditor editor, String textToAdd) {
        this.editor = editor;
        this.textToAdd = textToAdd;
    }
    
    @Override
    public void execute() {
        editor.appendText(textToAdd);
    }
    
    @Override
    public void undo() {
        // Отмена: удаляем добавленные символы
        for (int i = 0; i < textToAdd.length(); i++) {
            editor.deleteLastChar();
        }
    }
}

public class DeleteCharCommand implements Command {
    private TextEditor editor;
    private String deletedChar;
    
    public DeleteCharCommand(TextEditor editor) {
        this.editor = editor;
    }
    
    @Override
    public void execute() {
        String text = editor.getText();
        if (text.length() > 0) {
            this.deletedChar = text.substring(text.length() - 1);
            editor.deleteLastChar();
        }
    }
    
    @Override
    public void undo() {
        // Отмена: восстанавливаем удалённый символ
        if (deletedChar != null) {
            editor.appendText(deletedChar);
        }
    }
}

// 4. Инициатор (Invoker) — отправляет команды
public class EditorApplication {
    private Deque<Command> history = new LinkedList<>();
    private Deque<Command> redoStack = new LinkedList<>();
    
    public void executeCommand(Command command) {
        command.execute();
        history.push(command);
        redoStack.clear(); // Очищаем redo после новой команды
    }
    
    public void undo() {
        if (!history.isEmpty()) {
            Command command = history.pop();
            command.undo();
            redoStack.push(command);
        }
    }
    
    public void redo() {
        if (!redoStack.isEmpty()) {
            Command command = redoStack.pop();
            command.execute();
            history.push(command);
        }
    }
}

// 5. Использование
public static void main(String[] args) {
    TextEditor editor = new TextEditor();
    EditorApplication app = new EditorApplication();
    
    // Выполняем команды
    app.executeCommand(new AddTextCommand(editor, "Hello"));
    System.out.println(editor.getText()); // Hello
    
    app.executeCommand(new AddTextCommand(editor, " World"));
    System.out.println(editor.getText()); // Hello World
    
    app.executeCommand(new DeleteCharCommand(editor));
    System.out.println(editor.getText()); // Hello Worl
    
    // Отмена
    app.undo();
    System.out.println(editor.getText()); // Hello World
    
    // Повтор
    app.redo();
    System.out.println(editor.getText()); // Hello Worl
}

Реальные примеры использования

1. Отмена/Повтор операций

// Как в Photoshop, Word, Google Docs
public interface Action {
    void execute();
    void undo();
    void redo();
}

public class DrawCircleAction implements Action {
    private Canvas canvas;
    private Circle circle;
    
    public DrawCircleAction(Canvas canvas, Circle circle) {
        this.canvas = canvas;
        this.circle = circle;
    }
    
    @Override
    public void execute() {
        canvas.drawShape(circle);
    }
    
    @Override
    public void undo() {
        canvas.removeShape(circle);
    }
    
    @Override
    public void redo() {
        canvas.drawShape(circle);
    }
}

2. Очередь задач и планировщик

public interface Task extends Command {
    void execute();
}

// Планировщик выполняет задачи
public class TaskScheduler {
    private Queue<Task> tasks = new LinkedList<>();
    
    public void scheduleTask(Task task) {
        tasks.offer(task);
    }
    
    public void executeTasks() {
        while (!tasks.isEmpty()) {
            Task task = tasks.poll();
            task.execute();
        }
    }
}

// Использование
TaskScheduler scheduler = new TaskScheduler();
scheduler.scheduleTask(() -> System.out.println("Email sent"));
scheduler.scheduleTask(() -> System.out.println("Report generated"));
scheduler.executeTasks();

3. Логирование и аудит

public interface AuditableCommand extends Command {
    String getDescription();
}

public class TransferMoneyCommand implements AuditableCommand {
    private Account from;
    private Account to;
    private BigDecimal amount;
    private LocalDateTime timestamp = LocalDateTime.now();
    
    public TransferMoneyCommand(Account from, Account to, BigDecimal amount) {
        this.from = from;
        this.to = to;
        this.amount = amount;
    }
    
    @Override
    public void execute() {
        from.debit(amount);
        to.credit(amount);
        logTransaction();
    }
    
    @Override
    public void undo() {
        to.debit(amount);
        from.credit(amount);
        logUndo();
    }
    
    @Override
    public String getDescription() {
        return String.format("Transfer %s from %s to %s at %s", 
            amount, from.getId(), to.getId(), timestamp);
    }
    
    private void logTransaction() {
        System.out.println("[AUDIT] " + getDescription());
    }
    
    private void logUndo() {
        System.out.println("[AUDIT] Undo: " + getDescription());
    }
}

4. Макросы и автоматизация

public class MacroRecorder {
    private List<Command> recordedCommands = new ArrayList<>();
    private boolean isRecording = false;
    
    public void startRecording() {
        isRecording = true;
        recordedCommands.clear();
    }
    
    public void stopRecording() {
        isRecording = false;
    }
    
    public void recordCommand(Command command) {
        if (isRecording) {
            recordedCommands.add(command);
        }
    }
    
    public void playback() {
        for (Command command : recordedCommands) {
            command.execute();
        }
    }
}

// Использование
MacroRecorder recorder = new MacroRecorder();
recorder.startRecording();
recorder.recordCommand(new OpenFileCommand("document.txt"));
recorder.recordCommand(new SelectAllCommand());
recorder.recordCommand(new CopyCommand());
recorder.stopRecording();

// Позже — выполнить все записанные действия
recorder.playback();

5. Удалённое выполнение команд

// Команды могут быть сериализованы и отправлены по сети
public interface RemoteCommand extends Command, Serializable {
    void execute();
}

public class RemoteInvoker {
    private RemoteServer server;
    
    public void executeRemotely(RemoteCommand command) {
        // Отправляем команду на сервер
        server.send(command);
    }
}

Преимущества паттерна Command

Разделение ответственности — отправитель не знает о получателе ✅ История операций — легко реализовать Ctrl+Z ✅ Очередь операций — можно накапливать и выполнять команды ✅ Расширяемость — легко добавлять новые команды ✅ Отложенное выполнение — выполнить позже, в фоне или на другом потоке ✅ Макросы и автоматизация — записать и повторить последовательность операций ✅ Логирование и откат — полная история операций

Недостатки

Усложнение кода — для простых операций паттерн может быть избыточным ❌ Увеличение памяти — каждая команда — это объект

Когда использовать Command

  • Нужна функция отмены/повтора
  • Требуется отложенное выполнение операций
  • Нужно ставить операции в очередь или расписание
  • Требуется логирование и аудит операций
  • Нужны макросы или записанные последовательности действий
  • Требуется передача операций по сети

В заключение: паттерн Command — это один из самых полезных паттернов для создания гибких, отмену-способных и логируемых систем. Он широко используется в графических редакторах, текстовых процессорах, системах управления транзакциями и везде, где нужна история операций.