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

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

Разработчику Архитектору
Теория данных (раздел 3)

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

Play ITЗагрузка интерактивного демо…


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

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

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

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

Play ITЗагрузка интерактивного демо…


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

Код ITЗагрузка примера кода…

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.


См. также