web-dev-qa-db-fra.com

Injection de dépendance JavaScript

Je suis nouveau sur JavaScript. Je me demande comment l'injection de dépendance est implémentée en JavaScript? J'ai cherché sur Internet mais je n'ai rien trouvé.

32
Levent Sezer
var Injector = {
   dependencies: {},
   add : function(qualifier, obj){
      this.dependencies[qualifier] = obj; 
   },
   get : function(func){
      var obj = new func;
      var dependencies = this.resolveDependencies(func);
      func.apply(obj, dependencies);
      return obj;
   },
   resolveDependencies : function(func) {
      var args = this.getArguments(func);
      var dependencies = [];
      for ( var i = 0; i < args.length; i++) {
         dependencies.Push(this.dependencies[args[i]]);
      }
      return dependencies;
   },
   getArguments : function(func) {
      //This regex is from require.js
      var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
      var args = func.toString().match(FN_ARGS)[1].split(',');
      return args;
   }
};

La première chose dont nous avons besoin d’une configuration est de fournir les dépendances nécessaires avec des qualificateurs. Pour ce faire, nous définissons un ensemble de dépendances en tant que dépendances dans la classe Injector. Nous utilisons un ensemble de dépendances en tant que conteneur, qui prend en charge nos instances d'objet mappées en qualificateurs. Afin d'ajouter une nouvelle instance avec un qualificatif à l'ensemble de dépendances, nous définissons une méthode add. Suite à cela, nous définissons la méthode get pour récupérer notre instance. Dans cette méthode, nous trouvons d’abord le tableau des arguments, puis mappons ces arguments à des dépendances. Après cela, nous construisons simplement l'objet avec nos dépendances et le retournons. Pour plus d'informations et d'exemples, veuillez consulter le post sur mon blog.

35
yusufaytas

Vous pouvez utiliser AngularJS comme exemple. Que ce soit une bonne chose, vous devez décider vous-même. J'ai écrit il y a une semaine un article sur la démistification de l'injection de dépendance dans AngularJS Ici vous pouvez lire le code de l'article:

// The following simplified code is partly taken from the AngularJS source code:
// https://github.com/angular/angular.js/blob/master/src/auto/injector.js#L63

function inject(fn, variablesToInject) {
    var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
    var FN_ARG_SPLIT = /,/;
    var FN_ARG = /^\s*(_?)(\S+?)\1\s*$/;
    var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;

    if (typeof fn === 'function' && fn.length) {
        var fnText = fn.toString(); // getting the source code of the function
        fnText = fnText.replace(STRIP_COMMENTS, ''); // stripping comments like function(/*string*/ a) {}

        var matches = fnText.match(FN_ARGS); // finding arguments
        var argNames = matches[1].split(FN_ARG_SPLIT); // finding each argument name

        var newArgs = [];
        for (var i = 0, l = argNames.length; i < l; i++) {
            var argName = argNames[i].trim();

            if (!variablesToInject.hasOwnProperty(argName)) {
                // the argument cannot be injected
                throw new Error("Unknown argument: '" + argName + "'. This cannot be injected.");
            }

            newArgs.Push(variablesToInject[argName]);
        }

        fn.apply(window, newArgs);
    }
}

function sum(x, y) {
    console.log(x + y);
}

inject(sum, {
    x: 5,
    y: 6
}); // should print 11

inject(sum, {
    x: 13,
    y: 45
}); // should print 58

inject(sum, {
    x: 33,
    z: 1 // we are missing 'y'
}); // should throw an error: Unknown argument: 'y'. This cannot be injected.
9
bdadam

Pour moi yusufaytas réponse était exactement ce dont j'avais besoin! Les seules caractéristiques manquantes étaient: 

  1. Obtenir une dépendance avec des paramètres personnalisés.
  2. Enregistrement de dépendances à l'aide de rappels.

Je voulais pouvoir faire quelque chose comme ça:

Injector.register('someDependency', function () {
        return new ConcreteDependency();
});

function SomeViewModel(userId, someDependency) {
    this.userId = userId;
    this.someDependency = someDependency;
}

var myVm = Injector.get(SomeViewModel, { "userId": "1234" });

Alors j'ai fini avec le code suivant:

