Menu
Coddy logo textTech

Паттерн Команда

Часть раздела Объектно-ориентированное программирование путешествия по C# на Coddy — урок 56 из 70.

Паттерн Команда — это поведенческий паттерн, который превращает запрос в самостоятельный объект, содержащий всю информацию о запросе. Такая трансформация позволяет передавать запросы в качестве аргументов методов, ставить их в очередь, логировать, а также поддерживать отмену операций.

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

public interface ICommand
{
    void Execute();
}

public class Light  // Получатель
{
    public void TurnOn() => Console.WriteLine("Light is ON");
    public void TurnOff() => Console.WriteLine("Light is OFF");
}

public class TurnOnCommand : ICommand
{
    private Light _light;
    
    public TurnOnCommand(Light light) => _light = light;
    
    public void Execute() => _light.TurnOn();
}

public class TurnOffCommand : ICommand
{
    private Light _light;
    
    public TurnOffCommand(Light light) => _light = light;
    
    public void Execute() => _light.TurnOff();
}

Инициатор (invoker) сохраняет и выполняет команды, не зная, что именно они делают:

public class RemoteControl  // Инициатор (Invoker)
{
    private ICommand _command;
    
    public void SetCommand(ICommand command) => _command = command;
    
    public void PressButton() => _command.Execute();
}

// Использование
var light = new Light();
var remote = new RemoteControl();

remote.SetCommand(new TurnOnCommand(light));
remote.PressButton();  // Свет ВКЛЮЧЕН

remote.SetCommand(new TurnOffCommand(light));
remote.PressButton();  // Свет ВЫКЛЮЧЕН

Паттерн Команда особенно эффективен для реализации функциональности отмены, записи макросов или постановки операций в очередь для последующего выполнения. Каждая команда инкапсулирует всё необходимое для выполнения действия, делая вашу систему более гибкой и расширяемой.

challenge icon

Задание

Легко

Давайте создадим командную систему текстового редактора, используя паттерн «Команда» (Command). Вы создадите систему, в которой операции редактирования, такие как вставка и удаление текста, инкапсулированы в объекты команд, что позволяет редактору выполнять их через единый интерфейс.

Вы организуете свой код в трех файлах:

  • Command.cs: Определите интерфейс ICommand в пространстве имен TextEditor с единственным методом Execute(). Затем создайте два конкретных класса команд:
    • InsertCommand — принимает Document и строку text в своем конструкторе. При выполнении он добавляет текст в документ и выводит Inserted: {text}
    • DeleteCommand — принимает Document и int count в своем конструкторе. При выполнении он удаляет последние count символов из документа и выводит Deleted: {count} characters
  • Document.cs: Создайте класс Document в том же пространстве имен. Это ваш получатель (receiver) — объект, который непосредственно выполняет операции с текстом. Он должен иметь:
    • Приватное строковое поле для хранения содержимого (инициализируйте пустой строкой)
    • Метод Append(string text), который добавляет текст к содержимому
    • Метод RemoveLast(int count), который удаляет последние count символов (если count превышает длину содержимого, очистите все содержимое)
    • Метод GetContent(), который возвращает текущее содержимое
  • Program.cs: Создайте класс Editor, который выступает в роли инициатора (invoker). Он должен иметь метод SetCommand(ICommand command) и метод ExecuteCommand(), который запускает текущую команду. В основном коде продемонстрируйте работу паттерна «Команда», обработав серию операций редактирования.

Вы получите следующие входные данные:

  • Количество команд для выполнения
  • Для каждой команды: тип команды (insert или delete), за которым следует либо текст для вставки, либо количество символов для удаления

После выполнения всех команд выведите финальное содержимое документа на новой строке в формате Content: {content}.

Например, если входные данные следующие:

4
insert
Hello
insert
 World
delete
3
insert
!

Вывод должен быть таким:

Inserted: Hello
Inserted:  World
Deleted: 3 characters
Inserted: !
Content: Hello Wo!

Обратите внимание, что Editor (инициатор) ничего не знает о манипуляциях с текстом — он просто выполняет любую команду, которую ему дают. Команды инкапсулируют как действие, так и получателя, что позволяет легко добавлять новые операции, такие как ReplaceCommand, не меняя класс редактора!

Шпаргалка

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

Паттерн состоит из четырех ключевых компонентов:

  • Интерфейс команды (Command interface) — определяет метод Execute()
  • Конкретные команды (Concrete commands) — реализуют интерфейс команды и инкапсулируют конкретные действия
  • Получатель (Receiver) — объект, который выполняет фактическую работу
  • Инициатор (Invoker) — объект, который инициирует выполнение команд, не зная об их реализации

Интерфейс команды и конкретные команды

public interface ICommand
{
    void Execute();
}

public class TurnOnCommand : ICommand
{
    private Light _light;
    
    public TurnOnCommand(Light light) => _light = light;
    
    public void Execute() => _light.TurnOn();
}

public class TurnOffCommand : ICommand
{
    private Light _light;
    
    public TurnOffCommand(Light light) => _light = light;
    
    public void Execute() => _light.TurnOff();
}

Получатель

public class Light
{
    public void TurnOn() => Console.WriteLine("Light is ON");
    public void TurnOff() => Console.WriteLine("Light is OFF");
}

Инициатор

public class RemoteControl
{
    private ICommand _command;
    
    public void SetCommand(ICommand command) => _command = command;
    
    public void PressButton() => _command.Execute();
}

Использование

var light = new Light();
var remote = new RemoteControl();

remote.SetCommand(new TurnOnCommand(light));
remote.PressButton();  // Light is ON

remote.SetCommand(new TurnOffCommand(light));
remote.PressButton();  // Light is OFF

Паттерн Команда полезен для реализации функционала отмены, записи макросов или постановки операций в очередь для последующего выполнения. Каждая команда инкапсулирует всё необходимое для выполнения действия, что делает системы более гибкими и расширяемыми.

Попробуйте сами

using System;
using TextEditor;

// TODO: Создайте класс Editor (инициатор/invoker)
public class Editor
{
    // TODO: Добавьте приватное поле для хранения текущей команды
    private ICommand _command;

    // TODO: Реализуйте метод SetCommand
    public void SetCommand(ICommand command)
    {
        // TODO: Сохраните команду
    }

    // TODO: Реализуйте метод ExecuteCommand
    public void ExecuteCommand()
    {
        // TODO: Выполните текущую команду
    }
}

class Program
{
    public static void Main(string[] args)
    {
        // Считать количество команд
        int n = Convert.ToInt32(Console.ReadLine());

        // Создать документ (получатель/receiver)
        Document document = new Document();

        // Создать редактор (инициатор/invoker)
        Editor editor = new Editor();

        // TODO: Обработать каждую команду
        for (int i = 0; i < n; i++)
        {
            string commandType = Console.ReadLine();

            // TODO: На основе commandType создайте соответствующую команду
            // - Если "insert": прочитать текст и создать InsertCommand
            // - Если "delete": прочитать количество и создать DeleteCommand
            // Затем установите команду в редакторе и выполните её
        }

        // TODO: Выведите итоговое содержимое в формате "Content: {content}"
    }
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование