preface

When it comes to DI, IoC (Inversion of Control) is an idea, not a technology, but an important OOP principle that guides the design of loose-coupled programs. In general, we actively create dependent objects within classes, which leads to high coupling between classes and is difficult to test. With IoC, the control of creating and searching dependent objects is married to the container, and the container performs such tasks as combining objects. Therefore, the coupling degree between objects is loose, which is conducive to functional reuse and keeps the system flexible.

WHAT

So what is DI, and how does IoC relate to DI (dependency injection)? This concept was put forward by Martin Fowler, a master of physiologic objects, in 2004, just to describe IoC more clearly, and can also be understood as a more reasonable realization of IoC. So they’re essentially the same thing. The purpose of dependency injection is to improve the reuse frequency of components and provide a flexible and extensible platform for the system, rather than to bring more functions to the system. With dependency injection, you can specify the target object and do your business logic without any code with simple configuration, regardless of who creates and destroys the object.

HOW

In the actual page development process, we will create many services, such as cache, user, logger, and so on. We will use these services:

// In Apage import logger from '... ' import user from '... '// import logger from Bpage... ' import user from '... 'Copy the code

It’s pretty good for our programming habits, but there’s a big problem with this, because it’s very coupled. This problem is analyzed and resolved through a sample refactoring process.

1. Page A needs A log service to be reported to LogCenter. We may code it as follows:

class LoggerSvc {
	report(){console.log('logcenter report successfull~')}
}

class APage {
	logger = null
  constructor(){
  	this.logger = new LoggerSvc()
  }
  report(){
  	this.logger.report()
  }
}
const page  = new APage()
page.report()
  
// PRINT: logcenter report successfull~
Copy the code

2. As the system iterates, LoggerSvc will extend new reporting methods, such as Logan reporting methods, so we can use OO abstraction:

interface Logger{ report:() => void } class LogCenter implements Logger{ report(){ console.log('logcenter report successfull~')} } class LoggerSvc { private logger:Logger report(){this.logger.report()} constructor(){ this.logger= new  LogCenter() } } class APage { loggerSvc = null constructor(){ this.loggerSvc = new LoggerSvc() } report(){ this.loggerSvc.report() } } const page = new APage() page.report() // PRINT: logcenter report successfull~Copy the code

3. Through abstraction, you need to extend a Logan class that implements the Logger interface and modify LoggerSvc dependent Logger objects into Logan instances.

. Logger{report(){console.log(' Logan Report SuccessFull ~')}}... Class LoggerSvc {private Logger :Logger report(){this.logger.report()} constructor(){this.logger = new Logan() } } ... const page = new APage() page.report() //PRINT: logan report successfull~Copy the code

You’ll notice that switching LoggerSvc is done inside LoggerSvc, causing LoggerSvc to be too coupled to Logan. Is there a better way?

In line with the IoC thinking mentioned above, externalize service dependencies. All you need to do is inject the instance where it’s used.

. class LoggerSvc { private logger:Logger report(){this.logger.report()} constructor(logger:Logger){ this.logger= logger }  } class APage { loggerSvc = null constructor(loggerSvc:LoggerSvc){ this.loggerSvc = loggerSvc } .. }... Const logger =new LogCenter() const loggerSvc =new LoggerSvc(logger) const page = new APage(loggerSvc) page.report() //PRINT: logcenter report successfull~ // or //PRINT: logan report successfull~Copy the code

But we still need to manually create and manage dependencies. Can we automate object creation and dependency management? That’s where DI comes in. My side simulates the implementation in the most familiar Angular way. Injectable was used to register dependent services and Injectable was used to create and manage dependent containers. Is the manual creation and management of the way missing.

import { Injectable, Injector } from '@angular/core' abstract class Logger { report(): void { } } @Injectable() class LogCenter extends Logger { report() { console.log('logcenter report') } constructor() { super() } } @Injectable() class Logan extends Logger { report() { console.log('logan report') } constructor() { super() } } @Injectable() class LoggerService { logger: Logger report() { this.logger.report() } constructor(logger: Logger) { this.logger = logger } } @Injectable() class APage { constructor(private loggerService: LoggerService) {} report () {this. LoggerService. Report ()}} / / create the container const inject = Injector. Create ({will: [{ provide: Logger, useClass: LogCenter, deps: [] }, { provide: LoggerService, deps: [Logger] }, { provide: APage, deps: [LoggerService] }] }) const page = inject.get(APage) page.report() //PRINT: logcenter report successfull~Copy the code

You only need to modify the dependency configuration table to quickly complete the switchover.

. Const inject = Injector. Create ({will: [{/ / useClass into Logan can dojo.provide: Logger, useClass: Logan, deps: [] }, { provide: LoggerService, deps: [Logger] }, { provide: APage, deps: [LoggerService] }] }) ... //PRINT: logan report successfull~Copy the code

Angular DI is too strong with frameworks. Introduce a generic DI library, InversifyJS, that transforms the above example.

import 'reflect-metadata' import { Container, injectable, inject, Interfaces} from 'inversify' // const TYEPS = {LogCenter: symbol. for('LogCenter'), Logan: Symbol.for('Logan'), LoggerService: Symbol.for('LoggerService'), APage: Symbol.for('APage'), FactoryLogger: 'Factory<Logger>' } interface Logger { report: () => void } @injectable() class LogCenter implements Logger { report() { console.log('logcenter report') } } @injectable() class Logan implements Logger { report() { console.log('logan report') } } @injectable() class LoggerService { logger: Logger Report () {this.logger.report()} // Inject the FactoryLogger factory with the constructor(@inject(tyeps.factoryLogger) constructor(@inject(tyeps.factoryLogger)) loggerFactory: () => Logger) { this.logger = loggerFactory() } } @injectable() class APage { constructor(@inject(TYEPS.LoggerService) private loggerService: {{} LoggerService) report () this. LoggerService. Report ()}} / / create the container const container = new container () Container. Bind <Logger>(tyeps.logCenter). To (LogCenter) container. Bind <Logger>(tyeps.logan). To (Logan) // Is a FactoryLogger Bind <interfaces.Factory<Logger>>(tyeps.factoryLogger). ToFactory <Logger>((context: Interfaces.context) => {return () => {// This place completes the switch // 1. Use LogCenter return context.container. Get <Logger>(tyeps.logCenter) // 2. Get <Logger>(tyeps.logan)}; }); container.bind<LoggerService>(TYEPS.LoggerService).to(LoggerService) container.bind<APage>(TYEPS.APage).to(APage) let page = container.get<APage>(TYEPS.APage) page.report()Copy the code

The last

An example of an optimized refactoring process will help you understand DI in a simple way. In essence, it is the transfer of control of the object. DI has been a common way to decouple many complex systems. I hope you can use it flexibly.

reference

www.zhihu.com/question/25…

www.yuque.com/qinming-qvk…