Приведи пример реализации отмены действия (паттерн Command).

«Приведи пример реализации отмены действия (паттерн Command).» — вопрос из категории Архитектура, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Паттерн 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) или отложенный запуск команд.