Angular中动态查找服务实现的方案咨询
你说的这种策略模式场景我在Angular项目里也遇到过,那种满是switch分支的工厂函数确实维护起来很头疼。好在Angular的依赖注入系统天生就支持这种多实现服务的动态查找,完全不用靠手动写分支来处理。下面我给你讲讲几种原生的实现方式和最佳实践:
方法一:多提供者(Multi Providers)+ 工厂服务
这是最符合Angular风格的实现方式,通过多提供者把所有策略实例收集到一个注入令牌下,再用工厂服务做动态匹配。
第一步:定义接口与注入令牌
先统一上报接口,并创建一个用来收集所有策略实现的注入令牌:
import { InjectionToken, Injectable, Inject } from '@angular/core'; export interface IReporter { report(message: string): void; // 给每个实现添加唯一标识key,用于工厂查找 readonly key: string; } // 注入令牌,用来收集所有Reporter策略 export const REPORTER_STRATEGIES = new InjectionToken<IReporter[]>('REPORTER_STRATEGIES');
第二步:实现各个上报策略
每个策略类用@Injectable装饰,同时实现IReporter接口:
@Injectable() export class NoReporter implements IReporter { readonly key = 'none'; report(message: string): void { // 空实现,关闭上报 } } @Injectable() export class ConsoleReporter implements IReporter { readonly key = 'console'; report(message: string): void { console.log('[Console上报]', message); } } @Injectable() export class RestApiReporter implements IReporter { readonly key = 'rest-api'; constructor(private http: HttpClient) {} report(message: string): void { this.http.post('/api/report', { event: message }).subscribe(); } }
第三步:注册多提供者
在模块中把所有策略类注册到REPORTER_STRATEGIES令牌下,开启multi: true表示这是一个多提供者:
@NgModule({ providers: [ { provide: REPORTER_STRATEGIES, useClass: NoReporter, multi: true }, { provide: REPORTER_STRATEGIES, useClass: ConsoleReporter, multi: true }, { provide: REPORTER_STRATEGIES, useClass: RestApiReporter, multi: true }, ReporterFactory ] }) export class AppModule {}
第四步:编写工厂服务
工厂服务注入所有策略实例,将其存入Map实现快速查找:
@Injectable({ providedIn: 'root' }) export class ReporterFactory { private strategyMap = new Map<string, IReporter>(); constructor(@Inject(REPORTER_STRATEGIES) strategies: IReporter[]) { strategies.forEach(strategy => { this.strategyMap.set(strategy.key, strategy); }); } getReporter(key: string): IReporter { // 找不到时返回默认的NoReporter兜底 return this.strategyMap.get(key) || this.strategyMap.get('none')!; } }
消费端使用
在组件或其他服务中直接注入工厂,根据运行时偏好获取对应策略:
@Component({ selector: 'app-demo', template: '' }) export class DemoComponent { private reporter: IReporter; constructor( private factory: ReporterFactory, private userSettings: UserSettingsService ) { // 从用户配置获取偏好的上报方式 const preferredKey = userSettings.getReportStrategy(); this.reporter = factory.getReporter(preferredKey); } triggerReport() { this.reporter.report('用户完成了某个操作'); } }
方法二:Injector动态实例化
如果有些策略比较重,不想提前初始化所有实例,可以给每个策略单独定义注入令牌,通过Injector动态获取实例:
定义单个策略令牌
export const NO_REPORTER = new InjectionToken<IReporter>('NO_REPORTER'); export const CONSOLE_REPORTER = new InjectionToken<IReporter>('CONSOLE_REPORTER'); export const REST_API_REPORTER = new InjectionToken<IReporter>('REST_API_REPORTER');
注册单个提供者
@NgModule({ providers: [ { provide: NO_REPORTER, useClass: NoReporter }, { provide: CONSOLE_REPORTER, useClass: ConsoleReporter }, { provide: REST_API_REPORTER, useClass: RestApiReporter }, ReporterFactory ] }) export class AppModule {}
工厂服务用Injector动态获取
@Injectable({ providedIn: 'root' }) export class ReporterFactory { private keyToTokenMap = new Map<string, InjectionToken<IReporter>>([ ['none', NO_REPORTER], ['console', CONSOLE_REPORTER], ['rest-api', REST_API_REPORTER] ]); constructor(private injector: Injector) {} getReporter(key: string): IReporter { const token = this.keyToTokenMap.get(key) || this.keyToTokenMap.get('none')!; // 按需实例化策略 return this.injector.get(token); } }
最佳实践
- 接口一致性:所有策略严格实现
IReporter接口,确保替换策略时不会出现兼容性问题 - 默认兜底策略:工厂必须提供默认策略(比如
NoReporter),防止传入无效key导致报错 - 枚举化策略key:把策略key定义成枚举(比如
enum ReporterKey { None = 'none', Console = 'console' }),避免拼写错误 - 结合配置加载:用
APP_INITIALIZER在应用启动时加载用户偏好配置,提前初始化对应的策略 - 依赖交给DI管理:策略类的依赖(比如
RestApiReporter的HttpClient)全部通过构造函数注入,不要手动实例化
内容的提问来源于stack exchange,提问作者qqilihq
相关产品推荐
相关产品推荐

