Для чего нужен паттерн Command?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
# Паттерн Command: назначение и применение
Паттерн Command — это поведенческий паттерн проектирования, который инкапсулирует запрос на выполнение определённого действия в отдельный объект. Он позволяет параметризовать клиентов с различными запросами, ставить запросы в очередь, регистрировать запросы и поддерживать отмену операций.
Основное назначение
Паттерн Command решает следующие задачи:
- Инкапсуляция операций — каждое действие становится объектом
- Отделение отправителя от получателя — кто отправляет команду, не должен знать, как она выполняется
- Возможность отмены/повтора операций — команды могут быть отменены и повторены
- Очередь команд — команды можно накапливать и выполнять позже
- Логирование и аудит операций — отслеживание всех выполняемых действий
- Возможность отложенного выполнения — выполнить действие не сразу, а в нужный момент
Структура паттерна
// 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 — это один из самых полезных паттернов для создания гибких, отмену-способных и логируемых систем. Он широко используется в графических редакторах, текстовых процессорах, системах управления транзакциями и везде, где нужна история операций.