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

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

1.0 Junior🔥 142 комментариев
#Другое

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

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

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

Пример реализации отмены действия (Command Pattern + Undo/Redo)

В C# для реализации отмены действия (undo) и повторения (redo) часто используется паттерн Команда (Command Pattern). Это классический подход, который позволяет инкапсулировать действия как объекты, что делает возможным их отмену, повторение и историю.

Основные компоненты системы

  1. ICommand – интерфейс команды с методами Execute и Undo.
  2. ConcreteCommand – конкретные команды, реализующие бизнес-логику.
  3. CommandHistory – история команд для поддержки undo/redo.
  4. Invoker – объект, который выполняет команды и управляет историей.

Реализация

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

// 2. Пример конкретной команды - добавление текста
public class AddTextCommand : ICommand
{
    private readonly TextEditor _editor;
    private readonly string _addedText;
    private int _position;

    public AddTextCommand(TextEditor editor, string text, int position)
    {
        _editor = editor;
        _addedText = text;
        _position = position;
    }

    public void Execute()
    {
        _editor.InsertText(_addedText, _position);
    }

    public void Undo()
    {
        // Для undo мы удаляем добавленный текст
        _editor.DeleteText(_position, _addedText.Length);
    }
}

// 3. Класс TextEditor (Receiver)
public class TextEditor
{
    private StringBuilder _content = new StringBuilder();

    public string Content => _content.ToString();

    public void InsertText(string text, int position)
    {
        _content.Insert(position, text);
        Console.WriteLine($"Added '{text}' at position {position}. Content: {Content}");
    }

    public void DeleteText(int position, int length)
    {
        _content.Remove(position, length);
        Console.WriteLine($"Deleted {length} chars from {position}. Content: {Content}");
    }
}

// 4. История команд
public class CommandHistory
{
    private Stack<ICommand> _undoStack = new Stack<ICommand>();
    private Stack<ICommand> _redoStack = new Stack<ICommand>();

    public void ExecuteCommand(ICommand command)
    {
        command.Execute();
        _undoStack.Push(command);
        _redoStack.Clear(); // После нового действия redo история очищается
    }

    public void Undo()
    {
        if (_undoStack.Count > 0)
        {
            var command = _undoStack.Pop();
500-слов
command.Undo();
            _redoStack.Push(command);
        }
    }

    public void Redo()
    {
        if (_redoStack.Count > 0)
        {
            var command = _redoStack.Pop();
            command.Execute();
            _undoStack.Push(command);
        }
    }

    public bool CanUndo => _undoStack.Count > 0;
    public bool CanRedo => _redoStack.Count > 0;
}

// 5. Invoker (клиентский код)
public class TextEditorApp
{
    private TextEditor _editor = new TextEditor();
    private CommandHistory _history = new CommandHistory();

    public void AddText(string text, int position)
    {
        var command = new AddTextCommand(_editor, text, position);
        _history.ExecuteCommand(command);
    }

    public void Undo()
    {
        if (_history.CanUndo)
        {
            _history.Undo();
        }
    }

    public void Redo()
    {
        if (_history.CanRedo)
        {
            _history.Redo();
        }
    }

    public void DisplayContent()
    {
        Console.WriteLine($"Current content: {_editor.Content}");
    }
}

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

// Тестирование системы
var app = new TextEditorApp();

app.AddText("Hello", 0);      // Добавляем "Hello"
app.AddText(" World", 5);     // Добавляем " World" после Hello
app.DisplayContent();         // Вывод: Hello World

app.Undo();                   // Отменяем последнее действие (удаляем " World")
app.DisplayContent();         // Вывод: Hello

app.Undo();                   // Отменяем первое действие (удаляем "Hello")
app.DisplayContent();         // Вывод: (пустая строка)

app.Redo();                   // Повторяем первое действие (добавляем "Hello")
app.DisplayContent();         // Вывод: Hello

app.Redo();                   // Повторяем второе действие (добавляем " World")
app.DisplayContent();         // Вывод: Hello World

Ключевые моменты реализации

  • Инкапсуляция действий: Каждая команда содержит всю информацию для выполнения и отмены (текст, позиция).
  • Две стека (Stack):
    • Стек undo содержит выполненные команды.
    • Стек redo содержит отмененные команды.
  • Очистка redo: При выполнении новой команды после undo, стек redo очищается (стандартное поведение многих редакторов).
  • Отделение логики: Команды не зависят напрямую от Invoker, что позволяет легко добавлять новые команды.

Расширение системы

  1. Комплексные команды: Можно создать MacroCommand, который объединяет несколько команд:
public class MacroCommand : ICommand
{
    private List<ICommand> _commands = new List<ICommand>();

    public void AddCommand(ICommand command) => _commands.Add(command);

    public void Execute()
    {
        foreach (var cmd in _commands) cmd.Execute();
    }

    public void Undo()
    {
        // Undo в обратном порядке
        for (int i = _commands.Count - 1; i >= 0; i--)
            _commands[i].Undo();
    }
}
  1. Сохранение состояния: Для сложных объектов можно использовать Memento Pattern вместе с Command Pattern для сохранения состояния перед изменением.

Преимущества данного подхода

  • Поддержка истории: Легко реализовать историю действий.
  • Тестируемость: Команды легко тестировать изолированно.
  • Расширяемость: Новые действия добавляются через новые классы команд.
  • Отмена транзакций: Можно реализовать отмену группы действий как транзакции.

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