вторник, 30 июня 2026 г.

CQS, Command-Query Separation

CQS, Command-Query Separation

https://giga.chat/link/gcswPZKUTD

Английское наименование: Command-Query Separation (CQS).

Русская трактовка: Это принцип объектно-ориентированного программирования, который гласит: метод должен быть либо командой, которая выполняет действие (и изменяет состояние системы), либо запросом, который возвращает данные, но не тем и другим одновременно.

Этот принцип был предложен Бертраном Мейером, создателем языка Eiffel. Он помогает сделать код более предсказуемым, простым для понимания и менее подверженным ошибкам.

Детальное объяснение

Принцип разделяет все методы в системе на две непересекающиеся категории.

  1. Команды (Commands)

    • Назначение: изменяют состояние объекта или системы. Они выполняют какое-либо действие.
    • Возвращаемое значение: не возвращают данные (кроме, возможно, кода операции или статуса ошибки). В строгой трактовке CQS они возвращают void.
    • Пример: метод SaveToDatabase(), DeleteUser(int id), SetName(string name). Вызов такого метода имеет побочные эффекты: он что-то меняет в базе данных, в памяти или во внешнем мире.
  2. Запросы (Queries)

    • Назначение: возвращают данные о текущем состоянии объекта или системы. Они не должны ничего менять.
    • Возвращаемое значение: всегда возвращают результат (данные).
    • Пример: метод GetUserById(int id), GetAllOrders(), IsUserLoggedIn(). Вызов одного и того же запроса несколько раз подряд с одинаковыми аргументами не должен приводить к разным результатам или каким-либо побочным эффектам.

Почему это важно?

  • Предсказуемость и читаемость. Глядя на вызов метода, разработчик сразу понимает, что произойдёт. Если это запрос (Get..., Is..., Has...), он знает, что состояние системы не изменится. Если это команда (Save..., Update..., Delete...), он ожидает побочных эффектов.
  • Безопасность и кэширование. Запросы можно безопасно вызывать многократно, их результаты можно кэшировать для повышения производительности. С командами так поступать нельзя, так как повторный вызов может привести к дублированию операций (например, двойному списанию денег).
  • Упрощение тестирования. Методы, которые не имеют побочных эффектов (чистые функции/запросы), гораздо проще тестировать. Их результат зависит только от входных данных, а не от скрытого состояния системы.

Пример нарушения принципа (антипаттерн)

Рассмотрим метод, который нарушает CQS:

csharp
// Нарушает CQS: и возвращает данные, и изменяет состояние
public int Pop()
{
    // Изменяет состояние стека, удаляя верхний элемент
    return _stack.Pop(); 
}

В этом примере метод Pop является командой (он изменяет стек), но при этом он возвращает значение. Это может привести к ошибкам, если разработчик вызовет метод ради побочного эффекта, не ожидая возвращаемого значения, или наоборот.

Строгое следование CQS разделило бы эту операцию на два метода:

  • T Peek() — запрос, который возвращает верхний элемент, не изменяя стек.
  • void Pop() — команда, которая просто удаляет верхний элемент.

Практическое применение в вашей архитектуре

В контексте вашей системы на основе поведений (Behaviors) этот принцип можно применить к обработке событий:

  • Запрос: поведение может запрашивать данные из состояния ячейки (FractalCellState), чтобы принять решение о дальнейшей обработке. Например, метод CanHandleAsync(event) по своей сути является запросом — он проверяет состояние и возвращает true или false, ничего не меняя.
  • Команда: метод HandleAsync(event) является командой. Он получает событие и на его основе изменяет состояние ячейки или публикует новые события в шине.

Такое разделение делает логику каждого поведения более чистой и предсказуемой. Вы точно знаете, что методы проверки (CanHandle) безопасны для вызова в любом месте, а методы обработки (Handle) несут в себе логику изменения.

Комментариев нет:

Отправить комментарий