Advanced Usage
Interface and Symbol Identifier Pattern
Section titled “Interface and Symbol Identifier Pattern”For large projects or games requiring cross-platform adaptation, the “Interface + Symbol.for identifier” pattern is recommended.
Why Use Symbol.for
Section titled “Why Use Symbol.for”- Cross-package Sharing:
Symbol.for('key')creates/retrieves Symbol from global registry - Interface Decoupling: Consumers only depend on interface definitions, not concrete implementations
- Replaceable Implementations: Can inject different implementations at runtime (test mocks, platform adapters)
Define Interface and Identifier
Section titled “Define Interface and Identifier”export interface IAudioService { dispose(): void; playSound(id: string): void; playMusic(id: string, loop?: boolean): void; stopMusic(): void; setVolume(volume: number): void; preload(id: string, url: string): Promise<void>;}
// Use Symbol.for to ensure cross-package sharingexport const IAudioService = Symbol.for('IAudioService');Implement Interface
Section titled “Implement Interface”// WebAudioService.ts - Web platformexport class WebAudioService implements IAudioService { private audioContext: AudioContext;
constructor() { this.audioContext = new AudioContext(); }
playSound(id: string): void { // Web Audio API implementation }
dispose(): void { this.audioContext.close(); }}
// WechatAudioService.ts - WeChat Mini Game platformexport class WechatAudioService implements IAudioService { playSound(id: string): void { // WeChat Mini Game API implementation }
dispose(): void { // Cleanup }}Register and Use
Section titled “Register and Use”// Register different implementations based on platformif (typeof wx !== 'undefined') { Core.services.registerInstance(IAudioService, new WechatAudioService());} else { Core.services.registerInstance(IAudioService, new WebAudioService());}
// Business code - doesn't care about concrete implementationconst audio = Core.services.resolve<IAudioService>(IAudioService);audio.playSound('explosion');Symbol vs Symbol.for
Section titled “Symbol vs Symbol.for”// Symbol() - Creates unique Symbol each timeconst sym1 = Symbol('test');const sym2 = Symbol('test');console.log(sym1 === sym2); // false
// Symbol.for() - Shares in global registryconst sym3 = Symbol.for('test');const sym4 = Symbol.for('test');console.log(sym3 === sym4); // trueCircular Dependency Detection
Section titled “Circular Dependency Detection”The service container automatically detects circular dependencies:
// A depends on B, B depends on A@Injectable()class ServiceA implements IService { @InjectProperty(ServiceB) private b!: ServiceB; dispose(): void {}}
@Injectable()class ServiceB implements IService { @InjectProperty(ServiceA) private a!: ServiceA; dispose(): void {}}
// Throws error on resolution// Circular dependency detected: ServiceA -> ServiceB -> ServiceAService Management
Section titled “Service Management”// Get all registered service typesconst types = Core.services.getRegisteredServices();
// Get all instantiated service instancesconst instances = Core.services.getAll();
// Unregister single serviceCore.services.unregister(MyService);
// Clear all services (calls dispose on each)Core.services.clear();Best Practices
Section titled “Best Practices”Service Naming
Section titled “Service Naming”Service class names should end with Service or Manager:
class PlayerService implements IService {}class AudioManager implements IService {}class NetworkService implements IService {}Resource Cleanup
Section titled “Resource Cleanup”Always clean up resources in the dispose() method:
class ResourceService implements IService { private resources: Map<string, Resource> = new Map();
dispose(): void { for (const resource of this.resources.values()) { resource.release(); } this.resources.clear(); }}Avoid Overuse
Section titled “Avoid Overuse”Don’t register every class as a service. Services should be:
- Global singletons or need shared state
- Used in multiple places
- Need lifecycle management
- Require dependency injection
Dependency Direction
Section titled “Dependency Direction”Maintain clear dependency direction, avoid circular dependencies:
High-level services -> Mid-level services -> Low-level servicesGameLogic -> DataService -> ConfigServiceWhen to Use Singleton vs Transient
Section titled “When to Use Singleton vs Transient”- Singleton: Manager classes, config, cache, state management
- Transient: Command objects, request handlers, temporary tasks
Common Issues
Section titled “Common Issues”Service Not Registered Error
Section titled “Service Not Registered Error”Problem: Error: Service MyService is not registered
Solution:
// Ensure service is registeredCore.services.registerSingleton(MyService);
// Or use tryResolveconst service = Core.services.tryResolve(MyService);if (!service) { console.log('Service not found');}Circular Dependency Error
Section titled “Circular Dependency Error”Problem: Circular dependency detected
Solution: Redesign service dependencies, introduce intermediate services or use event system for decoupling.