var Injector = {

    factories = {},        
    singletons = {},

    register: function (key, factory) {
        this.factories[key] = factory;
    },

    registerSingle: function (key, instance) {
        this.singletons[key] = instance;
    },

    get: function (CTor, params) {            

        var dependencies = this.resolveDependencies(CTor, params);

        // a workaround to allow calling a constructor through .apply
        // see https://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
        function MiddlemanCTor() {
            CTor.apply(this, dependencies);
        }

        MiddlemanCTor.prototype = CTor.prototype;

        return new MiddlemanCTor();
    },

    resolveDependencies: function(CTor, params) {
        params = params || {};
        var args = this.getArguments(CTor);

        var dependencies = [];
        for (var i = 0; i < args.length; i++) {
            var paramName = args[i];
            var factory = this.factories[paramName];

            // resolve dependency using:
            // 1. parameters supplied by caller
            // 2. registered factories
            // 3. registered singletons
            var dependency = params[paramName] ||
                (typeof factory === "function" ? factory() : undefined) ||
                this.singletons[paramName];

            dependencies.Push(dependency);
        }
        return dependencies;
    }

    getArguments: func(func) {
        // Regex from require.js
        var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
        var args = func.toString().match(FN_ARGS)[1].split(',').map(function (str) {
            return str.trim();
        });
        return args;
    }
};

Mise à jour - 21.5.2018

J'utilise cette solution depuis quelques années maintenant. Lorsque j'ai déplacé ma base de code vers TypeScript, la solution a évolué pour prendre en charge TypeScript et JavaScript. Après un certain temps pendant lequel le code était en production, j'ai récemment (il y a deux jours) publié une bibliothèque basée sur cette solution. N'hésitez pas à y jeter un coup d'œil, à ouvrir des numéros, etc.

menthe poivrée

6
Alon Bar

Apprenons-le à faire un exemple du monde réel super simple :)

L'exemple de classe dont je vais parler ici est une Printer qui nécessite une driver pour imprimer quelque chose. J'ai démontré les avantages du modèle de conception d'injection de dépendance dans 4 étapes pour arriver à la meilleure solution à la fin.

Cas 1: aucune injection de dépendance utilisée:

class Printer {
   constructor () {
      this.lcd = '';
   }

   /* umm! Not so flexible! */
   print (text) {
     this.lcd = 'printing...';
     console.log (`This printer prints ${text}!`);
   }
}

// Usage:

var printer = new Printer ();
printer.print ('hello');

l'utilisation est simple, il est facile de créer une nouvelle imprimante de cette façon, mais cette imprimante n'est pas flexible.

Case2: résume les fonctionnalités de la méthode print dans une nouvelle classe appelée Driver:

class Printer {
  constructor () {
    this.lcd = '';
    this.driver = new Driver ();
  }

  print (text) {
    this.lcd = 'printing...';
    this.driver.driverPrint (text);
  }
}

class Driver {
  driverPrint (text) {
    console.log (`I will print the ${text}`);
  }
}

// Usage:

var printer = new Printer ();
printer.print ('hello');

Donc, notre classe d'imprimantes est maintenant plus modulaire, propre et facile undrestand mais Il n'est pas encore flexible. Chaque fois que vous utilisez le mot clé new, vous êtes réellement codé en dur} quelque chose; dans ce cas, vous êtes constructeur un pilote à l’intérieur de votre imprimante qui est en réalité un exemple d’imprimante dotée d’un pilote intégré qui ne peut jamais changer!

Case3: Injecter un pilote déjà créé dans votre imprimante

Une meilleure version consiste à injecter un pilote au moment où nous construisons une imprimante Cela signifie que vous pouvez créer n’importe quel type d’imprimante, couleur ou noir et blanc, car cela signifie que le pilote est créé séparément et en dehors de la classe Printer alors donné (INJECTED!) dans la Printer...

class Printer {
  constructor (driver) {
    this.lcd = '';
    this.driver = driver;
  }

  print (text) {
    this.lcd = 'printing...';
    this.driver.driverPrint (text);
  }
}

class BWDriver {
  driverPrint (text) {
    console.log (`I will print the ${text} in Black and White.`);
  }
}

class ColorDriver {
  driverPrint (text) {
    console.log (`I will print the ${text} in color.`);
  }
}

// Usage:
var bwDriver = new BWDriver ();
var printer = new Printer (bwDriver);
printer.print ('hello'); // I will print the hello in Black and White.

l’utilisation est maintenant différente, en tant qu’utilisateur, afin de disposer d’une imprimante, vous devez d’abord construire un pilote (de votre choix!), puis le transmettre à votre imprimante. Il peut sembler que l’utilisateur final ait maintenant besoin d’en savoir un peu plus sur le système, mais cette structure leur confère davantage de souplesse. Les utilisateurs peuvent passer N'IMPORTE QUEL pilote dans la mesure où ils sont valides! Par exemple, supposons que nous ayons un type de pilote BWDriver (noir et blanc); L'utilisateur peut créer un nouveau pilote de ce type et l'utiliser pour créer une nouvelle imprimante imprimant en noir et blanc.

