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

Приведи пример использования паттерна команда

2.3 Middle🔥 171 комментариев
#Docker, Kubernetes и DevOps#JVM и управление памятью#ORM и Hibernate

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

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

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

Паттерн Command: Практические примеры

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

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

Комманда — это объект, который отделяет отправителя от получателя и позволяет:

  • Ставить операции в очередь
  • Отменять (undo) операции
  • Откладывать выполнение
  • Логировать все операции

Пример 1: Система команд с Undo/Redo

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

// Получатель (объект, над которым выполняются действия)
public class TextEditor {
    private StringBuilder text = new StringBuilder();
    
    public void insert(String s) {
        text.append(s);
    }
    
    public void delete(int count) {
        text.delete(text.length() - count, text.length());
    }
    
    public String getText() {
        return text.toString();
    }
}

// Конкретная команда: вставка текста
public class InsertCommand implements Command {
    private TextEditor editor;
    private String text;
    
    public InsertCommand(TextEditor editor, String text) {
        this.editor = editor;
        this.text = text;
    }
    
    @Override
    public void execute() {
        editor.insert(text);
    }
    
    @Override
    public void undo() {
        editor.delete(text.length());
    }
}

// Конкретная команда: удаление текста
public class DeleteCommand implements Command {
    private TextEditor editor;
    private String deletedText;
    private int position;
    
    public DeleteCommand(TextEditor editor, int count) {
        this.editor = editor;
        this.deletedText = editor.getText().substring(
            Math.max(0, editor.getText().length() - count)
        );
    }
    
    @Override
    public void execute() {
        editor.delete(deletedText.length());
    }
    
    @Override
    public void undo() {
        editor.insert(deletedText);
    }
}

// Инициатор (отправитель команды)
public class TextEditorInvoker {
    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);
        }
    }
}

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

Пример 2: Очередь команд (Task Scheduler)

// Команда для выполнения задачи
public class PrintCommand implements Command {
    private String message;
    
    public PrintCommand(String message) {
        this.message = message;
    }
    
    @Override
    public void execute() {
        System.out.println("[" + Thread.currentThread().getName() + "] " + message);
    }
    
    @Override
    public void undo() {
        System.out.println("Undo: " + message);
    }
}

// Планировщик команд
public class CommandScheduler {
    private Queue<Command> taskQueue = new LinkedList<>();
    private ExecutorService executor = Executors.newFixedThreadPool(2);
    
    public void scheduleCommand(Command command) {
        taskQueue.add(command);
    }
    
    public void executeTasks() {
        while (!taskQueue.isEmpty()) {
            Command command = taskQueue.poll();
            executor.submit(command::execute);
        }
    }
    
    public void shutdown() {
        executor.shutdown();
    }
}

// Использование
CommandScheduler scheduler = new CommandScheduler();
scheduler.scheduleCommand(new PrintCommand("Task 1"));
scheduler.scheduleCommand(new PrintCommand("Task 2"));
scheduler.scheduleCommand(new PrintCommand("Task 3"));
scheduler.executeTasks();

Пример 3: Система платежей

// Получатель (банк)
public class BankAccount {
    private double balance = 0;
    
    public void deposit(double amount) {
        balance += amount;
        System.out.println("Deposited: " + amount + ", Balance: " + balance);
    }
    
    public void withdraw(double amount) throws Exception {
        if (balance >= amount) {
            balance -= amount;
            System.out.println("Withdrew: " + amount + ", Balance: " + balance);
        } else {
            throw new Exception("Insufficient funds");
        }
    }
    
    public double getBalance() {
        return balance;
    }
}

// Команда пополнения
public class DepositCommand implements Command {
    private BankAccount account;
    private double amount;
    
    public DepositCommand(BankAccount account, double amount) {
        this.account = account;
        this.amount = amount;
    }
    
    @Override
    public void execute() {
        account.deposit(amount);
    }
    
    @Override
    public void undo() {
        try {
            account.withdraw(amount);
        } catch (Exception e) {
            System.out.println("Cannot undo deposit");
        }
    }
}

// Команда снятия
public class WithdrawCommand implements Command {
    private BankAccount account;
    private double amount;
    
    public WithdrawCommand(BankAccount account, double amount) {
        this.account = account;
        this.amount = amount;
    }
    
    @Override
    public void execute() {
        try {
            account.withdraw(amount);
        } catch (Exception e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
    
    @Override
    public void undo() {
        account.deposit(amount);
    }
}

// Трансакционный логгер
public class TransactionLog {
    private List<Command> transactions = new ArrayList<>();
    
    public void log(Command command) {
        transactions.add(command);
    }
    
    public void printHistory() {
        System.out.println("Transaction History:");
        for (int i = 0; i < transactions.size(); i++) {
            System.out.println((i + 1) + ". " + transactions.get(i).getClass().getSimpleName());
        }
    }
}

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

  1. Разделение ответственности: отправитель и получатель полностью независимы
  2. Undo/Redo: легко реализовать отмену операций
  3. Отложенное выполнение: можно ставить операции в очередь
  4. Логирование: все операции можно записать для аудита
  5. Композиция команд: можно создавать макро-команды

Реальные примеры в Java

  • Spring: @Transactional аннотация использует Command паттерн
  • JavaFX: javafx.scene.control.Button использует Command для обработки кликов
  • Java Concurrency: Runnable и Callable — это те же Command
  • Transaction Management: системы платежей часто используют этот паттерн

Выводы

Паттерн Command — это мощный инструмент для:

  • Реализации Undo/Redo функционала
  • Создания очередей задач
  • Логирования и аудита операций
  • Отделения отправителя от получателя

Он особенно полезен в приложениях, где нужна история операций и возможность их отмены.