Skip to content

依赖注入和控制反转(DI & IoC)

基本概念

控制反转(IoC - Inversion of Control)

控制反转是一种设计原则,指的是将对象的创建和管理权从程序代码中反转给外部容器。使用 IoC 之后,对象的创建和管理权被反转给了容器,程序不再主动负责创建对象,而是被动地接收容器注入的对象。

依赖注入(DI - Dependency Injection)

依赖注入是实现 IoC 的一种手段,通过 DI,我们将类的依赖项注入到类中,而不是在类里面实例化这些依赖。

传统方式 vs 依赖注入

传统方式(紧耦合)

typescript
// ❌ 传统方式 - 类直接创建依赖
class Engine {
  start() {
    console.log('Engine started');
  }
}

class Car {
  private engine: Engine;
  
  constructor() {
    this.engine = new Engine(); // 直接创建依赖
  }

  start() {
    this.engine.start();
  }
}

const car = new Car();
car.start();

问题分析

  • Car 类直接负责创建 Engine 实例
  • 高度耦合,难以测试
  • 无法轻松替换 Engine 的实现
  • 违反了依赖倒置原则

依赖注入方式(松耦合)

typescript
// ✅ 依赖注入 - 通过构造函数注入依赖
class Engine {
  start() {
    console.log('Engine started');
  }
}

class Car {
  private engine: Engine;
  
  constructor(engine: Engine) { // 依赖通过构造函数注入
    this.engine = engine;
  }

  start() {
    this.engine.start();
  }
}

// 外部创建依赖并注入
const engine = new Engine();
const car = new Car(engine);
car.start();

优势分析

  • Car 类不再负责创建 Engine 实例
  • 低耦合,易于测试
  • 可以轻松替换 Engine 的不同实现
  • 符合依赖倒置原则

元数据反射与装饰器

@Injectable 装饰器

typescript
import 'reflect-metadata';

function Injectable(target: any) {
  // 这里可以不用写任何代码,此装饰器不需要执行任何操作
  // 仅仅用于元数据的生成
}

@Injectable
class Oil {
  constructor(private num: number) {}
  
  start() {
    console.log(`start oil`, this.num);
  }
}

// 获取构造函数参数类型信息
const dependencies = Reflect.getMetadata('design:paramtypes', Oil);
console.log(dependencies); // [Number]

复杂依赖关系示例

typescript
@Injectable
class Oil {
  constructor(private num: number) {}
  
  start() {
    console.log(`start oil`, this.num);
  }
}

@Injectable
class Engine {
  constructor(private oil: Oil, private age: number) {}
  
  start() {
    this.oil.start();
    console.log(`Engine started`);
  }
}

@Injectable
class Car {
  constructor(private engine: Engine) {}
  
  drive() {
    this.engine.start();
    console.log('Car is running');
  }
}

自定义 IoC 容器实现

容器核心实现

typescript
// 定义一个依赖注入容器类
class DIContainer {
  // 存储所有服务的 Map 对象
  private services = new Map<string, any>();
  
  // 注册服务的方法,把服务的名称和实现类保存到 Map 中
  register<T>(name: string, Service: any) {
    this.services.set(name, Service);
  }
  
  // 解析服务的方法,根据名称返回服务的实例
  resolve<T>(name: string): T {
    const Service = this.services.get(name);
    
    if (Service instanceof Function) { // 类构造函数
      // 获取构造函数参数的类型数组
      const dependencies = Reflect.getMetadata('design:paramtypes', Service) ?? [];
      // 递归解析所有的依赖项
      const injections = dependencies.map(dependency => 
        this.resolve(dependency.name)
      );
      // 创建并返回实例
      return new Service(...injections);
      
    } else if (Service.useFactory) { // 工厂函数
      const { inject } = Service;
      return Service.useFactory(...inject);
      
    } else if (Service.useValue) { // 直接值
      return Service.useValue;
    }
    
    throw new Error(`Service ${name} not found`);
  }
}

容器使用示例

typescript
// 创建依赖注入容器实例
const container = new DIContainer();