Jusqu'ici tout va bien! Mais ce que vous pensez que nous pouvons faire mieux et ce qui, selon vous, a encore un peu de marge de manoeuvre ici? Je suis sûr que vous pouvez le voir aussi!

Nous créons une nouvelle imprimante à chaque fois nous avons besoin que notre imprimante imprime avec Un pilote différent! C'est parce que nous passons notre pilote de choix à la classe Imprimante au moment de la construction. si l'utilisateur souhaite utiliser un autre pilote il doit créer une nouvelle imprimante avec ce pilote; Par exemple, si maintenant je veux faire une impression couleur, je dois faire:

var cDriver = new ColorDriver ();
var printer = new Printer (cDriver); // Yes! This line here is the problem!
printer.print ('hello'); // I will print the hello in color.

Cas 4: Fournissez une fonction de réglage pour configurer le pilote de votre imprimante à TOUT MOMENT!

class Printer {
  constructor () {
    this.lcd = '';
  }

  setDriver (driver) {
    this.driver = driver;
  }

  print (text) {
    this.lcd = 'printing...';
    this.driver.driverPrint (text);
  }
}

class BWDriver {
  driverPrint (text) {
    console.log (`I will print the ${text} in Black and White.`);
  }
}

class ColorDriver {
  driverPrint (text) {
    console.log (`I will print the ${text} in color.`);
  }
}

// Usage:
var bwDriver = new BWDriver ();
var cDriver = new ColorDriver ();
var printer = new Printer (); // I am happy to see this line only ONCE!

printer.setDriver (bwDriver);
printer.print ('hello'); // I will print the hello in Black and White.

printer.setDriver (cDriver);
printer.print ('hello'); // I will print the hello in color.

L'injection de dépendance n'est pas un concept vraiment difficile à comprendre. Le terme est peut-être un peu surchargé, mais une fois que vous avez compris son objectif, vous vous retrouverez à l’utiliser la plupart du temps. 

6
Vennesa

Je dirais que DI est une fonctionnalité clé en main de JS/ES2015. :-) Bien sûr, ce ne sont pas des conteneurs IOC complets, mais ils ont l’air utiles, n’est-ce pas? Découvrez un exemple ci-dessous!

const one = () => 1;
const two = ({one}) => one + one;
const three = ({one, two}) => one + two;

// IOC container
const decimalNumbers = {
  get one() { return one(this); },
  get two() { return two(this); },
  get three() { return three(this); }
};

const binaryTwo = ({one}) => one + 9;

// child IOC container
const binaryNumbers = Object.create(decimalNumbers, {
  two: { get() { return binaryTwo(this); } }
});

console.log(`${decimalNumbers.three} is ${binaryNumbers.three} in binary`);

Vous pouvez encapsuler les dépendances dans _.once (voir underscore ou lodash ) pour les transformer en singletons.

const Rand = function() {
  return (min, max) => min + Math.random() * (max - min) | 0;
};

const pair = function({Rand} = this) {
  return [Rand(10, 100), Rand(100, 1000)];
};

// IOC container
const ioc = Object.create({}, {
  Rand: {get: Rand},
  pair: {get: _.once(pair)} // singleton
});

console.log(`${[ioc.pair, ioc.pair === ioc.pair]}`);
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

2
dizel3d

J'ai codé mon propre cadre d'injection de dépendance JavaScript appelé Di-Ninja https://github.com/di-ninja/di-ninja

Comme je le sais, il est le seul en javascript à implémenter le modèle de conception Composition-Root, qui vous aide à garder tout ce qui est découplé et à connecter les composants de l’application et la configuration à un emplacement unique et racine ..__ http://blog.ploeh.dk/2011/07/28/CompositionRoot/

Cela fonctionne bien avec NodeJS et Webpack

Tout commentaire serait apprécié

1
Jo Takion

Prenez un butin chez Flyspeck: https://Gist.github.com/elfet/11349215

var c = new Flyspeck();

c.set('name', 'GistHub');

c.set('config', {
    server: 'https://Gist.github.com'
});

c.set('user', function (c) {
    return new User(c.get('name'));
});

c.extend('user', function (user, c) {
    return new ProxyUser(user);
});

c.set('app', function (c) {
    return new Application(c.get('config'), c.get('user'));
});

var app = c.get('app');
1
Medvedev

candiJS est une bibliothèque de création d’objets et de dépendance implicite légère. Regarde

Exemple:

candi.provider.singleton('ajax', function() {
    return {
        get: function() { /* some code */ },
        put: function() { /* some code */ }
    };
});

