Перейти к основному содержимому

Паттерн "Команда" в C# — объекты действий, а не методы

Разработчику Архитектору

Краткий обзор паттерна есть в поведенческих паттернах. Здесь — практика на C#: когда ICommand действительно полезен, а когда хватает делегата или обычного сервисного метода.

Загрузка редактора схем…

Задача паттерна

Команда превращает действие в объект. Такой объект можно:

  • передавать между слоями;
  • класть в очередь;
  • логировать;
  • повторять и отменять.

Клиент вызывает команду через общий интерфейс и не знает детали выполнения.

Загрузка ArchiStyler…

Классический вариант

public interface ICommand
{
void Execute();
void Undo();
}

public class SaveDocumentCommand : ICommand
{
private readonly DocumentService _receiver;
private readonly string _path;

public SaveDocumentCommand(DocumentService receiver, string path)
{
_receiver = receiver;
_path = path;
}

public void Execute() => _receiver.Save(_path);
public void Undo() => _receiver.RestorePreviousVersion(_path);
}

ToolbarButton (invoker) хранит ICommand и вызывает Execute(). Такая форма удобна в UI, редакторах и workflow-системах.


Что даёт C# поверх канона

Для простых сценариев можно хранить команды как делегаты:

var actions = new Dictionary<string, Action>
{
["refresh"] = () => cache.Reload(),
["clear"] = () => cache.Clear()
};

actions["refresh"]();

Минимум кода, но нет явной модели Undo, метаданных команды и сериализации.

Если нужен контекст действия, версия, user id, idempotency key, объект команды оказывается удобнее:

public record CreateOrderCommand(Guid UserId, IReadOnlyList<OrderItemDto> Items);

Command и MediatR в .NET

В современных ASP.NET Core проектах паттерн Command часто проявляется через MediatR:

  • CreateOrderCommand : IRequest<Guid> — объект намерения;
  • CreateOrderCommandHandler — исполнитель;
  • ISender.Send(command) — invoker.

Это вариант паттерна, адаптированный под DI и pipeline-поведения (валидация, логирование, трейсинг). Детально — в MediatR и pipeline.


Когда Command уместен

СитуацияПочему помогает Command
Undo/redo в UIКоманда хранит данные для отката
Очереди и отложенное исполнениеОбъект можно сериализовать и выполнить позже
Аудит действийОдин объект = одна бизнес-операция с метаданными
CQRS в Application-слоеЯвное разделение сценариев записи и чтения

Когда это лишний слой

Если операция:

  • короткая;
  • не требует очереди, истории и отката;
  • вызывается в одном месте;

обычный сервисный метод обычно проще:

await orderService.CreateAsync(dto, ct);

Иногда ICommand + Handler + Invoker только увеличивают число файлов без новой ценности.


Чек-лист

ВопросРекомендация
Нужно отменять и повторять действияCommand-объект
Нужны очередь, ретраи, журналCommand-объект
Просто вызвать одну операциюСервисный метод или Action
Есть Application-слой с MediatRКоманды как IRequest
Практика

В C# Command хорошо работает как контракт бизнес-действий и транспорт операции через слои. Для локальных коротких вызовов проще и дешевле остаются обычные методы.


Итог

Паттерн Command остаётся актуальным, когда действие нужно трактовать как отдельную сущность системы. В .NET это часто реализуется через MediatR, очереди и pipeline. Для простых вызовов разумнее оставить плоский сервисный API.


См. также

См. также

Другие статьи этого же раздела в боковом меню (как на странице "О разделе").