The concept of the IoC

IoC (Inversion of Control) is a design pattern based on dependency Inversion. It is interface oriented programming, rather than implementation oriented programming, that injects dependent low-level functional modules into higher-level modules

For example, the module that defines a person’s interests and hobbies is generally implemented in the module, such as playing basketball, swimming and traveling. However, one day the needs are suddenly changed and the person gets some new skills, such as playing piano and dancing. Then the main module can only be modified. This makes module expansion and maintenance very tedious and can not guarantee that the new function will not affect other functions, and it is easy to produce bugs

Use under the transformation of the Ioc, specific hobby into the underlying module And then according to need to rely on into high level modules, it is the inversion of control, high definition interface and low-level implementing an interface, when need to be modified and new lower function modules will not affect the upper They also won’t affect each other, between low coupling between modules

The specific implementation

Create an IoC container module CreateIoc

class CreateIoc {
    constructor() {
        this.container = new Map()}// Inject the module
    inject(. params) {
        this.handleInject(params, false)}// Injection module (singleton mode)
    injectSingleton(. params) {
        this.handleInject(params, true)}handleInject(params, singleton) {
        const [key, Fn] = params
        const callback = () = > new Fn()
        this.container.set(key, { callback, singleton }) 
    }
    
    // Initialize the usage module
    init(key) {
        const obj = this.container.get(key)
        if (obj) {
          if(obj.singleton && ! obj.instance) { obj.instance = obj.callback() } }else {
          console.error('Can't find it.'${key}"Module `)}}}Copy the code

Methods that inject low-level function modules into high-level modules and then invoke them when needed

class Basketball {
    constructor() {}
    play() { console.log('Go play basketball')}}class Swim {
    constructor() {}
    play() { console.log('Go swimming')}}const personIoc = new CreateIoc()

// dependency injection
personIoc.inject('playBasketball', PlayBasketball)
personIoc.inject('swim', Swim)

// Use only one module
const basketball = personIoc.init('playBasketball')
basketball.play()
Copy the code

Modify with TypeScript to add some static types to use

// Declare the container interface
interface IContainer {
  callback: () = > {}
  singleton: boolean
  instance?: {}
}

// Declare an interface that can be new instances and return the constructor type T
interface NewAble<T> {
  new(... args:any[]): T
}

// Parameter type (represented by a tuple when added to generics)
type TParams<T> = [key: string.Fn: NewAble<T>]
Copy the code

Modify the class CreateIoc

class CreateIoc {
  private container = new Map<string, IContainer<any> > ()/ /... Params expands the parameters into tuples
  publicinject<T>(... params: TParams<T>) {this.helpBind(params, false)}publicinjectSingleton<T>(... params: TParams<T>) {this.helpBind(params, true)}private handleInject<T>(params: TParams<T>, singleton: boolean) {
    const [key, Fn] = params
    const callback = <T>() = > new Fn()
    // use typeof Fn to extract the typeof the constructor
    const _instance: IContainer<typeof Fn> = { callback, singleton }
    this.container.set(key, _instance) 
  }
  
  public init<T>(key: string) {
    const obj = this.container.get(key)
    if(obj ! = =undefined) {
      if(obj.singleton && ! obj.instance) { obj.instance = obj.callback() }// obj.instance stores an instance of the injected module constructor
      returnitem.singleton ? <T>item.instance : <T>item? .callback() }else {
      console.error('Can't find it.'${key}"Module `)}}}Copy the code

When using:

// Create the class as an interface implementation
interface ISwim {
  play(str: string) :void
}
class Swim implements ISwim {
  constructor() {}
  public goto(place: string) :void {
    console.log(` to${place}Swimming `)}}const personIoc = new CreateIoc()
// Be sure to pass in the interface as a generic
personIoc.inject<ISwim>('swim', Swim)
const swim = personIoc.init<ISwim>('swim')
swim.goto('Open-air swimming pool')
Copy the code

The expected result is displayed successfully after the TS-node is run. ~~ NPM I ts-node -g ts-node xxx.ts

So just to conclude

IoC using dependency injection concept is to low-level module as parameters of on-demand into high-level modules, high-level modules by decoupling dependence on low-level module loader mechanism, and depend on low-level modules of abstract, low-level modules to the implementation of the abstract, and through the injector to dependency injection of high-level modules, it’s so good to keep the flexibility of business logic layer