Implémentez-le complètement. Di-in-js


Bonjour à tous! Aujourd'hui, je vais essayer d'expérimenter l' injection de dépendance en JavaScript pur . Ceux qui ne savent pas de quel jeu il s'agit et comment le cuisiner, je vous invite à vous familiariser. Eh bien, ceux qui sont au courant auront l'occasion d'écrire un commentaire important et utile. Nous avons donc conduit ...


Injection de dépendance


DI est un modèle architectural conçu pour réduire la connectivité des entités du système - composants, modules, classes. Moins ils sont connectés (à ne pas confondre avec la connectivité ), plus il est facile de changer ces mêmes entités, d'en ajouter de nouvelles et de les tester. En général, un plus est un plus, mais voyons si c'est vraiment le cas.


Sans DI:


   class Engine {...};
   class ElectroEngine {...};
   class Transmission {...};
   class Chassis {...};
   class TestChassis {...};

   class Car {
        constructor() {
            this.engine = new Engine();
            this.transmission = new Transmission();
            this.chassis = new Chassis();
        }
    }

    class ElectroCar {
        constructor() {
            this.engine = new ElectroEngine();
            this.transmission = new Transmission();
            this.chassis = new Chassis();
        }
    }

   class TestCar {
        constructor() {
            this.engine = new Engine();
            this.transmission = new Transmission();
            this.chassis = new TestChassis ();
        }
    }

    const car = new Car();
    const electroCar = new ElectroCar();
    const testCar = new TestCar();

Avec DI:


    class Engine{...};
    class ElectroEngine {...};
    class TestEngine {...};

    class Transmission {...};
    class TestTransmission {...};

    class Chassis {...};
    class SportChassis {...};
    class TestChassis {...};

     class Car {
        constructor(engine, transmission, chassis) {
            this.engine = engine;
            this.transmission = transmission;
            this.chassis = chassis;
        }
    }

    const petrolCar = new Car(new Engine(), new Transmission(), new Chassis());
    const sportCar = new Car(new Engine(), new Transmission(), new SportChassis());
    const electroCar = new Car(new ElectroEngine(), new Transmission(), new Chassis());
    const testCar = new Car(new TestEngine(), new TestTransmission(), new TestChassis());

Dans le premier exemple sans DI, notre classe Car est liée à des classes spécifiques, et donc, pour créer, par exemple, electroCar , vous devez créer une classe ElectroCar distincte . Dans ce mode de réalisation, il existe une dépendance de mise en œuvre "dure", c'est-à-dire Dépendance de l'instance d'une classe particulière.


DI, Car. . ! — . , "" — .


DI . "" — , , . , ? , . :


class Engine{
   constructor(candles, pistons, oil) {….}
};

class Chassis{
    constructor(doors, hood, trunk) {….}
};

const petrolCar = new Car(
    new Engine(new Candles(), new Pistons(), new Oil() ), 
    new Transmission(…..), 
    new Chassis(new Doors, new Hood(), new Trunk())
);

. , . , , .


Inversion of Control


"" DI- — Inversion of Control (IoC). , — , . DI, IoC , . - , . :


class Engine{...};
class Transmission{...};
class Chassis{…}

class Car {
        constructor(engine: Engine, transmission: Transmission, chassis: Chassis) {}
} 

const car = new Car();

car.engine instanceof Engine; //*true*

— new Car(). — , a .


DI-in-JS


JS. , DI . "".


, , , , . . .


constructor(engine = Engine, transmission = Transmission, chassis = Chassis)

:


constructor(engine: Engine, transmission: Transmission, chassis: Chassis)

, . IoC «» , . , ?
, Reflection. , — .


, JS:


function reflectionMetaInfo(a) { console.log(a); }

reflectionMetaInfo.name ;       // reflectionMetaInfo;
reflectionMetaInfo.length   ;   //1
reflectionMetaInfo.toString();  //function reflectionMeta(a) { console.log(a);}
arguments;                      //Arguments [%value%/]

, toString(). . , , . () , . , .


