Меню

Принцип инверсии зависимостей в JS (DIP)

25.06.2018 - java script, NodeJS, ЯП

Принцип инверсии зависимостей (Dependency inversion principle) DIP

Также известный как «внедрение зависимости» или «инверсия контроля». Снимает функцию или класс его ответственности, чтобы найти и создать экземпляры их зависимостей.

При это принципе нам не нужно волноваться о зависимостях, так как они создаются чем то еще.
Что это за «что-то еще».
Это DI Container/IoC container (Dependency injection/ Inverse of Control).

Этот контейнер — это глобальное место куда сваливаются все зависимости.

Давайте представим, вы хотите создать экземпляр класса PostsService. Для его нормальной работы нам нужно три экземпляра классов: UserService, HttpClient, Endpoints, в это же время, у UserService есть свои зависимости: HttpClien и AuthService. У HttpClient тоже есть зависимость — XHRBackend.

Т.е. мы пришли к тому, что классу PostService нужно знать не только как создать свой экземпляр, а также как создать экземпляры зависимостей своих экземпляров, а также зависимости зависимостей. Чем больше проект, тем сложнее это получить.

Итак, как же работает IoC.

У нас есть IoC контейнер, и контроллер, с помощью которого мы помещаем наши зависимостей в этот контейнер. Всё будет зарегистрированным в нём. Например у нас в контейнере указан класс HttpClient и контейнер будет знать как создать экземпляр класса. AuthService также будет зареген в контейнере. XHRBackend также будет зареген в контейнере.

Также будет зареген в контейнере и сам UserService, и контроллер будет знать какие зависимости и как к нему вызвать.

Также мы зарегили класс без зависимостей, такой как Endpoints. Таже мы зарегили PostsService, который зависит от HttpClient, UserService и Endpoints классов.

Поток управления был перевернут. PostsService классу теперь не нужно знать как создать экземпяры его зависимостей, т.к. контейнер знает как. Если Вы поймаете себя за тем что используете оператор new для создания зависимостей, значит Вы что то делаете не так. Вам нужно просто вызвать контейнер.

Как нам это сделать? Посмотрим на примере.

Обратите внимание на библиотеку BottleJS. Мы создадим свой контейнер, но в продакшене лучше пользоваться хорошими рабочими вариантами.

Это контейнер для инъекций зависимостей.

Установим bottleJS с помощью команды

npm i bottlejs

Пропишем простенькую конфигурацию вебпака

module.exports = {
    entry: 'app.js',
    output: {
        filename: 'bundle.js'
    }
}

Затем создадим файлы:

В файле http-client.js экспортируем обычный класс без зависимостей

export class HttpClient {
    get(url) {
        return fetch(url).then(res => res.json());
    }
}

Теперь давайте создадим сервис (post-manager.js)
Этот класс зависит от http-клиента. Где внедрение зависимости будет очень полезным. Нам нужно внедрить http клиент в конструктор. Нам не нужно создавать экземпляр или импортировать его. Представьте насколько это удобно, если бы у нас было несколько зависимостей.

export class PostsManager {
    constructor(http) {
        this.http = http;
    }

    getPosts() {
        return this.http.get("https://jsonplaceholder.typicode.com/posts");
    }

    getComments() {
        return this.http.get("https://jsonplaceholder.typicode.com/comments");
    }
}

Теперь засунем всё это добро в Dependency Ijection container в файле app.js.
Для начала импортнем нашу библиотеку bottlejs. Затем импортнем наши классы.

Теперь нужно создать новый экземпляр контейнера. Это единственное место, где мы воспользуемся оператором new в нашем приложении.

Теперь зарегим наш http-client класс, используя метод контейнера service.
После зарегим posts-manager, последним параметром укажем зависимости. В нашем случае одну.

Когда придем время для bottlejs чтобы вызвать posts-manager, он глянет на аргумент с зависимостями, посмотрит на список уже зарегенных у себя классов и вызовет нужный.

import * as Bottle from 'bottlejs';
import {HttpClient} from './http-client';
import {PostsManager} from './posts-manager';

const bottle = new Bottle();
bottle.service("HttpClient", HttpClient);
bottle.service("PostsManager", PostsManager, 'HttpClient');


const postManager = bottle.container.PostsManager;
postsManager.getPosts().then(posts => console.log("Posts: ", posts));
postsManager.getComments().then(comments => console.log("Comments: ", comments));
Метки: , , , , ,

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *