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

Ответ

Команда (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(); // Вывод: Свет в Гостиной ВЫКЛЮЧЕН. (отмена)
    }
}

Ответ 18+ 🔞

А, паттерн Команда, ну это же классика, блядь! Прям как в старом анекдоте про пульт и тещу. Сейчас объясню на пальцах, только не ори, что сложно.

Представь, что ты царь и бог в своём умном доме. У тебя есть куча приборов — свет, чайник, телевизор. И есть пульт с кнопками. Так вот, паттерн Команда — это когда каждая кнопка на пульте это не просто «пиу-пиу, сделай что-то», а целый запечатанный приказ в конверте. Ты, как царь (клиент), создаёшь эти приказы: «Включить свет в гостиной», «Вскипятить чайник». Пульт (инициатор) даже не в курсе, что внутри конверта. Его дело — тыкнуть кнопку и отправить конверт адресату. А адресат (получатель) уже вскрывает конверт и делает, что там написано: лампочка зажигается, чайник булькает.

И вся магия в том, что ты можешь эти конверты-команды складывать в стопку, чтобы потом отменить (ой, бля, не туда нажал!) или вообще составить один жирный конверт «Сценарий „Вечерний дебош“», внутри которого «Включить свет, включить музыку, начать наливать».

Смотри, как это выглядит в коде, на примере этого самого пульта. Только не засыпай, я стараюсь.

// 1. Это как договор для всех команд. Любая команда должна уметь Выполниться и Отмениться.
public interface ICommand
{
    void Execute();
    void Undo();
}

// 2. А это получатель, тот самый прибор. Он реально знает, как надо жужжать и светиться.
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. Пульт управления, инициатор. Тупая железяка, которая только кнопки жмёт.
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(); // Ой, передумал! *Жмяк* — и свет пропал. Вообще красота!
    }
}

Вот и вся магия, ёпта. Главная фишка — развязал руки. Пульту похуй, что там за прибор, он просто тыкает в кнопку. А команда — это умный посредник, который знает КОМУ и ЧТО сказать. Хочешь записать все действия в лог — пожалуйста, команды же объекты. Хочешь сделать очередь задач — да легко, складывай их в список и выполняй по одной. Хочешь макрос «Прийти с работы» — создай команду, которая внутри себя выполняет кучу других команд. Удобно, модульно, и отменять нехуёво.

Прям как в жизни: начальник (клиент) даёт секретарше (пульту) приказ (команду) для отдела (получателя). Секретарше всё равно, что в приказе, её дело — передать. А отдел уже голову ломает, как это выполнить. А если начальник — мудак, он всегда может сказать «Отмена, я передумал».