// 注册 Oil 服务 - 使用工厂函数
container.register<Oil>('Oil', {
  provide: 'Oil',
  inject: [100],
  useFactory: (number: number) => {
    return new Oil(number);
  }
});

// 注册 Engine 服务 - 使用直接值
container.register<Engine>('Engine', {
  provide: 'Engine',
  useValue: new Engine(new Oil(200), 18)
});

// 注册 Car 服务 - 使用类构造函数
container.register<Car>('Car', Car);

// 解析并使用服务
const car = container.resolve<Car>('Car');
car.drive();

高级 IoC 容器实现

支持单例模式的容器

typescript
class AdvancedDIContainer {
  private services = new Map<string, any>();
  private instances = new Map<string, any>(); // 单例缓存
  
  register<T>(name: string, definition: ServiceDefinition) {
    this.services.set(name, definition);
  }
  
  resolve<T>(name: string): T {
    // 检查是否已有单例实例
    if (this.instances.has(name)) {
      return this.instances.get(name);
    }
    
    const service = this.services.get(name);
    if (!service) {
      throw new Error(`Service ${name} not found`);
    }
    
    let instance: T;
    
    if (service.useClass) {
      const dependencies = Reflect.getMetadata('design:paramtypes', service.useClass) ?? [];
      const injections = dependencies.map((dep: any) => {
        // 支持 token 注入
        const token = Reflect.getMetadata('inject:token', service.useClass, dependencies.indexOf(dep));
        return token ? this.resolve(token) : this.resolve(dep.name);
      });
      instance = new service.useClass(...injections);
      
    } else if (service.useFactory) {
      const injections = service.inject?.map((token: string) => this.resolve(token)) ?? [];
      instance = service.useFactory(...injections);
      
    } else if (service.useValue) {
      instance = service.useValue;
    } else {
      throw new Error(`Invalid service definition for ${name}`);
    }
    
    // 如果是单例,缓存实例
    if (service.singleton !== false) {
      this.instances.set(name, instance);
    }
    
    return instance;
  }
  
  // 清除单例缓存
  clearInstances() {
    this.instances.clear();
  }
}

// 服务定义接口
interface ServiceDefinition {
  useClass?: any;
  useFactory?: (...args: any[]) => any;
  useValue?: any;
  inject?: string[];
  singleton?: boolean;
}

支持装饰器的依赖注入

typescript
// 注入装饰器
function Inject(token: string) {
  return function (target: any, propertyKey: string | symbol | undefined, parameterIndex: number) {
    Reflect.defineMetadata('inject:token', token, target, parameterIndex);
  };
}

// 可注入装饰器
function Injectable(token?: string) {
  return function <T extends { new (...args: any[]): {} }>(constructor: T) {
    if (token) {
      Reflect.defineMetadata('service:token', token, constructor);
    }
    return constructor;
  };
}

// 使用示例
@Injectable('DatabaseConfig')
class DatabaseConfig {
  constructor(
    @Inject('DB_HOST') public host: string,
    @Inject('DB_PORT') public port: number
  ) {}
}

@Injectable()
class DatabaseService {
  constructor(
    @Inject('DatabaseConfig') private config: DatabaseConfig
  ) {}
  
  connect() {
    console.log(`Connecting to ${this.config.host}:${this.config.port}`);
  }
}

// 容器配置
const advancedContainer = new AdvancedDIContainer();

advancedContainer.register('DB_HOST', {
  useValue: 'localhost'
});

advancedContainer.register('DB_PORT', {
  useValue: 5432
});

advancedContainer.register('DatabaseConfig', {
  useClass: DatabaseConfig,
  inject: ['DB_HOST', 'DB_PORT']
});

advancedContainer.register('DatabaseService', {
  useClass: DatabaseService,
  inject: ['DatabaseConfig']
});

在 NestJS 中的应用

基础服务注入

typescript
// 服务定义
@Injectable()
export class UserRepository {
  async findById(id: string): Promise<User> {
    // 数据库查询逻辑
    return {} as User;
  }
  
  async save(user: User): Promise<User> {
    // 保存逻辑
    return user;
  }
}

