1. GameFacade показывает доступные возможности

Service выполняет одну понятную задачу. Facade даёт типизированный доступ к важным сервисам и операциям текущего окружения. Он скрывает сложность доступа, но не забирает себе игровую логику.

В проекте могут быть отдельные фасады для Menu и Play. В обоих случаях точка входа называется Game., но IDE показывает возможности именно этой части игры. В исходных примерах это вызовы:

Game.UI.Settings.Open();
Game.Flow.PlayMap(_mapDocks, _gameMode_Roam);

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

Основные обязательные сервисы стоит делать видимыми в типизированном API фасада. GetService<T> — редкий и объяснимый выход для возможностей, которые не нужно включать в основной интерфейс. Необязательная зависимость должна быть явно необязательной: для неё нужен TryGetService или отдельный optional API.

Свойство должно оставаться дешёвым кешированным чтением, без выделения памяти и скрытых действий — тогда его можно использовать в Update. Запросы и изменения состояния выражаются методами или командами.

2. Интерфейс меняет форму зависимости, данные — её границу

Flexy сознательно использует явный layered Service Locator. Это выбор способа организации окружения. DI тоже не уничтожает зависимости: он может перенаправлять их через контейнер. Выбор инструмента не отменяет необходимость понимать связи и время жизни объектов.

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

Предположим, мобу нужен бонус скорости. Передача IPerkProvider оставляет зависимость от поставщика perks. Передача готового значения SpeedBoost делает границу уже: объекту нужен результат, а не чужая подсистема. Но если по design моб действительно живёт в coregame и использует perks игрока, прямой доступ через Game.Player.Perks.SpeedBoost может быть проще и честнее.

Выбирайте форму по реальной ответственности. Интерфейс полезен, когда выражает нужную границу; дополнительная абстракция без задачи только усложняет путь к данным.

Границы и применение Facade

Facade - это typed access surface к important services и operations.

Meaning

  • Facade упрощает доступ к context.
  • Facade не должен владеть gameplay logic.
  • Shortcut methods допустимы, если они не превращаются в domain behavior.

What It Prevents

  • Прямой доступ к service container из любого места.
  • Новый GameManager под красивым именем.
  • Hidden dependency resolution через Resolve<Anything>().

Critique Notes

Facade, Context и explicit Service Locator

  • В Flexy Context и GameFacade сознательно являются explicit layered Service Locator.
  • Это не ошибка, а design choice.
  • DI в Unity часто на практике является тем же Service Locator, только скрытым за container, bindings, installers и framework ceremony.
  • DI не убирает dependencies, а перенаправляет их через container.
  • Способность DI создавать objects на ходу в gameplay runtime может быть вредной, потому что усложняет lifetime и делает связи менее явными.
  • Flexy предпочитает открытые и прямые связи скрытым связям.
  • Если entity не может жить вне CoregameContext, dependency на CoregameContext не является проблемой. Это production fact.
  • CoregameContext содержит конкретные обязательные services, без которых core game не живет.
  • Optional services должны доставаться явно через TryGetService, например camera-like services.
  • GetService<T> допустим как rare escape hatch для services, которые редко используются и не должны засорять основной facade surface.
  • GetService<T> не должен быть основной формой доступа, если service является важной частью игры.
  • Для записи/изменений используется MsgBus.Send или explicit service methods.
  • Public API конкретного service - ответственность самого service, а не глобальной architecture.
  • Interface не делает object независимым. Он только меняет форму dependency.
  • Если в production существует одна реализация interface, interface часто является ceremony и лишним indirection.
  • Настоящая независимость появляется, когда object получает data/value, а не provider/interface.
  • Например, чтобы mob не зависел от perks, ему нужно передать SpeedBoost как value, а не IPerkProvider.
  • Но если mob по design живет внутри coregame и зависит от player perks, прямой доступ через Game.Player.Perks.SpeedBoost может быть проще и честнее.
  • В hot path не стоит добавлять interface/virtual indirection без реальной пользы.
  • Execution order в играх не является smell.
  • Hidden execution order является smell.
  • Games are frame pipelines.
  • Systems и groups должны делать frame pipeline явным.
  • ScriptExecutionOrder плох не сам по себе, а когда порядок размазан, не документирован и не выражает gameplay pipeline.
  • Доступ через facade в Update допустим, если он cached, predictable, allocation-free и не скрывает mutation или object creation.
  • Field/property/method naming должен не врать о cost и side effects.
  • Properties должны быть cheap cached reads.
  • Methods могут быть queries/actions.
  • Commands должны явно менять state.

Риски, которые остаются:

  • обязательные и optional services смешиваются;
  • GetService<T> становится основным способом доступа;
  • interfaces используются как fake decoupling;
  • systems order существует, но не описан явно;
  • service API плохо назван и скрывает cost/mutation.

Формулировки, которые можно перенести почти напрямую:

DI does not remove dependencies. It redirects them through a container.

Interface does not make an object independent. Data input does.

If a game object cannot exist outside CoregameContext, dependency on CoregameContext is not a problem. It is a fact.

Flexy does not hide Service Locator. Flexy makes service location explicit, layered and visible.

Execution order is not a smell in games. Hidden execution order is.

Games are frame pipelines. Systems and groups should make that pipeline explicit.

Service location is not the enemy. Uncontrolled hidden access is the enemy.

Facade naming confusion

  • GameFacade - корректное название, если сразу объяснить его Flexy-смысл.
  • GameFacade действительно является facade: он скрывает dynamic context/service structure за simple typed API.
  • GameFacade скрывает access complexity, а не должен владеть всей domain/game logic.
  • GameFacade отвечает за:
    • доступ к services текущего context/package;
    • кеширование access;
    • удобный Game. entry point;
    • IDE/IntelliSense visibility;
    • readable service surface.
  • GameFacade показывает, что доступно из конкретной части игры: menu, play, package, etc.
  • Logic в основном остается в services, systems, states, bricks/toys.
  • Methods в facade допустимы, если это small shortcut methods.
  • Small shortcut method может делать несколько вызовов к flow/state/service, чтобы дать простой API.
  • Пример: Game.Flow.PlayMap(...) может скрывать несколько вызовов для перехода из menu в play.
  • Это нормально, если method не содержит business logic и не становится owner gameplay decision.
  • Facade method плох, если начинает содержать domain/game logic.
  • Граница:
    • Game.Flow.PlayMap(...) - ok, если это transition shortcut;
    • Game.UI.Settings.Open() - ok;
    • Game.Audio.PlayClick() - ok;
    • Game.CalculateDamage() - suspicious;
    • Game.ResolveCombat() - suspicious;
    • Game.ApplyEconomyReward() - suspicious, если это настоящая domain logic.
  • Правило не "никаких methods в facade".
  • Правило: facade can simplify access, but must not own gameplay logic.

Формулировки, которые можно перенести почти напрямую:

GameFacade hides dynamic Context/Service structure behind simple typed API.

Facade is allowed to simplify access. Facade is not allowed to own gameplay logic.

Small shortcut methods are fine. Hidden domain logic is not.

GameFacade turns service location into readable code.

Все темы Flexy.Bricks\ToYs