Terapkan sepenuhnya. Di-in-js


Halo semuanya! Hari ini saya akan mencoba bereksperimen dengan Dependency Injection dalam JavaScript murni . Mereka yang tidak tahu jenis permainannya dan bagaimana memasaknya, saya mengundang Anda untuk membiasakan diri. Nah, mereka yang tahu akan memiliki kesempatan untuk menulis komentar yang penting dan berguna. Jadi, kami melaju ...


Ketergantungan injeksi


DI adalah pola arsitektur yang dirancang untuk mengurangi keterhubungan entitas sistem - komponen, modul, kelas. Semakin sedikit terhubung (tidak menjadi bingung dengan keterhubungan ), semakin mudah untuk mengubah entitas ini, menambahkan yang baru dan mengujinya. Secara umum, nilai tambah adalah nilai tambah, tetapi mari kita lihat apakah ini benar-benar terjadi.


Tanpa 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();

Dengan 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());

Pada contoh pertama tanpa DI, kelas Mobil kami terikat ke kelas tertentu, dan karenanya, untuk membuat, misalnya, electroCar , Anda harus membuat kelas ElectroCar yang terpisah . Dalam perwujudan ini, ada ketergantungan implementasi "keras" yaitu Ketergantungan pada instance dari kelas tertentu.


โ€” 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 , .


:



Menurut pendapat saya, opsi terakhir - ke-3 paling cocok untuk definisi DI & IoC, sehingga mekanisme implementasi paling tersembunyi dari kode klien.
Yah, itu saja. Saya harap ini menarik dan informatif. Selamat tinggal semuanya!


All Articles