@Injectable()
export class EmailService {
  async sendEmail(to: string, subject: string, body: string): Promise<void> {
    // 邮件发送逻辑
    console.log(`Sending email to ${to}: ${subject}`);
  }
}

@Injectable()
export class UserService {
  constructor(
    private readonly userRepository: UserRepository,
    private readonly emailService: EmailService
  ) {}
  
  async createUser(userData: CreateUserDto): Promise<User> {
    const user = await this.userRepository.save(userData as User);
    await this.emailService.sendEmail(
      user.email,
      'Welcome!',
      'Welcome to our platform!'
    );
    return user;
  }
}

自定义提供者

typescript
// 值提供者
const databaseConfig = {
  provide: 'DATABASE_CONFIG',
  useValue: {
    host: 'localhost',
    port: 5432,
    database: 'myapp'
  }
};

// 工厂提供者
const databaseConnectionFactory = {
  provide: 'DATABASE_CONNECTION',
  useFactory: (config: any) => {
    return createConnection(config);
  },
  inject: ['DATABASE_CONFIG']
};

// 类提供者
const userServiceProvider = {
  provide: 'USER_SERVICE',
  useClass: UserService
};

// 别名提供者
const userServiceAlias = {
  provide: 'IUserService',
  useExisting: 'USER_SERVICE'
};

@Module({
  providers: [
    UserRepository,
    EmailService,
    databaseConfig,
    databaseConnectionFactory,
    userServiceProvider,
    userServiceAlias,
    {
      provide: UserService,
      useClass: UserService
    }
  ],
  exports: [UserService, 'DATABASE_CONNECTION']
})
export class UserModule {}

条件注入和作用域

typescript
// 条件提供者
@Injectable()
export class DevelopmentLogger implements ILogger {
  log(message: string) {
    console.log(`[DEV] ${message}`);
  }
}

@Injectable()
export class ProductionLogger implements ILogger {
  log(message: string) {
    // 发送到日志服务
  }
}

const loggerProvider = {
  provide: 'LOGGER',
  useFactory: () => {
    return process.env.NODE_ENV === 'production'
      ? new ProductionLogger()
      : new DevelopmentLogger();
  }
};

// 作用域提供者
@Injectable({ scope: Scope.REQUEST })
export class RequestScopedService {
  private readonly id = Math.random();
  
  getId() {
    return this.id;
  }
}

@Injectable({ scope: Scope.TRANSIENT })
export class TransientService {
  private readonly id = Math.random();
  
  getId() {
    return this.id;
  }
}

循环依赖处理

typescript
// 使用 forwardRef 解决循环依赖
@Injectable()
export class UserService {
  constructor(
    @Inject(forwardRef(() => PostService))
    private postService: PostService
  ) {}
}

@Injectable()
export class PostService {
  constructor(
    @Inject(forwardRef(() => UserService))
    private userService: UserService
  ) {}
}

// 或者使用 ModuleRef 动态获取
@Injectable()
export class UserService {
  private postService: PostService;
  
  constructor(private moduleRef: ModuleRef) {}
  
  onModuleInit() {
    this.postService = this.moduleRef.get(PostService);
  }
}

依赖注入的优势

1. 松耦合

typescript
// 紧耦合示例
class OrderService {
  private emailService = new EmailService(); // 硬编码依赖
  
  processOrder(order: Order) {
    // 处理订单
    this.emailService.sendConfirmation(order.email);
  }
}

// 松耦合示例
class OrderService {
  constructor(private emailService: IEmailService) {} // 依赖抽象
  
  processOrder(order: Order) {
    // 处理订单
    this.emailService.sendConfirmation(order.email);
  }
}

2. 易于测试