candi.provider.singleton('carService', function(ajax) {
    return {
        getSpecs: function(manufacturer, year, model, trim) {
            return ajax.get();
        }
    };
});

var Car = candi.provider.instance('Car', function(carService, year, manufacturer, model, trim) {
    this.year = year;
    this.manufacturer = manufacturer;
    this.model = model;
    this.trim = trim;
    this.specs = carService.getSpecs(manufacturer, year, model, trim);
});

var car = new Car(2009, 'honda', 'accord', 'lx');
0
bflemi3

Même si c'est une vieille question, je sens l'envie. ;)

//dependency injection
class Thing1 {
    constructor(aThing){
        this.otherThing = aThing;
    }
}
class Thing2 {}

const thing = new Thing1(new Thing2())

//dependency inversion
class Thing1 {
    constructor({
        read = null
    } = {}){
        if(typeof read !== 'function'){
            //establish a simple contract
            throw new TypeError(read + ' is not a function.');
        }
        this._read = read;
        //Somewhere an instance of Thing1()
        //will call this._read()
    }
}

class Thing2 {
    read(){
       //read something
    }
}

const thing2 = new Thing2();
const thing1 = new Thing1({
    read(){
        //Here is the equivalent to the so called "interface"
        return thing2.read();
    }
});
0
Quentin Engles

bubble-di est un conteneur DI léger pour Javascript et TypeScript.

Il vous permet d’enregistrer des méthodes d’usine (rappels) ou des instances. Vous trouverez ci-dessous un exemple simple ( plusieurs exemples ).

npm install --save bubble-di

var {DiContainer} = require("bubble-di");
// import { DiContainer } from "bubble-di";

DiContainer.setContainer(new DiContainer());

class Bar { sayBar(){ console.log("bar"); } }
class Baz { sayBaz(){ console.log("baz"); } }
class Foo { 
    constructor (bar, baz)
    {
        bar.sayBar();
        baz.sayBaz();
        // ...
    }
};

DiContainer.getContainer().registerInstance("bar", new Bar());
DiContainer.getContainer().registerInstance("baz", new Baz());
DiContainer.getContainer().register("foo", {
    dependencies: ["bar", "baz"],
    factoryMethod: (bar, baz) => new Foo(bar, baz) },
);
const foo = DiContainer.getContainer().resolve("foo"); // will print "bar" and "baz".
0
Ben

Injecting est un conteneur DI léger mais puissant, il peut gérer les injections promises. 

Page d'accueil de l'injection

Code source seulement plus de 100 lignes.

Cas de test pour voir ses exemples.

0
ssnau

Je suis nouveau sur JavaScript. Je me demande comment l'injection de dépendance est implémentée en JavaScript? J'ai cherché sur Internet mais je n'ai rien trouvé.

Pour être tout à fait honnête après avoir travaillé avec JavaScript (principalement côté serveur) et avec l’ensemble de l’écosystème pendant quelques années, j’ai le sentiment que l’injection de dépendance (sans parler des conteneurs) n’a pas vraiment été intégrée à la boîte à outils d’un programmeur JS classique. C'est probablement la raison pour laquelle il n'y a pas beaucoup d'informations à ce sujet (cela s'améliore cependant).

Contrairement à un langage tel que Java, vous ne pouvez pas compter sur des types statiques en JavaScript. Ce seul fait exclut la manière traditionnelle de déclarer des dépendances via des interfaces. Vous pouvez bien sûr ajouter des types à JS (voir Flow ) mais ceux-ci sont supprimés avant que le code ne soit exécuté. La même chose s’applique à TypeScript mais je pense qu’il existe un moyen de conserver les types en tant que métadonnées non appliquées. De plus, JavaScript ne prend pas en charge les annotations (bien qu'il y ait une proposition pour elle).

Les gens contournent les limitations de différentes manières. Certains conteneurs analysent la définition de fonction/classe (comme ils appellent .toString() sur la fonction/classe transmise et analysent la chaîne résultante) et recherchent les dépendances basées sur les noms, certains exigent que les fonctions/classes fournissent une méthode property/static liste des dépendances.

Je travaille moi-même sur un conteneur appelé Ashley , qui demande simplement les dépendances dans le cadre du processus de reliure. Aucune inspection supplémentaire requise.

container.instance('Client', Client, ['DependencyA', 'DependencyB']);
container.instance('DependencyA', DependencyA, ['DependencyC']);
container.instance('DependencyB', DependencyB, ['DependencyC']);
container.instance('DependencyC', DependencyC, [], {
  scope: 'Prototype', // Defaults to Singleton
  initialize: true,
  deinitialize: true
});

const client = await container.resolve('Client');

Plus d'exemples sur GitHub .

0
Jiří Pospíšil