const constructorSignature =  classFunc
                                 .toString()
                                 .replace(/\s|['"]/g, '')
                                 .replace(/.*(constructor\((?:\w+=\w+?,?)+\)).*/g, '$1')
                                 .match(/\((.*)\)/)[1]
                                 .split(',')
                                 .map(item => item.split('='));

constructorSignature // [ [dep1Name, dep1Value], [dep2Name, dep2Value] …. ]

, , . , , . . IoC — .


:


function Injectable(classFunc, options) {
    const 
        depsRegistry = Injectable.depsRegistry || (Injectable.depsRegistry = {}),
        className = classFunc.name,
        factories = options && options.factories;

    if (factories) {
        Object.keys(factories).forEach(factoryName => {
           depsRegistry[factoryName] = factories[factoryName];
        })
    }

    const depDescriptions = classFunc.toString()
                                .replace(/\s/g, '')
                                .match(/constructor\((.*)[^(]\){/)[1]
                                .replace(/"|'/g, '')
                                .split(',')
                                .map(item => item.split('='));

    const injectableClassFunc = function(...args) {

            const instance = new classFunc(...args);

            depDescriptions.forEach(depDescription => {
                const 
                    depFieldName = depDescription[0],
                    depDesc = depDescription[1];

                if (instance[depFieldName]) return;

                try {
                    instance[depFieldName] = new depsRegistry[depDesc]();
                } catch (err) {
                    instance[depFieldName] = depDesc;
                } 
            });

            return instance;
        }

    return depsRegistry[classFunc.name] = injectableClassFunc;
}

class CustomComponent {
    constructor(name = "Custom Component") {
        this.name = name;
    }
    sayName() {
        alert(this.name);
    }
}

const Button = Injectable(
    class Button extends CustomComponent {
        constructor(name = 'Button') {
            super(name);
        }
    }
)

const Popup = Injectable(
    class Popup extends CustomComponent {
        constructor(
            confirmButton = 'confirmButtonFactory',
            closeButton = Button,
            name = 'NoticePopup'
        ) {
            super(name);
        }
    },
    {
        factories: {
            confirmButtonFactory: function() { return new Button('Confirm Button') }
        }
    }
);

const Panel = Injectable(
    class Panel extends CustomComponent {
        constructor(
            closeButton = 'closeButtonFactory',
            popup = Popup,
            name = 'Head Panel'
        ) {
            super(name);
        }
    },
    {
        factories: {
            closeButtonFactory: function() { return new Button('Close Button') }
        }
    }
);

const customPanel = new Panel();

, , . , . — Injectable, IoC. :


  1. ;
  2. ;
  3. ;
  4. ;
  5. ;

, try-catch .


:


  1. . . , , , - .
  2. . , , . - option.factories, .

.


:


function inject(context, ...deps) {
    const 
        depsRegistry = inject.depsRegistry || (inject.depsRegistry = {}),
        className = context.constructor.name;

    let depsNames = depsRegistry[className]; 

    if (!depsNames) {
        depsNames 
            = depsRegistry[className] 
            = context.constructor
                .toString()
                .replace(/\s|['"]/g, '')
                .replace(/.*(inject\((?:\w+,?)+\)).*/g, '$1')
                .replace(/inject\((.*)\)/, '$1')
                .split(',');

       depsNames.shift();
    } 

    deps.forEach((dep, index) => {
        const depName = depsNames[index];
        try {
            context[depName] = new dep();
        } catch (err) {
            context[depName] = dep;
        }
    });

    return context;
}

class Component {

    constructor(name = 'Component') {

        inject(this, name);
    }

    showName() {
        alert(this.name);
    }
}

class Button extends Component {

    constructor(name = 'Component') {
        super();
        inject(this, name);
    }

    disable() {
        alert(`button ${this.name} is disabled`);
    }

    enable() {
        alert(`button ${this.name} is enabled`);
    }
}

class PopupComponent extends Component {

    show() {
        alert(`show ${this.name} popup`);
    }

    hide() {
         alert(`hide ${this.name} popup`);
    }
}

class TopPopup extends PopupComponent {
    constructor(
        popupButton = Button,
        name = 'Top Popup'
    ) {
        super();
        inject(this, popupButton, name);
        this.popupButton.name = 'TopPopup Button';
    }
}

class BottomPopup extends PopupComponent {
    constructor(
        popupButton = function() { return new Button('BottomPopup Button') },
        name = 'Bottom Popup'
    ) {
        super();
        inject(this, popupButton, name);
    }
}

class Panel extends Component {
    constructor(
        name = 'Panel',
        popup1 = TopPopup,
        popup2 = BottomPopup,
        buttonClose = function() { return new Button('Close Button') }
    ) {
        super();
        inject(this, name, popup1, popup2, buttonClose);
    }
}

const panel = new Panel('Panel 1');

. . . , inject, .


inject :


  1. (this)
  2. context.constructor.
  3. .
  4. , — inject.depsRegistry
  5. context

— . — Inject , . , .


:


class Injectable {
    constructor(...dependensies) {

       const 
            depsRegistry = Injectable.depsRegistry || (Injectable.depsRegistry = {}),
            className = this.constructor.name;

       let depNames = depsRegistry[className];

       if (!depNames) {
           depNames = this.constructor
                            .toString()
                            .replace(/\s|['"]/g, '')
                            .replace(/.*(super\((?:\w+,?)+\)).*/g, '$1')
                            .replace(/super\((.*)\)/, '$1')
                            .split(',');
       }

       dependensies.forEach((dependense, index) => {
          const depName = depNames[index];
          try {
            this[depName] = new dependense();
          } catch (err) {
            this[depName] = dependense;
          }
       })                     
    }
}

class Component extends Injectable {
    showName() {
        alert(this.name);
    }
}

class Button extends Component {
    constructor(name = 'button') {
        super(name);
    }

    disable() {
        alert(`button ${this.name} is disabled`);
    }

    enable() {
        alert(`button ${this.name} is enabled`);
    }
}

class PopupComponent extends Component {
    show() {
        alert(`show ${this.name} popup`);
    }

    hide() {
         alert(`hide ${this.name} popup`);
    }
}

class TopPopup extends PopupComponent {
    constructor(
        popupButton = Button,
        name = 'Top Popup'
    ) {
        super(popupButton, name); 
        this.popupButton.name = 'TopPopup Button';
    }
}

class BottomPopup extends PopupComponent {
    constructor(
        popupButton = function() { return new Button('BottomPopup Button') },
        name = 'Bottom Popup'
    ) {
        super(popupButton, name);
    }
}

class Panel extends Component {
    constructor(
        name = 'Panel',
        popup1 = TopPopup,
        popup2 = BottomPopup,
        buttonClose = function() { return new Button('Close Button') }
    ) {
        super(name, popup1, popup2, buttonClose);
    }
}

const panel = new Panel('Panel 1');

Injectable. super , .


:



À mon avis, la dernière - la 3e option est la plus appropriée pour les définitions DI et IoC, de sorte que le mécanisme de mise en œuvre est le plus caché du code client.
Eh bien voilà tout. J'espère que c'était intéressant et instructif. Au revoir tout le monde!


All Articles