ESEngine服务容器指南:依赖注入让代码更解耦、更可测试
ESEngine服务容器指南依赖注入让代码更解耦、更可测试【免费下载链接】esengineESEngine - High-performance TypeScript ECS Framework for Game Development项目地址: https://gitcode.com/gh_mirrors/ese/esengineESEngine服务容器是TypeScript游戏开发框架的核心依赖注入系统它让代码组织更加优雅、模块化程度更高。通过服务容器开发者可以轻松管理游戏中的各种服务实现代码解耦和更好的可测试性。什么是服务容器服务容器是ESEngine框架中的依赖注入DI容器负责管理所有服务的注册、解析和生命周期。它提供了一个集中式的服务管理机制让组件之间的依赖关系更加清晰代码更加模块化。在游戏开发中我们经常需要管理各种服务时间管理、音频播放、网络通信、物理引擎、AI系统等。传统的做法是直接在代码中创建这些服务的实例导致代码高度耦合难以测试和维护。ESEngine的服务容器通过依赖注入解决了这个问题。核心功能与优势✨ESEngine服务容器提供了以下核心功能1. 服务注册与解析单例模式全局唯一实例瞬时模式每次解析都创建新实例工厂函数自定义实例创建逻辑实例注册直接注册已有实例2. 生命周期管理自动管理服务实例的创建和销毁提供dispose()方法进行资源清理支持循环依赖检测3. 依赖注入装饰器Injectable()标记可注入的类InjectProperty()属性依赖注入Updatable()标记需要每帧更新的服务4. 多层容器架构ESEngine提供了三级服务容器层级Core.services (应用全局) └─ World.services (世界层级) └─ Scene.services (场景层级)这种分层设计让服务的作用域更加清晰不同层级的服务可以独立管理避免全局污染。快速上手指南⚡基础使用让我们通过一个简单的例子来看看ESEngine服务容器的基本用法import { Core, Injectable, InjectProperty, IService } from esengine/ecs-framework; // 定义服务接口 interface IAudioService extends IService { playSound(name: string): void; stopSound(name: string): void; } // 音频服务实现 Injectable() class AudioService implements IAudioService { private sounds new Mapstring, HTMLAudioElement(); playSound(name: string): void { const audio this.sounds.get(name); if (audio) { audio.currentTime 0; audio.play(); } } stopSound(name: string): void { const audio this.sounds.get(name); if (audio) { audio.pause(); } } dispose(): void { this.sounds.clear(); } } // 游戏服务依赖音频服务 Injectable() class GameService implements IService { InjectProperty(AudioService) private audio!: AudioService; private score 0; addScore(points: number): void { this.score points; this.audio.playSound(score); } dispose(): void { // 清理资源 } } // 注册服务到容器 Core.services.registerSingleton(AudioService); Core.services.registerSingleton(GameService); // 解析并使用服务 const gameService Core.services.resolve(GameService); gameService.addScore(100); // 播放得分音效依赖注入装饰器ESEngine提供了强大的装饰器来简化依赖注入import { Injectable, InjectProperty, Updatable, IService, IUpdatable } from esengine/ecs-framework; // 配置服务 Injectable() class ConfigService implements IService { private settings { volume: 0.8, difficulty: normal, graphics: high }; getSetting(key: string): any { return this.settings[key]; } dispose(): void {} } // 时间服务 - 每帧更新 Injectable() Updatable(5) // 优先级5数值越小越先执行 class TimeService implements IService, IUpdatable { private deltaTime 0; private totalTime 0; update(deltaTime?: number): void { this.deltaTime deltaTime || 0; this.totalTime this.deltaTime; } getDeltaTime(): number { return this.deltaTime; } getTotalTime(): number { return this.totalTime; } dispose(): void {} } // 物理系统依赖配置和时间服务 Injectable() Updatable(10) // 优先级10在时间服务之后执行 class PhysicsSystem implements IService, IUpdatable { InjectProperty(ConfigService) private config!: ConfigService; InjectProperty(TimeService) private time!: TimeService; private gravity 9.8; onInitialize(): void { // 从配置服务获取重力设置 const gravitySetting this.config.getSetting(gravity); if (gravitySetting) { this.gravity gravitySetting; } } update(): void { const dt this.time.getDeltaTime(); // 物理模拟逻辑 // ... } dispose(): void {} } // 自动注册所有服务 import { registerInjectable } from esengine/ecs-framework; registerInjectable(Core.services, ConfigService); registerInjectable(Core.services, TimeService); registerInjectable(Core.services, PhysicsSystem);实际应用场景游戏状态管理服务容器非常适合管理游戏状态Injectable() class GameStateService implements IService { private state: menu | playing | paused | gameover menu; private score 0; private level 1; startGame(): void { this.state playing; this.score 0; this.level 1; } addScore(points: number): void { this.score points; } getState(): string { return this.state; } getScore(): number { return this.score; } dispose(): void {} } // UI系统依赖游戏状态 Injectable() class UISystem implements IService { InjectProperty(GameStateService) private gameState!: GameStateService; private scoreElement: HTMLElement | null null; onInitialize(): void { this.scoreElement document.getElementById(score); this.updateUI(); } updateUI(): void { if (this.scoreElement) { this.scoreElement.textContent Score: ${this.gameState.getScore()}; } } dispose(): void {} }插件系统集成ESEngine的插件系统与服务容器完美结合import { IPlugin, Core, ServiceContainer } from esengine/ecs-framework; // 音频插件 class AudioPlugin implements IPlugin { readonly name audio-plugin; readonly version 1.0.0; install(core: Core, services: ServiceContainer): void { // 注册音频服务 services.registerSingleton(AudioService, () { const audio new AudioService(); audio.initialize(); return audio; }); // 注册音效管理器 services.registerSingleton(SoundManager); } uninstall(): void { // 清理资源 } } // 物理插件 class PhysicsPlugin implements IPlugin { readonly name physics-plugin; readonly version 1.0.0; install(core: Core, services: ServiceContainer): void { // 依赖音频插件的服务 const audio services.resolve(AudioService); // 注册物理服务 services.registerSingleton(PhysicsService, () { const physics new PhysicsService(); physics.setCollisionSoundCallback((collision) { audio.playSound(collision-${collision.intensity}); }); return physics; }); } uninstall(): void {} } // 安装插件 Core.installPlugin(new AudioPlugin()); Core.installPlugin(new PhysicsPlugin());高级特性工厂函数注册使用工厂函数可以更灵活地创建服务实例// 使用工厂函数创建配置服务 Core.services.registerSingleton(ConfigService, (container) { const config new ConfigService(); // 根据环境加载不同配置 if (process.env.NODE_ENV development) { config.load(config/dev.json); } else { config.load(config/prod.json); } return config; }); // 使用工厂函数创建网络服务 Core.services.registerSingleton(NetworkService, (container) { const config container.resolve(ConfigService); const network new NetworkService(); // 从配置服务获取服务器地址 network.setEndpoint(config.get(serverUrl)); network.setTimeout(config.get(timeout, 5000)); return network; });Symbol标识符对于接口抽象推荐使用Symbol作为服务标识符// 定义接口和Symbol标识符 const IFileSystem Symbol.for(IFileSystem); const ILogger Symbol.for(ILogger); interface IFileSystem extends IService { readFile(path: string): Promisestring; writeFile(path: string, content: string): Promisevoid; } interface ILogger extends IService { log(message: string, level: info | warn | error): void; } // 具体实现 class BrowserFileSystem implements IFileSystem { async readFile(path: string): Promisestring { // 浏览器文件系统实现 return ; } async writeFile(path: string, content: string): Promisevoid { // 浏览器文件系统实现 } dispose(): void {} } class ConsoleLogger implements ILogger { log(message: string, level: info | warn | error): void { consolelevel; } dispose(): void {} } // 使用Symbol注册服务 Core.services.registerInstance(IFileSystem, new BrowserFileSystem()); Core.services.registerInstance(ILogger, new ConsoleLogger()); // 使用Symbol解析服务 const fs Core.services.resolveIFileSystem(IFileSystem); const logger Core.services.resolveILogger(ILogger);可更新服务使用Updatable装饰器标记需要每帧更新的服务Injectable() Updatable(0) // 最高优先级 class InputService implements IService, IUpdatable { private keys new Setstring(); update(): void { // 每帧更新输入状态 this.processInput(); } isKeyPressed(key: string): boolean { return this.keys.has(key); } private processInput(): void { // 处理输入逻辑 } dispose(): void { this.keys.clear(); } } Injectable() Updatable(20) // 较低优先级 class ParticleSystem implements IService, IUpdatable { InjectProperty(TimeService) private time!: TimeService; private particles: Particle[] []; update(): void { const dt this.time.getDeltaTime(); // 更新所有粒子 for (const particle of this.particles) { particle.update(dt); } // 移除死亡粒子 this.particles this.particles.filter(p p.isAlive()); } dispose(): void { this.particles []; } } // 所有可更新服务都会在Core.update()中自动调用 function gameLoop(deltaTime: number) { Core.update(deltaTime); // 自动更新InputService和ParticleSystem }最佳实践1. 服务设计原则单一职责每个服务只负责一个明确的功能接口分离使用接口定义服务契约用Symbol作为标识符依赖注入使用InjectProperty而非构造函数注入资源管理在dispose()方法中清理所有资源2. 注册顺序注册服务时要注意依赖关系// 正确的注册顺序基础服务先注册 Core.services.registerSingleton(ConfigService); Core.services.registerSingleton(TimeService); Core.services.registerSingleton(AudioService); // 依赖其他服务的后注册 Core.services.registerSingleton(PhysicsService); Core.services.registerSingleton(GameLogicService);3. 错误处理// 使用tryResolve处理可选依赖 const optionalService Core.services.tryResolve(OptionalService); if (optionalService) { // 服务存在安全使用 optionalService.doSomething(); } // 检查服务是否已注册 if (Core.services.isRegistered(RequiredService)) { const service Core.services.resolve(RequiredService); // 使用服务 } // 处理循环依赖 try { const service Core.services.resolve(CircularService); } catch (error) { if (error.message.includes(Circular dependency)) { console.error(检测到循环依赖请重新设计服务结构); } }4. 测试友好服务容器让单元测试变得更加简单// 生产代码 Injectable() class GameService implements IService { InjectProperty(ScoreService) private scoreService!: ScoreService; addPoints(points: number): void { this.scoreService.add(points); } dispose(): void {} } // 测试代码 describe(GameService, () { let container: ServiceContainer; let mockScoreService: jest.MockedScoreService; beforeEach(() { container new ServiceContainer(); // 创建模拟服务 mockScoreService { add: jest.fn(), dispose: jest.fn() } as any; // 注册模拟服务 container.registerInstance(ScoreService, mockScoreService); container.registerSingleton(GameService); }); test(should add points through score service, () { const gameService container.resolve(GameService); gameService.addPoints(100); expect(mockScoreService.add).toHaveBeenCalledWith(100); }); });常见问题解答❓Q: 服务容器和普通全局变量有什么区别A: 服务容器提供了依赖注入、生命周期管理、循环依赖检测等高级功能而全局变量只是简单的存储。服务容器让代码更加模块化、可测试。Q: 什么时候使用单例什么时候使用瞬时服务A:单例用于全局状态管理、配置服务、管理器类瞬时用于命令对象、临时计算、无状态服务Q: 如何处理服务间的循环依赖A: ESEngine会自动检测循环依赖并抛出错误。解决方案通常是重新设计服务结构提取公共功能使用事件系统解耦引入中间服务Q: 服务容器性能如何A: ESEngine的服务容器经过高度优化服务解析使用缓存机制性能开销极小。在生产环境中几乎可以忽略不计。总结ESEngine的服务容器是一个强大而灵活的依赖注入系统它为游戏开发提供了代码解耦服务之间通过接口而非具体实现耦合可测试性轻松替换模拟服务进行单元测试生命周期管理自动管理服务的创建和销毁插件化架构支持动态加载和卸载服务类型安全完整的TypeScript支持通过合理使用服务容器你可以构建出更加模块化、可维护、可测试的游戏代码。无论是小型休闲游戏还是大型复杂项目ESEngine的服务容器都能帮助你更好地组织代码结构。ESEngine服务容器架构示意图 - 展示多层容器和依赖注入关系开始使用ESEngine服务容器让你的游戏代码更加优雅和健壮【免费下载链接】esengineESEngine - High-performance TypeScript ECS Framework for Game Development项目地址: https://gitcode.com/gh_mirrors/ese/esengine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考