Skip to content

a

✍️ w 🕒 2024-06-20 14:17:34(2 days ago) 🔗 G.设计模式

当然,这段代码是一个简单的依赖注入(Dependency Injection,简称 DI)实现示例。我们逐步解释每个部分的作用和功能。

1. injectable 装饰器

typescript
function injectable(target: any) {
  // 将类标记为可注入
  Reflect.defineMetadata('injectable', true, target);
}
  • 作用injectable 装饰器用于标记一个类可以被依赖注入容器管理。
  • 实现:通过 Reflect.defineMetadata 方法为目标类(target)添加一个名为 'injectable' 的元数据,并设置其值为 trueReflect 是一个元数据操作库,需要额外安装 reflect-metadata 包。

2. 标记 MyService 类为可注入

typescript
@injectable
class MyService {
  // 服务逻辑
}
  • 作用:使用 @injectable 装饰器标记 MyService 类,使其可以被依赖注入容器管理。
  • 实现@injectable 装饰器会将 MyService 类标记为可注入,并在元数据中记录这个信息。

3. inject 装饰器

typescript
function inject(target: any, propertyKey: string, parameterIndex: number) {
  const existingParameters = Reflect.getOwnMetadata('design:paramtypes', target, propertyKey) || [];
  existingParameters[parameterIndex] = true; // 标记参数为可注入
  Reflect.defineMetadata('design:paramtypes', existingParameters, target, propertyKey);
}
  • 作用inject 装饰器用于标记构造函数参数需要依赖注入。
  • 实现Reflect.getOwnMetadata 获取现有的参数元数据,如果没有则初始化为空数组。然后将指定参数位置(parameterIndex)标记为 true。最后,通过 Reflect.defineMetadata 方法将这些元数据重新设置到目标类上。

4. 使用 @inject 装饰器在 MyComponent 构造函数中注入 MyService

typescript
class MyComponent {
  constructor(@inject private myService: MyService) {}
}
  • 作用:使用 @inject 装饰器标记 MyComponent 构造函数的参数 myService 需要依赖注入。
  • 实现@inject 装饰器会在参数元数据中记录这个依赖注入需求。

依赖注入流程

  1. 定义服务类MyService@injectable 装饰,表明它是一个可注入的服务。
  2. 定义使用依赖的类MyComponent 类的构造函数参数 myService@inject 装饰,表明它需要注入一个 MyService 实例。
  3. 注入依赖:在创建 MyComponent 实例时,依赖注入容器会读取元数据,发现 myService 需要注入一个 MyService 实例,并自动创建 MyService 实例并注入。

依赖注入容器实现(示例)

为了使上述装饰器真正起作用,还需要一个简单的依赖注入容器实现:

typescript
import 'reflect-metadata';

class Container {
  private static instances = new Map();

  static get<T>(target: any): T {
    if (this.instances.has(target)) {
      return this.instances.get(target);
    }

    const injectable = Reflect.getMetadata('injectable', target);
    if (!injectable) {
      throw new Error(`Cannot inject ${target.name}. It is not marked as injectable.`);
    }

    const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
    const params = paramTypes.map((param: any) => this.get(param));

    const instance = new target(...params);
    this.instances.set(target, instance);
    return instance;
  }
}

const myComponent = Container.get(MyComponent);
console.log(myComponent);

依赖注入容器工作原理

  1. 检查是否已有实例:如果容器中已经存在目标类的实例,直接返回该实例。
  2. 检查可注入性:通过元数据检查目标类是否标记为可注入。
  3. 获取构造函数参数类型:通过元数据获取构造函数参数类型,并递归获取这些参数的实例。
  4. 创建实例:使用获取的参数实例创建目标类实例,并存储到容器中。
  5. 返回实例:返回目标类的实例。

通过这种方式,可以实现简单的依赖注入,使得类之间的依赖关系更加清晰和易于管理。

依赖注入(Dependency Injection, DI)是一个设计模式,通过将对象的创建和对象之间的依赖关系管理从代码中剥离出来,转移到一个独立的依赖注入容器中,以提高代码的可维护性、可测试性和灵活性。装饰器在依赖注入模式中起到了标记和配置依赖关系的作用。以下是依赖注入及其配套装饰器解决的问题和优势:

1. 解耦

依赖注入将类的实例化和依赖管理移到外部容器,使得类不再直接依赖具体的实现,而是通过容器注入。这样做使得代码更加解耦,降低了模块之间的耦合度。

示例

typescript
class UserService {
  getUser() {
    return 'User data';
  }
}

class UserController {
  constructor(private userService: UserService) {}

  getUserData() {
    return this.userService.getUser();
  }
}

// 依赖注入方式
const userService = new UserService();
const userController = new UserController(userService);

2. 提高可测试性

通过依赖注入,可以方便地替换依赖项,从而进行单元测试。可以在测试中注入模拟对象或假对象,而不需要修改实际代码。

示例

typescript
class MockUserService {
  getUser() {
    return 'Mock user data';
  }
}

const mockUserService = new MockUserService();
const userController = new UserController(mockUserService);
console.log(userController.getUserData()); // 输出 'Mock user data'

3. 促进单一职责原则

依赖注入容器负责创建和管理对象的生命周期,类只需关注自身的职责,不需要负责依赖项的创建和管理。这符合单一职责原则,增强了代码的可维护性。

4. 方便扩展和维护

通过依赖注入,可以轻松地更换依赖项的实现而不影响使用这些依赖项的代码。例如,可以将 UserService 换成 AdvancedUserService 而不需要修改 UserController

示例

typescript
class AdvancedUserService extends UserService {
  getUser() {
    return 'Advanced user data';
  }
}

// 使用新的实现
const advancedUserService = new AdvancedUserService();
const userController = new UserController(advancedUserService);
console.log(userController.getUserData()); // 输出 'Advanced user data'

5. 集中管理配置

通过依赖注入容器,可以在一个地方集中管理所有依赖项的配置,便于统一管理和调整。

装饰器的作用

装饰器在依赖注入模式中起到标记和配置的作用。以下是装饰器在依赖注入中的具体作用:

  1. 标记类为可注入:通过 @injectable 装饰器标记某个类为可注入,这样依赖注入容器就能识别并管理它。
typescript
function injectable(target: any) {
  Reflect.defineMetadata('injectable', true, target);
}

@injectable
class UserService {
  // 服务逻辑
}
  1. 标记需要注入的依赖项:通过 @inject 装饰器标记构造函数参数为需要注入的依赖项,容器在实例化时会自动注入相应的依赖。
typescript
function inject(target: any, propertyKey: string, parameterIndex: number) {
  const existingParameters = Reflect.getOwnMetadata('design:paramtypes', target, propertyKey) || [];
  existingParameters[parameterIndex] = true;
  Reflect.defineMetadata('design:paramtypes', existingParameters, target, propertyKey);
}

class UserController {
  constructor(@inject private userService: UserService) {}
}

综合示例

typescript
import 'reflect-metadata';

// 装饰器定义
function injectable(target: any) {
  Reflect.defineMetadata('injectable', true, target);
}

function inject(target: any, propertyKey: string, parameterIndex: number) {
  const existingParameters = Reflect.getOwnMetadata('design:paramtypes', target, propertyKey) || [];
  existingParameters[parameterIndex] = true;
  Reflect.defineMetadata('design:paramtypes', existingParameters, target, propertyKey);
}

// 依赖注入容器
class Container {
  private static instances = new Map();

  static get<T>(target: any): T {
    if (this.instances.has(target)) {
      return this.instances.get(target);
    }

    const injectable = Reflect.getMetadata('injectable', target);
    if (!injectable) {
      throw new Error(`Cannot inject ${target.name}. It is not marked as injectable.`);
    }

    const paramTypes = Reflect.getMetadata('design:paramtypes', target) || [];
    const params = paramTypes.map((param: any) => this.get(param));

    const instance = new target(...params);
    this.instances.set(target, instance);
    return instance;
  }
}

// 使用装饰器标记类
@injectable
class UserService {
  getUser() {
    return 'User data';
  }
}

@injectable
class UserController {
  constructor(@inject private userService: UserService) {}

  getUserData() {
    return this.userService.getUser();
  }
}

// 从容器中获取实例
const userController = Container.get(UserController);
console.log(userController.getUserData()); // 输出 'User data'

通过依赖注入和装饰器,代码变得更加模块化、易于测试和维护,同时增强了扩展性和配置的集中管理能力。这就是依赖注入及其装饰器的主要优势和解决的问题。

Released under the MIT License.