Что такое паттерн Команда (Command)?

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

Ответ

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

Суть паттерна: Он превращает операцию в самостоятельный объект, содержащий всю информацию, необходимую для её выполнения. Это отделяет объект, инициирующий операцию (Invoker), от объекта, который знает, как её выполнить (Receiver).

Ключевые компоненты:

  • ICommand: Интерфейс, объявляющий метод выполнения (например, Execute()). Часто включает метод Undo().
  • ConcreteCommand: Конкретная реализация команды. Она связывает действие с Получателем (Receiver), реализуя методы Execute/Undo.
  • Invoker: Инициатор команды. Он знает, когда команду нужно выполнить, но не знает, как она выполняется. Хранит ссылку на объект команды.
  • Receiver: Объект, который знает, как выполнить само действие. Команда делегирует ему основную работу.
  • Client: Создаёт объект конкретной команды и связывает его с получателем и инициатором.

Преимущества:

  • Отделение отправителя от получателя: Отправитель (Invoker) не зависит от конкретных классов получателей.
  • Реализация отмены/повтора (Undo/Redo): Легко реализуется, если команды сохраняют состояние, необходимое для отката.
  • Очереди и планирование операций: Команды как объекты можно собирать в очередь, выполнять по расписанию или передавать по сети.
  • Сбор сложных команд: Можно создавать макрокоманды (составные команды) из набора простых.

Пример на C# (Управление умным домом с поддержкой отмены):

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

// 2. Получатель (Receiver) — знает, как выполнять действия
public class Light
{
    private bool _isOn = false;
    private string _location;

    public Light(string location) => _location = location;

    public void TurnOn()
    {
        _isOn = true;
        Console.WriteLine($"Свет в {_location} ВКЛЮЧЕН.");
    }

    public void TurnOff()
    {
        _isOn = false;
        Console.WriteLine($"Свет в {_location} ВЫКЛЮЧЕН.");
    }
}

// 3. Конкретные команды
public class LightOnCommand : ICommand
{
    private Light _light;

    public LightOnCommand(Light light)
    {
        _light = light;
    }

    public void Execute()
    {
        _light.TurnOn();
    }

    public void Undo()
    {
        _light.TurnOff(); // Отмена действия «включить» — это «выключить»
    }
}

public class LightOffCommand : ICommand
{
    private Light _light;

    public LightOffCommand(Light light)
    {
        _light = light;
    }

    public void Execute()
    {
        _light.TurnOff();
    }

    public void Undo()
    {
        _light.TurnOn(); // Отмена действия «выключить» — это «включить»
    }
}

// 4. Инициатор (Invoker) — пульт с одной кнопкой и поддержкой отмены
public class SimpleRemoteControl
{
    private ICommand _slot;
    private ICommand _lastCommand; // Для реализации отмены

    public void SetCommand(ICommand command)
    {
        _slot = command;
    }

    public void ButtonWasPressed()
    {
        _slot?.Execute();
        _lastCommand = _slot; // Запоминаем последнюю выполненную команду
    }

    public void UndoButtonWasPressed()
    {
        _lastCommand?.Undo();
        _lastCommand = null;
    }
}

// 5. Клиентский код
class Program
{
    static void Main()
    {
        // Создаём получателя
        Light livingRoomLight = new Light("Гостиной");

        // Создаём команды, связывая их с получателем
        ICommand lightOn = new LightOnCommand(livingRoomLight);
        ICommand lightOff = new LightOffCommand(livingRoomLight);

        // Создаём инициатор (пульт)
        SimpleRemoteControl remote = new SimpleRemoteControl();

        // Настраиваем пульт на команду «Включить свет»
        remote.SetCommand(lightOn);
        remote.ButtonWasPressed(); // Вывод: Свет в Гостиной ВКЛЮЧЕН.

        // Настраиваем пульт на команду «Выключить свет»
        remote.SetCommand(lightOff);
        remote.ButtonWasPressed(); // Вывод: Свет в Гостиной ВЫКЛЮЧЕН.

        // Демонстрация отмены: снова включим свет и отменим действие
        remote.SetCommand(lightOn);
        remote.ButtonWasPressed(); // Вывод: Свет в Гостиной ВКЛЮЧЕН.
        remote.UndoButtonWasPressed(); // Вывод: Свет в Гостиной ВЫКЛЮЧЕН. (отмена)
    }
}