typescript
describe('UserService', () => {
  let userService: UserService;
  let userRepository: jest.Mocked<UserRepository>;
  let emailService: jest.Mocked<EmailService>;

  beforeEach(async () => {
    const module: TestingModule = await Test.createTestingModule({
      providers: [
        UserService,
        {
          provide: UserRepository,
          useValue: {
            save: jest.fn(),
            findById: jest.fn()
          }
        },
        {
          provide: EmailService,
          useValue: {
            sendEmail: jest.fn()
          }
        }
      ]
    }).compile();

    userService = module.get<UserService>(UserService);
    userRepository = module.get(UserRepository);
    emailService = module.get(EmailService);
  });

  it('should create user and send email', async () => {
    const userData = { name: 'John', email: 'john@example.com' };
    const savedUser = { id: '1', ...userData };

    userRepository.save.mockResolvedValue(savedUser);

    const result = await userService.createUser(userData);

    expect(userRepository.save).toHaveBeenCalledWith(userData);
    expect(emailService.sendEmail).toHaveBeenCalledWith(
      'john@example.com',
      'Welcome!',
      'Welcome to our platform!'
    );
    expect(result).toEqual(savedUser);
  });
});

3. 配置灵活性

typescript
// 开发环境配置
const developmentProviders = [
  {
    provide: 'CACHE_SERVICE',
    useClass: InMemoryCache
  },
  {
    provide: 'LOGGER',
    useClass: ConsoleLogger
  }
];

// 生产环境配置
const productionProviders = [
  {
    provide: 'CACHE_SERVICE',
    useClass: RedisCache
  },
  {
    provide: 'LOGGER',
    useClass: FileLogger
  }
];

@Module({
  providers: [
    ...commonProviders,
    ...(process.env.NODE_ENV === 'production' 
      ? productionProviders 
      : developmentProviders)
  ]
})
export class AppModule {}

依赖注入模式

1. 构造函数注入(推荐)

typescript
@Injectable()
export class UserService {
  constructor(
    private readonly userRepository: UserRepository,
    private readonly emailService: EmailService
  ) {}
}

2. 属性注入

typescript
@Injectable()
export class UserService {
  @Inject(UserRepository)
  private readonly userRepository: UserRepository;
  
  @Inject(EmailService)
  private readonly emailService: EmailService;
}

3. 方法注入

typescript
@Injectable()
export class UserService {
  private userRepository: UserRepository;
  
  @Inject(UserRepository)
  setUserRepository(userRepository: UserRepository) {
    this.userRepository = userRepository;
  }
}

最佳实践

1. 依赖接口而非实现

typescript
// ❌ 依赖具体实现
@Injectable()
export class OrderService {
  constructor(private emailService: SMTPEmailService) {}
}

// ✅ 依赖抽象接口
@Injectable()
export class OrderService {
  constructor(
    @Inject('IEmailService') 
    private emailService: IEmailService
  ) {}
}

2. 使用工厂函数处理复杂创建逻辑

typescript
const databaseProviderFactory = {
  provide: 'DATABASE_CONNECTION',
  useFactory: async (configService: ConfigService) => {
    const config = configService.get('database');
    const connection = await createConnection(config);
    await connection.runMigrations();
    return connection;
  },
  inject: [ConfigService]
};

3. 合理使用作用域

typescript
// 单例 - 默认,适用于无状态服务
@Injectable()
export class UserService {}

// 请求作用域 - 适用于需要请求上下文的服务
@Injectable({ scope: Scope.REQUEST })
export class RequestContextService {}

// 瞬态 - 每次注入都创建新实例
@Injectable({ scope: Scope.TRANSIENT })
export class UtilityService {}

4. 模块化组织依赖

typescript
@Module({
  imports: [DatabaseModule, EmailModule],
  providers: [UserService, UserRepository],
  exports: [UserService]
})
export class UserModule {}

@Module({
  providers: [
    {
      provide: 'EMAIL_CONFIG',
      useValue: emailConfig
    },
    EmailService
  ],
  exports: [EmailService]
})
export class EmailModule {}

总结

依赖注入和控制反转是现代软件开发中的重要设计模式,它们:

  1. 提高代码质量:降低耦合度,提高内聚性
  2. 增强可测试性:便于单元测试和集成测试
  3. 提升灵活性:易于扩展和维护
  4. 支持配置化:不同环境使用不同实现
  5. 促进模块化:清晰的依赖关系和模块边界

在 NestJS 中,依赖注入是框架的核心特性,通过装饰器和元数据反射,提供了强大而灵活的依赖管理能力。正确使用依赖注入可以显著提高应用程序的质量和可维护性。

Released under the MIT License.