Ответ
Паттерн Command инкапсулирует запрос как объект, что позволяет параметризовать клиентов с различными запросами, ставить запросы в очередь, логировать их, а также поддерживать отмену операций (Undo).
Реализация на C# с поддержкой отмены:
// 1. Абстрактная команда
public interface ICommand
{
void Execute();
void Undo();
}
// 2. Получатель команды — объект, над которым выполняется операция.
public class TextEditor
{
public string Text { get; set; } = string.Empty;
}
// 3. Конкретная команда для изменения текста
public class ChangeTextCommand : ICommand
{
private readonly TextEditor _editor;
private string _previousText;
private readonly string _newText;
public ChangeTextCommand(TextEditor editor, string newText)
{
_editor = editor;
_newText = newText;
}
public void Execute()
{
// Перед выполнением сохраняем состояние для возможной отмены
_previousText = _editor.Text;
_editor.Text = _newText;
Console.WriteLine($"Execute: Text changed to '{_newText}'.");
}
public void Undo()
{
// Восстанавливаем предыдущее состояние
_editor.Text = _previousText;
Console.WriteLine($"Undo: Text reverted to '{_previousText}'.");
}
}
// 4. Инициатор команд (Invoker) — управляет выполнением и историей.
public class CommandManager
{
private readonly Stack<ICommand> _undoStack = new Stack<ICommand>();
public void ExecuteCommand(ICommand command)
{
command.Execute();
_undoStack.Push(command); // Добавляем команду в историю для отмены
}
public void Undo()
{
if (_undoStack.Count > 0)
{
ICommand lastCommand = _undoStack.Pop();
lastCommand.Undo();
}
else
{
Console.WriteLine("Nothing to undo.");
}
}
}
// Пример использования
class Program
{
static void Main()
{
var editor = new TextEditor();
var manager = new CommandManager();
// Выполняем команды
manager.ExecuteCommand(new ChangeTextCommand(editor, "Hello"));
manager.ExecuteCommand(new ChangeTextCommand(editor, "Hello, World!"));
manager.ExecuteCommand(new ChangeTextCommand(editor, "Hello, Universe!"));
Console.WriteLine($"nCurrent text: {editor.Text}"); // Hello, Universe!
// Отменяем действия
manager.Undo(); // Возврат к "Hello, World!"
Console.WriteLine($"After undo: {editor.Text}");
manager.Undo(); // Возврат к "Hello"
Console.WriteLine($"After second undo: {editor.Text}");
}
}
Ключевые преимущества подхода:
- Разделение ответственности: Код, инициирующий действие (
CommandManager), не зависит от кода, который его выполняет (ChangeTextCommand) и от получателя (TextEditor). - Реализация сложных операций: Команда может объединять несколько действий над разными объектами, а её метод
Undo()корректно откатывает их все. - Расширяемость: Легко добавить новые типы команд, логирование, повторное выполнение (Redo) или отложенный запуск команд.
Ответ 18+ 🔞
Слушай, вот этот паттерн Command — он вообще огонь, если нужно сделать так, чтобы твой код не превращался в спагетти из условных операторов. Представь себе: у тебя есть куча действий, и каждое нужно уметь выполнять, отменять, ставить в очередь или логировать. Если всё это пихать в один метод, получится пиздец, а не код.
Вот смотри, как это работает на практике, на примере текстового редактора. Суть в том, что мы каждое действие заворачиваем в отдельный объект-команду. Получается этакая консерва, которую можно открыть сейчас, а можно — потом, или вообще выкинуть, если передумал.
// 1. Это типа наш контракт для всех команд. Любая команда должна уметь две вещи: сделать и отменить.
public interface ICommand
{
void Execute();
void Undo();
}
// 2. А это — получатель, тот, над кем мы будем издеваться. В нашем случае — текстовый редактор.
public class TextEditor
{
public string Text { get; set; } = string.Empty;
}
// 3. А вот и конкретная пакостная команда, которая меняет текст.
public class ChangeTextCommand : ICommand
{
private readonly TextEditor _editor;
private string _previousText; // Вот это — ключевое! Сохраняем старое состояние, чтобы было что откатывать.
private readonly string _newText;
public ChangeTextCommand(TextEditor editor, string newText)
{
_editor = editor;
_newText = newText;
}
public void Execute()
{
// Перед тем как нагадить, запоминаем, как было чисто.
_previousText = _editor.Text;
_editor.Text = _newText;
Console.WriteLine($"Execute: Текст поменяли на '{_newText}'.");
}
public void Undo()
{
// А если нагадили, то убираем за собой. Возвращаем как было.
_editor.Text = _previousText;
Console.WriteLine($"Undo: Откатили текст обратно на '{_previousText}'.");
}
}
// 4. А это — наш главный распорядитель, командир. Он решает, когда какую команду запускать и хранит историю, чтобы было что отменять.
public class CommandManager
{
private readonly Stack<ICommand> _undoStack = new Stack<ICommand>(); // История команд — наш стек отмены.
public void ExecuteCommand(ICommand command)
{
command.Execute();
_undoStack.Push(command); // Запихнули команду в историю — теперь её можно отменить.
}
public void Undo()
{
if (_undoStack.Count > 0)
{
ICommand lastCommand = _undoStack.Pop(); // Достаём последнюю команду из стека.
lastCommand.Undo(); // И говорим ей: "Отмена, сука!"
}
else
{
Console.WriteLine("Отменять нечего, иди отсюда.");
}
}
}
// Ну и смотрим, как это всё работает вживую.
class Program
{
static void Main()
{
var editor = new TextEditor();
var manager = new CommandManager();
// Начинаем творить историю.
manager.ExecuteCommand(new ChangeTextCommand(editor, "Привет"));
manager.ExecuteCommand(new ChangeTextCommand(editor, "Привет, Мир!"));
manager.ExecuteCommand(new ChangeTextCommand(editor, "Привет, Вселенная!"));
Console.WriteLine($"nТекущий текст: {editor.Text}"); // Привет, Вселенная!
// Ой, передумал! Откатываем.
manager.Undo(); // Возвращаемся к "Привет, Мир!"
Console.WriteLine($"После отмены: {editor.Text}");
manager.Undo(); // Возвращаемся к "Привет"
Console.WriteLine($"После второй отмены: {editor.Text}");
}
}
Вот в чём, блядь, сила этого подхода:
- Всё по полочкам: Тот, кто команду даёт (
CommandManager), вообще не в курсе, что она там делает внутри. Он просто тыкает кнопкиExecuteиUndo. А реальная работа идёт в команде и редакторе. Разделение ответственности — красота! - Сложные операции — не проблема: Одна команда может внутри себя делать кучу действий над разными объектами. А метод
Undo()аккуратно всё это откатит, как будто так и было. Никаких костылей. - Расширяется проще пареной репы: Захотел добавить логирование — допиши в
CommandManager. Нужна очередь команд на выполнение — пожалуйста. Нужен повтор (Redo) — заведи второй стек. Архитектура к этому готова, потому что команды — это независимые кирпичики, а не монолитная хуйня.