Concept is clear

Inversion of Control (IoC) is a design principle used in object-oriented programming to reduce coupling between computer code. One of the most common is called Dependency Injection, or DI, and another is called Dependency Lookup

Inversion of control is an idea, a design principle, and dependency injection is an “implementation pattern” of this idea. Dependency injection can also be implemented differently in different languages and scenarios.

Inversion of control

Inversion of control has multiple semantics

  1. Inversion of control, in which you hand over control of the execution of part of your program to a third party. That is, the method of program invocation, time, times, etc., is not “controlled” by the defining party.

    An example of “trust issues” in callback functions is mentioned in JavaScript you Don’t Know (Middle Volume) 2.3

    analytics.trackPurchase(purchaseData, function(){ chargeCreditCard(); displayThankyouPage(); });Copy the code

    The author of JavaScript you Don’t Know argues that the way callback functions are executed leads to “inversion of control” (where third-party programs have control over how callback functions are executed). And called this “reversal of control” “harmful.” For example, in the example above, the payment callback code was executed five times or not at all (such as a third party program that failed and terminated).

    We call this an inversion of control, where you hand over execution control of a portion of your program to a third party. There is an “undefined contract” between your code and third-party tools (a set of things you want someone to maintain).

    The biggest problem with callbacks is inversion of control, which leads to a complete break in the chain of trust.

    This is essentially an asynchronous programming problem, and the book lists two ways to optimize this situation. One is the Error First pattern common in Nodejs. One of the arguments to the callback is error.

    dosomethingSync(function(error,... args) {
      if(error){ ... }... });Copy the code

    This allows handling of uncontrolled error situations in third-party programs.

    The second is to use promises, which is an optimization of one. There is also inversion of control. Instead of controlling the invocation, the third-party program maintains the promise’s state, with one and only resolution state (either complete or reject). But the consumer doesn’t control the call to the callback function, either. Promise does. This is a “clearly expressed contract”.

  2. The second kind of meaning is the most widely discussed and is used in encyclopedic entries.

    “Where has control been reversed?” .

    Martin Fowler concludes that it is the acquisition of dependent objects that is reversed. That is, the caller does not “control” which specific instance object is called.

    Because most applications are made up of two or more classes that work with each other to implement business logic. This makes each object need to get a reference to the object it works with (that is, the object it depends on). If this fetching process is self-contained, this results in highly coupled code that is difficult to maintain and debug.

    Class A uses the object B of Class B. In general, you need to explicitly new an object of B in the code of A.

    With dependency injection, A’s code only needs to define A private B object. Instead of obtaining the object directly, it uses the relevant container controller to externally new the B object and inject it into A reference in class A. The method and state of the object are specified by the configuration file (such as XML).

    The so-called “reverse” dependency object acquisition refers to: the actual object of the dependency is not specified by the developer when writing the code; It is determined by the runtime framework at run time. In pseudocode, it looks something like this:

    // framework.js
    const fruitMap = new Map(a);export function inject(instance) {
      // do inject
      instance.afterInjected();
    }
    export function map(key, value) {}// tropical.js
    import framework from './framework';
    
    export default class Tropical(a){
      construct() {
        this.inject = ['fruit'];
        framework.inject(this);
      }
    
      afterInjected(){}}// index.js
    import framework from './framework';
    
    // Crude dependency injection
    if ( // some logic) {
      framework.map('fruit'.new Fruit('banana'));
    } else ( // Another logic) {
      framework.map('fruit'.new Fruit('watermelon'));
    }
    const tropical = new Tropical();
    Copy the code

    Inversion of control, as mentioned below, refers to semantic 2.

Dependency injection

The so-called dependency injection, in a simple way, is to “inject” the dependency of the module that the high-level module depends on into the module by passing parameters. Reference from IoC philosophy in the front end.

Give the caller what it needs. A dependency is something that can be called by a method. In the dependency injection form, the caller no longer refers to “dependency” directly, but “injection” instead. “Injection” refers to the process of passing “dependencies” to the caller. After injection, the caller will invoke the dependency. Passing dependencies to the caller, rather than letting the caller get them directly, is a fundamental requirement of the design.

Dependency injection is the most common technique for inversion of control.

// app.js
class App {
    static modules = []
    constructor(options) {
        this.options = options;
        this.init();
    }
    init() {
        window.addEventListener('DOMContentLoaded'.() = > {
            this.initModules();
            this.options.onReady(this);
        });
    }
    static use(module) {
        Array.isArray(module)?module.map(item= > App.use(item)) : App.modules.push(module);
    }
    initModules() {
        App.modules.map(module= > module.init && typeof module.init == 'function' && module.init(this)); }}// modules/Router.js
import Router from 'path/to/Router';
export default {
    init(app) {
        app.router = new Router(app.options.router);
        app.router.to('home'); }};// modules/Track.js
import Track from 'path/to/Track';
export default {
    init(app) {
        app.track = newTrack(app.options.track); app.track.tracking(); }};// index.js
import App from './app.js';
import Router from './modules/Router';
import Track from './modules/Track';

App.use([Router, Track]);

new App({
    router: {
        mode: 'history',},track: {
        // ...
    },
    onReady(app) {
        // app.options ...}});Copy the code

Passing a dependency through an argument is a form of dependency injection, and callbacks are certainly a form of dependency injection. The substitution of interface methods through decorators is also a dependency injection. It’s just that some of them are more sophisticated, more advanced, more expansive. Specific implementation fits the demand is good.

Dependency injection is implemented in the following ways:

  • Interface-based. Implements specific interfaces for external containers to inject objects of dependent types.
  • Based on set method. Implement a public set method for a specific property to let the external container call in an object of the dependent type.
  • Based on constructors. Implements a constructor for a particular parameter that passes in an object of the dependent type when creating a new object.
  • Based on annotations. Java-based annotations, such as “@autowired” in front of private variables.

Dependency injection is not necessarily for inversion of control. Examples are Provide/Inject in vue, Provider/Consumer in React, and AngularJS. Dependency injection follows the dependency inversion principle and the single responsibility principle.

Here’s another word, dependency inversion.

Dependency inversion principle (Dependency inversion principle)

Dependency inversion and inversion of control, at first glance, are too similar in meaning to the second semantics mentioned above. Wikipedia – Dependency inversion

In the object-oriented programming world, Dependency Inversion principle (DIP) refers to a specific form of decoupling (traditional dependencies are created at a high level, while specific policy Settings are applied at a low level), By making the high-level modules independent of the low-level modules’ implementation details, the dependencies are reversed (reversed), thus making the low-level modules dependent on the high-level modules’ requirements abstraction.

The principle provides that:

  1. High-level modules should not depend on low-level modules; both should depend on abstract interfaces.
  2. Abstract interfaces should not depend on concrete implementations. The concrete implementation, on the other hand, should depend on the requirements of the abstract interface (this should be understood from a design implementation perspective, i.e. the requirements determine the implementation).

Personal understanding: Inversion of dependency has a broader meaning than inversion of control. Inversion of control is an idea in object-oriented programming that injects an instance of an object. Inversion of control also stems from dependency inversion.

reference

  1. IoC philosophy in the front end
  2. The front end understands dependency injection (inversion of control), this article is not completely accurate, see comments section for top comments.
  3. Wikipedia – Dependency inversion
  4. Wikipedia – Inversion of control
  5. Wikipedia – Dependency injection
  6. JavaScript You Didn’t Know By Volume