应用自身可能提供多种不同的能力,结合服务提供者概念和容器,我们可以实现类似插件的扩展机制,并且通过容器来统一管理服务对象,方便后续扩展。
一、容器
容器提供注册同步/异步工厂的方法、同步/异步获取指定服务对象的方法,移除某服务的方法和清空全部服务对象的方法。
export class Container {
private instances: Map<string, any> = new Map()
private factories: Map<string, () => any> = new Map()
private asyncFactories: Map<string, () => Promise<any>> = new Map()
private resolvingPromises: Map<string, Promise<any>> = new Map()
private singleton: Set<string> = new Set()
// 同步注册工厂(默认为单例)
register<T>(token: string, factory: () => T, options: { singleton?: boolean } = { singleton: true }) {
this.factories.set(token, factory)
if (options.singleton) {
this.singleton.add(token)
} else {
this.singleton.delete(token)
}
}
// 异步注册工厂(默认为单例)
registerAsync<T>(token: string, factory: () => Promise<T>, options: { singleton?: boolean } = { singleton: true }) {
this.asyncFactories.set(token, factory)
if (options.singleton) {
this.singleton.add(token)
} else {
this.singleton.delete(token)
}
}
// 同步解析
resolve<T>(token: string): T {
// 如果是单例且已存在实例,直接返回
if (this.singleton.has(token) && this.instances.has(token)) {
return this.instances.get(token)
}
// 查找工厂函数
const factory = this.factories.get(token)
if (!factory) {
// 如果没有工厂但有实例,可能是异步实例已完成
if (this.instances.has(token)) {
return this.instances.get(token)
}
throw new Error(`Service not found: ${token}`)
}
// 创建实例
const instance = factory()
// 如果是单例,保存实例
if (this.singleton.has(token)) {
this.instances.set(token, instance)
}
return instance
}
// 异步解析
async resolveAsync<T>(token: string): Promise<T> {
// 如果是单例且已存在实例,直接返回
if (this.singleton.has(token) && this.instances.has(token)) {
return this.instances.get(token)
}
// 检查是否已经有正在解析的Promise
let resolvingPromise = this.resolvingPromises.get(token)
if (resolvingPromise) {
// 如果有,直接返回这个Promise的结果
return resolvingPromise
}
// 先检查是否有异步工厂
const asyncFactory = this.asyncFactories.get(token)
if (asyncFactory) {
// 创建一个新的解析Promise并存储
resolvingPromise = (async () => {
try {
const resolved = await asyncFactory()
// 如果是单例,保存实例
if (this.singleton.has(token)) {
this.instances.set(token, resolved)
}
return resolved
} finally {
// 解析完成后从resolvingPromises中移除
this.resolvingPromises.delete(token)
}
})()
// 如果是单例,存储Promise以避免重复创建
if (this.singleton.has(token)) {
this.resolvingPromises.set(token, resolvingPromise)
}
return resolvingPromise
}
// 再检查是否有同步工厂
const factory = this.factories.get(token)
if (factory) {
try {
const resolved = factory()
// 如果是单例,保存实例
if (this.singleton.has(token)) {
this.instances.set(token, resolved)
}
return resolved
} catch (error) {
if (error instanceof Error) {
throw new Error(`Failed to resolve service '${token}': ${error.message}`)
} else {
throw error
}
}
}
// 最后检查是否已有实例(可能是之前异步解析完成的)
if (this.instances.has(token)) {
return this.instances.get(token)
}
throw new Error(`Service not found: ${token}`)
}
// 检查是否存在指定token的服务
has(token: string): boolean {
return this.instances.has(token) ||
this.factories.has(token) ||
this.asyncFactories.has(token)
}
// 清除指定服务的实例(不移除工厂)
clearInstance(token: string): void {
this.instances.delete(token)
this.resolvingPromises.delete(token)
}
// 移除服务(包括工厂和实例)
remove(token: string): void {
this.instances.delete(token)
this.factories.delete(token)
this.asyncFactories.delete(token)
this.resolvingPromises.delete(token)
this.singleton.delete(token)
}
}
二、服务提供者
服务提供者接口比较简单,区分register和boot主要是为了确保boot中可以调用任何其他提供者。
import type { Container } from "../container"
export default interface ServiceProvider {
/**
* 应用启动后立即调用
* 建议仅用于注册服务,不要执行其他操作,其他操作可以放在boot方法中
*
* @param container
*/
register?(container: Container): void
/**
* boot函数在所有服务注册完成后调用
* 可以执行一些初始化操作
*
* @param container
*/
boot?(container: Container): void | Promise<void>
}
三、应用对象
import { Container } from './container'
export default class APP {
/**
* 容器对象
*/
private container: Container
/**
* 服务提供者列表
*/
private providers: ServiceProvider[] = []
constructor() {
// 初始化容器并注册服务提供者
this.container = new Container()
// 注册服务
this.registerProviders()
// 启动服务
this.bootProviders()
}
registerProviders() {
for (const provider of this.providers) {
provider.register?.(this.container)
}
}
bootProviders() {
return Promise.all(
this.providers.map(provider => provider.boot?.(this.container))
)
}
}