Salut tout le monde !

Ayant regardé la très bonne vidéo sur le design pattern relatif au DIC, j'ai eu la folle idée de recréer ce système en JavaScript. Je me suis heurté à plusieurs problèmes :

  • Le JavaScript ne dispose d'aucune reflection du code (impossible par exemple d'accéder aux paramètres d'une classe)
  • Il n'existe pas de méthode pour instancer dynamiquement une classe depuis une chaine de caractères ou une variable
  • Il est impossible d'initialiser une classe ou une fonction avec un nombre inconnus de paramètres depuis un tableau
  • Il n'existe aucun typage des variables en JavaScript

J'ai la bonne nouvelle de vous annoncer que j'ai plus ou moins réussi à contourner ces problèmes. C'est un petit projet qui m'a demandé quelques recherches. Beaucoup de fonctions de facilité qui existent en PHP n'existent tout simplement pas en JavaScript (du style newInstanceArgs ou encore getParameters).

Avant de vous présenter le script, je vais directement vous présenter le résultat du script. Celui-ci est très puissant couplé à système comme browserify qui permet de s'affranchir des limitations de chargement asynchrone de code. (on peut utiliser d'autres systèmes bien entendu comme require.js).

var DIC  = require("./core/DIC.js");
var app  = new DIC();

window.Foo = function Foo() {}
// Bar dépend de Foo
window.Bar = function Bar(Foo) { }

console.log(app.get('Bar'));

paramètres par défaut

Si vous avez des paramètres qui ont une valeur par défaut, une valeur null sera envoyée. Il est donc très facile d'initialiser vos paramètres qui ne sont pas des classes :

window.Bar = function Bar(Foo, param)
{
    this.param = param || "chien";
}

Limitations du système
  • Vos classes doivent obligatoirement se trouver dans le scope global (le problème de scope n'existe pas en PHP ...)
  • Elles doivent respecter la nomenclature suivante pour accéder à leurs paramètres, à leur accéssibilité et à leur nom :
window.NomDeClasse = function NomDeClasse(Parametres)
{

}
  • Si votre classe dépend d'une autre et que vous utilisez l'initialisation implicite, alors un attribut du même nom de la classe sera créé avec pour valeur une instance de cette classe (comme sur la capture).

Voici le script :

/**
 * Conteneur d'injection de dépendances avec réflexion de code
 * @class DIC
 * @constructor
**/

var DIC = function()
{
    "use strict";

    /**
     * Contient la liste des callback d'une classe nommée par un alias
     * @propery registry {Array}
     * @default empty array
    **/

    this.registry = new Array();

    /**
     * Contient la liste des instances d'une classe nommée par un alias
     * @propery instances {Array}
     * @default empty array
    **/

    this.instances = new Array();

    /**
     * Contient la liste des recettes d'instances d'une classe nommée par un alias
     * @propery factories {Array}
     * @default empty array
    **/

    this.factories = new Array();

    /**
     * Permet d'enregistrer une classe dans le registre avec son alias et un callback qui permet de savoir comment instancier cette classe
     * @method set
     * @param alias {String} L'alias de la classe
     * @param callback {Function} La fonction d'instanciation de la classe
     * @return {void}
    **/

    this.set = function(alias, callback)
    {
        this.registry[alias] = callback;
    };

    /**
     * Permet d'enregistrer de manière unique une classe dans le registre avec son alias et un callback qui permet de savoir comment instancier cette classe
     * @method setFactory
     * @param alias {String} L'alias de la classe
     * @param callback {Function} La fonction d'instanciation de la classe
     * @return {void}
    **/

    this.setFactory = function(alias, callback)
    {
        this.factories[alias] = callback;
    };

    /**
     * Permet d'ajouter l'instance d'une classe au DIC
     * @method setInstance
     * @return {void}
    **/

    this.setInstance = function(instance)
    {
        var alias = instance.constructor.name;
        this.instances[alias] = instance;
    };

    /**
     * Permet d'accéder à l'instance ou la définition d'une classe par son alias
     * @method get
     * @param alias {String} L'alias de la classe à accéder
     * @return {Function}
    **/
    this.get = function(alias)
    {
        if(this.factories[alias])
            return this.factories[alias]();

        if(!this.instances[alias])
        {
            if(this.registry[alias])
                this.instances[alias] = this.registry[alias]();
            else
            {
                if(window[alias])
                {
                    this.instances[alias] = new window[alias]();
                    var args = this._getArguments(alias);

                    for(var i = 0; i < args.length; i++) {
                        if(window[args[i]])
                            args[i] = this.get(args[i]);
                        else
                            args[i] = null;
                    }

                    for(var i = 0; i < args.length; i++)
                        if(args[i])
                            this.instances[alias][args[i].constructor.name] = args[i];
                }
                else
                    throw new Error("Can't resolve class " + alias);
            }
        }

        return this.instances[alias];
    };

    /**
     * Permet de connaitre le nom des paramètres d'une classe
     * @method _getArguments
     * @param alias {String} L'alias de la classe à accéder
     * @return {Array}
    **/
    this._getArguments = function(alias)
    {
        var str = window[alias].toString();
        str = str.split('{')[0].split('(')[1].split(')')[0].split(',');

        for(var s in str)
            str[s] = str[s].trim(' ');

        return str;
    };
};

En éspérant que cela serve à d'autres !

Aucune réponse