What is Dependency injection
Dependency injection is defined as the dependencies between components is determined by container at runtime, said that by the container dynamic image will be a dependency injection into components in object-oriented programming, we often deal with problem is decoupled, inversion of control (IoC) is commonly used, the design principles of object-oriented programming, including dependency injection is the realization of inversion of control is the most commonly used. The target solution is that the current class is not responsible for creating and initializing instances of dependent classes.
What is Dependency
Dependencies are A common phenomenon in programs, assuming that A and B are coupled to C dependencies, dependencies are everywhere in OOP programming. Dependencies can take many forms, such as a class sending a message to another class, a class being a member of another class, or a class being an argument to another class.
class A {}
class B {
classA: A;
constructor() {
this.classA = newA(); }}class C {
classA: A;
classB: B;
constructor() {
this.classA = new A();
this.classB = newB(); }}Copy the code
When is use Dependency injection
Eg: The user invokes the API layer to print logs
- LoggerService is dependent on ApiService and UserService
- ApiService is dependent on 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()
Copy the code
Existing problems
- Unit tests are difficult to write
- Components are difficult to reuse and maintain, and the scalability is low
UserService
Should not carryApiService
andLoggerService
Instance creation.
How to solve
With dependency injection, The UserService is not responsible for the creation and destruction of dependent classes, but is injected externally through API and Logger objects. There are three common dependency injection methods, and constructor injection is used as an example in this paper.
const apiService = Injector.resolve < ApiService > ApiService;
const userService = Injector.resolve < UserService > UserService;
// returns an instance of , with all injected dependencies
Copy the code
Implement simply Dependency injection
Preliminary knowledge
- ES6 uses relatively few features in its normal business: Reflect, Proxy, Decorator, Map, Symbol
- Understand Dependency Injection, ES/TS decorator
- Learn more about typescript-Reflect Metadata
Reflect
Introduction to the
Proxy and Reflect are apis introduced by ES6 to operate objects. Reflect’s API and Proxy API correspond one by one, and some object operations can be implemented functionally. In addition, using reflect-metadata allows Reflect to support metaprogramming
Type of access
- Type metadata: design:type
- Parameter type metadata: Design: Paramtypes
- Function returns value type metadata: design: returnType
Reflect.defineMetaData(metadataKey, metadataValue, target) // Define metadata on the class
Reflect.getMetaData("design:type", target, propertyKey); // Returns the class decorator property type
Reflect.getMetaData("design:paramtypes", target, propertyKey); // Returns the class decorator parameter type
Reflect.getMetaData("design:returntype", target, propertyKey); // The return class is decorated with the function return value type
Copy the code
Decorators
function funcDecorator(target, name, descriptor) {
// Target indicates that the class's prototype name is the function name descriptor
let originalMethod = descriptor.value;
descriptor.value = function () {
console.log("I am the decorator logic of Func.");
return originalMethod.apply(this.arguments);
};
return descriptor;
}
class Button {
@funcDecorator
onClick() {
console.log("I am the original logic of Func"); }}Copy the code
Reflect and Decorators
const Injector = (): ClassDecorator= > {
// es7 decorator
return (target, key, descriptor) = > {
console.log(Reflect.getMetadata("design:paramtypes", target));
// [apiService, loggerService]
};
};
@Injector(a)class userService {
constructor(api: ApiService, logger: LoggerService){}}Copy the code
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 newtarget(... injections); }};Copy the code
Only the core part of dependency extraction is implemented. Another part of dependency injection is related to Container storage.
Resolve Dependency
@Service(a)class LoggerService {
/ /...
}
@Service(a)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()
Copy the code
Implement simply Dependency injection with container
APIs of InversifyJS with TypeScript
Using the step
- Step 1: Declare the interface and its type
- Step 2: Declare dependencies using @Injectable & @Inject decorators
- Step 3: Create and configure a Container
- Step 4: Parse and extract dependencies
The sample
Declare interface and type:
export interface ILoggerService {}
export interface IApiService {}
export interface IUserService {}
export default TYPES = {
// Unique dependent identifiers. It is recommended to use symbol. for instead of class identifiers
ILoggerService: Symbol.for("ILoggerService"),
IApiService: Symbol.for("IApiService"),
IUserService: Symbol.for("IUserService"),};Copy the code
Declare dependencies:
import 'reflect-metadata'
import { injectable, inject } from 'inversify'
@injectable(a)export class LoggerService implements ILoggerService{
/ /...
}
@injectable(a)export class ApiService implements IApiService{
protected _logger: LoggerService
constructor (
private @inject(TYPES.ILoggerService) logger: LoggerService
) {
this._logger = logger
}
}
Copy the code
Property injection can also be used instead of constructor injection so that constructors are not declared.
@injectable(a)export class ApiService implements IApiService {
@inject(TYPES.ILoggerService) private _logger: LoggerService;
}
Copy the code
@injectable(a)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
}
}
Copy the code
Create and configure a Container
.const DIContainer = new container()
DIContainer.bind<ApiService>(TYPES.IApiService).toSelf()
DIContainer.bind<LoggerService>(TYPES.ILoggerService).toSelf()
Copy the code
Analytical dependence
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();
Copy the code
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(a)class Dom {
public _domUi: DomUi;
constructor(@inject(DomUi) domUi: DomUi) {
this._domUi = domUi; }}@injectable(a)class DomUi {
public _dom;
constructor(@inject(Dom) dom: Dom) {
this._dom = dom; }}@injectable(a)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!
Copy the code
InversifyJS recommends using symboy. for to generate dependent unique identifiers
FrameWorks
Dependency injection (DI) is usually implemented by third-party frameworks, which consider cyclic dependencies, error handling, container storage, etc.
- Tsyringe:github.com/microsoft/t…
Practice: github.com/DTStack/mol…
- InversifyJS: github.com/inversify/I…
Practice: codesandbox. IO/s/a lot/in…