Чистая логика

Принципиальное отличие ППЛ от большинства современных архитектурных подходов состоит не в том, что она предлагает ещё один способ отделить «логику» от «среды», а в том, что она ставит под сомнение саму необходимость такого стерильного разделения. В классическом понимании чистой архитектуры обычно предполагается, что предметная логика должна находиться как можно дальше от деталей исполнения, а связи с конкретной средой должны проходить через абстракции. В ППЛ отправная точка другая: объект предметного мира сам по себе уже является частью среды, в которой существует модель, и поэтому пытаться заменить его на чистую абстракцию только ради сохранения чистоты — значит разрушить естественную структуру самой предметной области.

Рассмотрим общество. В нём существуют люди, компании, здания, транспорт, рынки и множество других сущностей. Если написать SocietyLogic как «чистую» логику в классическом смысле, очень легко прийти к идее, что она не должна иметь дело с Person или Company как с реальными объектами. Предполагается, что у каждой сущности есть отделённая PersonLogic или CompanyLogic, а SocietyLogic должна работать только с ними. Тогда на схеме появляется логически привлекательная конструкция: SocietyLogic → CompanyLogic → какие-то абстрактные данные. Но это начинает расходиться с содержанием модели. Общество ведь управляет не «чистой логикой компании». Оно управляет компанией как реально существующей сущностью общества. Компания имеет капитал, производство, склад, владельцев, работников, положение, режим работы и множество других свойств, часть которых вообще относится не к вычислению как таковому, а к её существованию в конкретном мире.

ППЛ поэтому допускает прямое взаимодействие SocietyLogic с Company как с Flow-объектом. Это не считается нарушением разделения. Она получает саму сущность общества — компанию — и работает с ней как с участником мира. Внутри самой Company уже находится CompanyLogic, и правила ППЛ позволяют Company представить наружу нужную часть её поведения. В результате SocietyLogic может спросить у компании, сколько ей нужно сырья, заказать для неё товар, проверить её капитал или получить сведения о производстве, не превращая эти действия в прямые операции над внутренним объектом CompanyLogic.

Здесь особенно хорошо видна разница между «логикой логики» и «логикой объекта». CompanyLogic может содержать вычислительные правила, определяющие, сколько товара нужно заказать, можно ли выполнить заказ, как пересчитать состояние капитала и так далее. Но Company как Flow является тем объектом, который существует в обществе. Поэтому SocietyLogic совершенно естественно работает с Company. Искусственное решение заставить SocietyLogic работать только с CompanyLogic создаёт дополнительную границу там, где с точки зрения предметного мира её нет.

В чистом объектно-ориентированном подходе, особенно если воспринимать объект как самостоятельного владельца состояния и поведения, подобная конструкция выглядит вполне естественно. Общество содержит компании, компания содержит своё состояние и операции, агент имеет имя, здоровье и другие свойства. Объекты взаимодействуют друг с другом, потому что именно так устроена предметная область. Однако чистый ООП сам по себе не отвечает на вопрос, где провести границу между внутренним поведением объекта и зависимостью от среды. В результате Unity-класс может постепенно превратиться в гигантский объект, внутри которого одновременно находятся правила предметной области, UI, физика, таймеры, навигация и другие детали исполнения. ППЛ сохраняет объектную модель, но вводит дополнительное измерение: объект можно рассматривать как Flow, внутри которого выделена специализированная Logic. Таким образом, ППЛ не уничтожает ООП-модель, а дисциплинирует её изнутри.

В традиционном подходе, близком к MVC, проблема часто решается через ещё большее разделение: модель, контроллер, представление, сервисы, репозитории, адаптеры и так далее. Это может быть полезно, когда целью является изоляция технических деталей, но в сложной интерактивной модели появляется риск разорвать сам объектный мир на набор представлений. Сотрудник общества превращается в один объект, его логика — в другой, его представление — в третий, его состояние среды — в четвёртый, а связь между ними поддерживается инфраструктурным кодом. В ППЛ агент остаётся агентом. У него есть AgentLogic, но его не требуется превращать в абстрактную сущность, с которой обязаны работать все остальные логики. Другие части мира работают с Agent, а Agent уже локально отделяет собственную предметную логику от средовой реализации.

