Services Container
Описание
Модуль ServiceContainer
реализует паттерн Dependency Injection в ECS фреймворке. Он позволяет управлять зависимостями как на глобальном уровне, так и на уровне отдельных модулей.
Назначение
- Управление зависимостями в приложении
- Внедрение зависимостей в Системы
- Поддержка модульной архитектуры
- Создание иммутабельных сервисов
Архитектура
Основные компоненты
- ServiceContainer - синглтон для управления зависимостями
- Provider - описание способа создания зависимости
- Token - уникальный идентификатор зависимости
- @Inject - декоратор для внедрения зависимостей в Системы
Уровни зависимостей
ServiceContainer
├── Глобальные зависимости
│ └── Доступны везде по умолчанию
└── Модульные зависимости
└── Переопределяют глобальные в рамках модуля
Использование
Регистрация глобальных зависимостей
// В EmpressCore или другой точке инициализации
ServiceContainer.instance.registerGlobal([
// Класс как зависимость
{
provide: AbstractAudioService,
useClass: DefaultAudioService
},
// Объект как зависимость
{
provide: GameConfig,
useFactory: () => ({
width: 800,
height: 600
})
},
// Иммутабельный объект
{
provide: GameState,
useFactory: () => ({ score: 0 }),
immutable: true
}
]);
Переопределение в модуле
class GameModule extends SystemGroup {
protected setupDependencies(): Provider[] {
return [
// Переопределяем глобальный сервис
{
provide: AbstractAudioService,
useClass: CustomAudioService
}
];
}
}
Использование в Системах
class MovementSystem extends System {
// Внедрение зависимости через декоратор
@Inject(AbstractAudioService)
private _audio!: AbstractAudioService;
@Inject(GameState)
private _state!: GameState; // Иммутабельный объект
public execute() {
// Используем сервисы
this._audio.playSound('move');
// Попытка изменить иммутабельный объект вызовет предупреждение
this._state.score = 100; // Warning: Direct state mutation is not allowed
}
}
API
ServiceContainer
Методы
registerGlobal(providers: Provider[])
- регистрация глобальных зависимостейregisterModule(moduleId: string, providers: Provider[])
- регистрация зависимостей модуляget<T>(token: Token<T>, moduleId?: string)
- получение зависимостиmemorizeSystem(system, token, key)
- запоминание зависимости для СистемыgetDependencyForSystem(moduleId, system)
- внедрение зависимостей в Систему
Provider
interface Provider<T = any> {
provide: Token<T>; // Токен зависимости
useClass?: new () => T; // Класс для создания
useFactory?: () => T; // Фабричная функция
immutable?: boolean; // Флаг иммутабельности
}
FAQ
Как работает поиск зависимостей?
- Сначала ищется в зависимостях текущего модуля
- Если не найдено, ищется в глобальных зависимостях
- Если нигде не найдено, выбрасывается ошибка
Как работает иммутабельность?
Если провайдер помечен как immutable: true
:
- Создается Proxy вокруг объекта
- Попытки изменения свойств блокируются
- Выводится предупреждение в консоль
Когда создаются инстансы зависимостей?
- При первом запросе через
get
- Созданный инстанс кэшируется
- Последующие запросы возвращают кэшированный инстанс
Как переопределять зависимости?
- Глобальные зависимости регистрируются через
registerGlobal
- Модульные зависимости через
registerModule
илиsetupDependencies
- Модульные имеют приоритет над глобальными
Как использовать декоратор @Inject?
- Пометить свойство Системы декоратором
- Указать токен зависимости
- Зависимость будет внедрена автоматически при создании Системы