聊聊 IOC 中依赖注入那些事 Dependency inject

发布于 2023-09-10 10:56:06 字数 9175 浏览 57 评论 0

What is Dependency injection

依赖注入定义为组件之间依赖关系由容器在运行期决定,形象的说即由容器动态的将某个依赖关系注入到组件之中在面向对象编程中,我们经常处理的问题就是解耦,控制反转(IoC) 就是常用的面向对象编程的设计原则,其中依赖注入是控制反转最常用的实现。目标解决当前类不负责被依赖类实例的创建和初始化。

What is Dependency

依赖是程序中常见的现象,假设有 A 和 B 都被 C 耦合依赖着,在 OOP 编程中依赖无处不在。依赖形式有多种表现形式,比如一个类向另一个类发消息,一个类是另一个类的成员,一个类是另一个类的参数。

class A {}

class B {
  classA: A;
  constructor() {
    this.classA = new A();
  }
}

class C {
  classA: A;
  classB: B;
  constructor() {
    this.classA = new A();
    this.classB = new B();
  }
}​

When is use Dependency injection

eg: 以用户调用 API 层打印日志来说明

  • LoggerService 被 ApiService 和 UserService 所依赖
  • ApiService 被 UserService 所依赖
class LoggerService {
    constructor() {
    }
    log(args) {
        console.log(args)
    }
}

class ApiService {
    constructor (
        private readonly logger: LoggerService
) {
        this.logger.log('api constructor')
    }

    public async getMydata () {
        return { name: 'mumiao', hobby: 'focusing in web'}
    }
}

class UserService {
    constructor (
        private readonly api: ApiService,
        private readonly logger: LoggerService
) {
        this.logger.log('user constructor')
    }

    async getMyhobby () {
        const { hobby } = await this.api.getMydata()
        return hobby
    }
}

async function Main {
    const loggerService = new LoggerService()
    const apiService = new ApiService(loggerService)
    const userService = new UserService(loggerService, userService)
    console.log('my hobby is', await userService.getMyhobby())
}

Main()

1、存在的问题

  • Unit tests 很难写
  • 组件不易复用和维护,可扩展性比较低
  • UserService 不应该承载 ApiService 和 LoggerService 实例的创建。

2、如何解决

采用依赖注入,UserService 不负责被依赖类的创建和销毁,而是通过外部传入 api 和 logger 对象的方式注入。常见依赖注入方式有三种,本文主要以构造器注入为例解释。

const apiService = Injector.resolve < ApiService > ApiService;
const userService = Injector.resolve < UserService > UserService;
// returns an instance of , with all injected dependencies​

Implement simply Dependency injection

1、预备知识

  • ES6 的平时业务中相对使用较少的特性:Reflect、Proxy、Decorator、Map、Symbol
  • 了解 Dependency injection,ES/TS 装饰器
  • 深入理解 TypeScript - Reflect Metadata

1)Reflect

简介

Proxy 与 Reflect 是 ES6 为了操作对象引入的 API,Reflect 的 API 和 Proxy 的 API 一一对应,并且可以函数式的实现一些对象操作。

另外,使用 reflect-metadata 可以让 Reflect 支持元编程。

类型获取

  • 类型元数据:design:type
  • 参数类型元数据:design:paramtypes
  • 函数返回值类型元数据:design:returntype
Reflect.defineMetaData(metadataKey, metadataValue, target) // 在类上定义元数据
Reflect.getMetaData("design:type", target, propertyKey); //返回类被装饰属性类型
Reflect.getMetaData("design:paramtypes", target, propertyKey); //返回类被装饰参数类型
Reflect.getMetaData("design:returntype", target, propertyKey); // 返回类被装饰函数返回值类型
2)Decorators
function funcDecorator(target, name, descriptor) {
  // target 指 类的 prototype name 是函数名 descriptor 是属性描述符
  let originalMethod = descriptor.value;
  descriptor.value = function () {
    console.log("我是 Func 的装饰器逻辑");
    return originalMethod.apply(this, arguments);
  };
  return descriptor;
}

class Button {
  @funcDecorator
  onClick() {
    console.log("我是 Func 的原有逻辑");
  }
}

Reflect and Decorators

const Injector = (): ClassDecorator => {
  // es7 decorator
  return (target, key, descriptor) => {
    console.log(Reflect.getMetadata("design:paramtypes", target));
    // [apiService, loggerService]
  };
};

@Injector()
class userService {
  constructor(api: ApiService, logger: LoggerService) {}
}​

3)Implement simply Dependency injection

// interface.ts

type Type<T = any> = new (...args: any[]) => T;
export type GenericClassDecorator<T> = (target: T) => void;