Если посмотреть на функциональное программирование, ситуация ещё интереснее. Функциональный подход обычно предпочитает чистые функции, явные данные и отсутствие скрытого состояния. В таком представлении очень естественно вынести правила общества в функции, принимающие данные о компаниях и возвращающие новое состояние. Это даёт замечательную локальную тестируемость и математическую прозрачность. Но появляется другой вопрос: что именно считать данными? Если компания перестаёт быть реальным объектом и превращается в структуру данных, то вместе с этим исчезает часть поведения как части самой сущности. Нужно явно передавать всё состояние, контекст, зависимости и результаты преобразований. Для некоторых задач это идеально. Но для интерактивной среды, где объекты постоянно живут, перемещаются, получают события, взаимодействуют с физикой и представляют собой долгоживущие сущности, такой стиль приводит к тому, что значительная часть контекста начинает передаваться через аргументы функций и структуры данных.

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

В терминах Entity-Component-System различие ещё заметнее. ECS обычно рассматривает сущность прежде всего как идентификатор, а данные и поведение выносит в компоненты и системы. Это очень хорошо масштабируется на массовую обработку и позволяет сделать операции над состоянием максимально отделёнными от конкретных объектов. Но за эту способность приходится платить отказом от привычного представления о том, что именно объект является носителем поведения. В ECS логика общества, скорее всего, будет системой, которая получает набор компонентов компаний и обрабатывает их. ППЛ делает противоположный выбор на уровне модели: компания остаётся конкретной сущностью, а SocietyLogic может взаимодействовать с ней непосредственно. То есть вместо «система ищет подходящие компоненты» ППЛ говорит «общество знает свои компании и работает с ними как с участниками мира».

Если объект имеет смысл как самостоятельная сущность, то нет необходимости разрывать его на данные, системы и абстрактные представления только потому, что это считается архитектурно чище. Если вычисление действительно не зависит от состояния конкретного объекта, его можно вынести в Logic. Если действие требует пространства, времени, UI или физики, оно остаётся во Flow. Если Society должна работать с компанией, она работает с Company, потому что Company и есть объект общества.

Из этого появляется важный рекурсивный принцип. ППЛ не требует строить иерархию «Logic над Logic над Logic». Она позволяет строить иерархию реальных сущностей, у каждой из которых есть собственная Logic. Society работает с Company и Person. Company работает с Agent и Market. Building работает с Agent. Каждая сущность является Flow относительно среды своего уровня и содержит отдельную Logic, ответственную за её внутреннее вычислительное содержание. TacCompiler обеспечивает автоматическую проекцию публичного контракта этой Logic на носителя Flow, поэтому верхний уровень может работать с реальным объектом, не проникая внутрь его логики.

Именно поэтому слово «смешение» здесь оказывается обманчивым. SocietyLogic действительно зависит от Company, но это не означает, что SocietyLogic смешана с CompanyLogic. SocietyLogic знает о компании как о понятии своего мира, а не о способе реализации её внутреннего поведения. Это примерно та же разница, что между «знать, что существует человек» и «знать устройство его мозга». Общество должно работать с человеком, иначе оно перестаёт быть моделью общества. Но оно не обязано напрямую вмешиваться во внутренний механизм принятия решений этого человека.

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

И именно это, пожалуй, является главным отличием ППЛ от идеи «чистой логики»: чистота не измеряется отсутствием связей с другими объектами среды. Она измеряется тем, знает ли Logic устройство среды, которое стоит за этими объектами, и не начинает ли сама выполнять его работу. SocietyLogic может работать с реальными Company, Agent и Building и при этом оставаться переносимой, потому что она не обязана знать, как Company рисуется, как Agent ищется через Physics или как Building хранит своё представление в Unity. Она знает мир, но не знает механизм его реализации. В этом смысле ППЛ пытается сохранить сразу две вещи, которые обычно приходится противопоставлять: реалистичный объектный мир и переносимую логику.