Комментарии (2)
🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Пример реализации отмены действия (Command Pattern + Undo/Redo)
В C# для реализации отмены действия (undo) и повторения (redo) часто используется паттерн Команда (Command Pattern). Это классический подход, который позволяет инкапсулировать действия как объекты, что делает возможным их отмену, повторение и историю.
Основные компоненты системы
- ICommand – интерфейс команды с методами Execute и Undo.
- ConcreteCommand – конкретные команды, реализующие бизнес-логику.
- CommandHistory – история команд для поддержки undo/redo.
- 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, что позволяет легко добавлять новые команды.
Расширение системы
- Комплексные команды: Можно создать 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();
}
}
- Сохранение состояния: Для сложных объектов можно использовать Memento Pattern вместе с Command Pattern для сохранения состояния перед изменением.
Преимущества данного подхода
- Поддержка истории: Легко реализовать историю действий.
- Тестируемость: Команды легко тестировать изолированно.
- Расширяемость: Новые действия добавляются через новые классы команд.
- Отмена транзакций: Можно реализовать отмену группы действий как транзакции.
Этот пример демонстрирует классический и надежный подход к реализации отмены действий, который используется в многих приложениях (текстовые редакторы, графические программы, системы управления).