// ServiceDecorator.ts

const Service = (): GenericClassDecorator<Type<object>> => {
  return (target: Type<object>) => {};
};

// Injector.ts
export const Injector = {
  // resolving instances
  resolve<T>(target: Type<any>): T {
    // resolved injections from the Injector
    let injections = Reflect.getMetadata("design:paramtypes", target) || [],
      injections = injections.map((inject) => Injector.resolve<any>(inject));

    return new target(...injections);
  },
};​

只实现了依赖提取的核心部分,依赖注入还有一个部分是 Container 容器存储相关。

Resolve Dependency

@Service()
class LoggerService {
//...
}

@Service()
class ApiService {
    constructor (
        private readonly logger: LoggerService
) {
    }
}

@Service
class UserService {
    constructor (
        private readonly api: ApiService,
        private readonly logger: LoggerService
) {
    }
}

async function Main {
    // jnject dependencies
   const apiService = Injector.resolve<ApiService>(ApiService);
   const userService = Injector.resolve<UserService>(UserService);
   console.log('my hobby is', await userService.getMyhobby())
}

Main()​

4)Implement simply Dependency injection with container

APIs of InversifyJS with TypeScript

1、使用步骤

  • Step 1: 声明接口及类型
  • Step 2: 声明依赖使用@ injectable & @ inject decorators
  • Step 3: 创建并配置一个 Container
  • Step 4: 解析并提取依赖

2、示例

声明接口及类型:

export interface ILoggerService {}
export interface IApiService {}
export interface IUserService {}

export default TYPES = {
  // 唯一依赖标识,建议使用 Symbol.for 替换类作为标识符
  ILoggerService: Symbol.for("ILoggerService"),
  IApiService: Symbol.for("IApiService"),
  IUserService: Symbol.for("IUserService"),
};​

声明依赖:

import 'reflect-metadata'
import { injectable, inject } from 'inversify'

@injectable()
export class LoggerService implements ILoggerService{
//...
}

@injectable()
export class ApiService implements IApiService{
    protected _logger: LoggerService
    constructor (
        private @inject(TYPES.ILoggerService) logger: LoggerService
) {
        this._logger = logger
    }
}​

也可以使用 property injection 代替 constructor injection ,这样就不用声明构造函数。

@injectable()
export class ApiService implements IApiService {
  @inject(TYPES.ILoggerService) private _logger: LoggerService;
}

@injectable()
export class UserService implements IUserService {
    protected _api: ApiService;
    protected _logger: LoggerService;

    constructor (
        private readonly @inject(TYPES.IApiService) api: ApiService,
        private readonly @inject(TYPES.ILoggerService) logger: LoggerService
) {
        this._api = api
        this._logger = logger
    }
}​

创建并配置一个 Container

...
const DIContainer = new container()
DIContainer.bind<ApiService>(TYPES.IApiService).toSelf()
DIContainer.bind<LoggerService>(TYPES.ILoggerService).toSelf()​

解析依赖

import "reflect-matadata";
import { UserService } from "./services";
import DIContainer from "./container";

async function Main() {
  const userService: UserService = DIContainer.resolve<UserService>(
    UserService
  );
  console.log("my hobby is", await userService.getMyhobby());
}

Main();​

Classes as identifiers and circular dependencies

An exception:

Error: Missing required @Inject or @multiinject annotation in: argument 0 in class Dom.

import "reflect-metadata";
import { injectable } from "inversify";

@injectable()
class Dom {
  public _domUi: DomUi;
  constructor(@inject(DomUi) domUi: DomUi) {
    this._domUi = domUi;
  }
}

@injectable()
class DomUi {
  public _dom;
  constructor(@inject(Dom) dom: Dom) {
    this._dom = dom;
  }
}

@injectable()
class Test {
  public _dom;
  constructor(@inject(Dom) dom: Dom) {
    this._dom = dom;
  }
}

container.bind<Dom>(Dom).toSelf();
container.bind<DomUi>(DomUi).toSelf();
const dom = container.resolve(Test); // Error!​

主要原因:decorator 被调用时,类还没有声明,导致 inject(undefined),InversifyJS 推荐使用 Symboy.for 生成依赖唯一标识符。

FrameWorks

依赖注入一般都借助第三方框架来实现,实现需要考虑循环依赖,错误处理,容器存储等。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

听风吹

暂无简介

文章
评论
29 人气
更多

推荐作者

櫻之舞

文章 0 评论 0

弥枳

文章 0 评论 0

m2429

文章 0 评论 0

野却迷人

文章 0 评论 0

我怀念的。

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文