Философия IoC во внешнем интерфейсе

внешний интерфейс Шаблоны проектирования
Философия IoC во внешнем интерфейсе

Photo by kevin laminto

Оригинальная ссылка:Концепция IoC во внешнем интерфейсе — знайте столбец

задний план

В процессе роста клиентских приложений зависимости между внутренними модулями могут становиться все более и более сложными.низкая возможность повторного использованияпривести к применениюсложно поддерживать, но мы можем решить эти проблемы в определенной степени с помощью некоторых отличных концепций программирования в компьютерной области.IoCявляется одним из них.

Что такое ИОК

IoCполное имя называетсяInversion of Control, что можно перевести как «Инверсия контроля"или"Инверсия зависимости, который в основном содержит три критерия:

  1. Модули высокого уровня не должны зависеть от модулей низкого уровня, все они должны зависеть от абстракций.
  2. Абстракция не должна зависеть от конкретной реализации, конкретная реализация должна зависеть от абстракции.
  3. интерфейсно-ориентированное программированиеа не программирование, ориентированное на реализацию

Понятия всегда абстрактны, поэтому приведенные выше понятия поясняются на примере:

Предположим, вам нужно создать приложение с именемApp, который содержит модуль маршрутизацииRouterи модуль мониторинга страницTrack, который изначально может быть реализован следующим образом:

// app.js
import Router from './modules/Router';
import Track from './modules/Track';

class App {
    constructor(options) {
        this.options = options;
        this.router = new Router();
        this.track = new Track();

        this.init();
    }

    init() {
        window.addEventListener('DOMContentLoaded', () => {
            this.router.to('home');
            this.track.tracking();
            this.options.onReady();
        });
    }
}

// index.js
import App from 'path/to/App';
new App({
    onReady() {
        // do something here...
    },
});

Что ж, вроде бы не проблема, но требования в практических приложениях очень изменчивы, и может возникнуть необходимость в добавлении в маршрутизацию новых функций (таких как реализацияhistoryрежиме) или обновить конфигурацию (включитьhistory, new Router({ mode: 'history' })). Это должно бытьAppВнутренне изменить эти два модуля, этоINNER BREAKINGоперации, в то время как для ранее испытанногоAppНапример, его также необходимо перепроверить.

Очевидно, что это не очень хорошая структура приложения, модули высокого уровняAppЗависит от двух низкоуровневых модулейRouterиTrack, изменения в модулях более низкого уровня повлияют на модули более высокого уровня.App. Итак, как решить эту проблему, решение будет описано далееВнедрение зависимости.

внедрение зависимости

Проще говоря, так называемая инъекция зависимостей — это «внедрение» зависимостей модулей, от которых зависят высокоуровневые модули, в модуль путем передачи параметров.Приведенный выше код можно преобразовать следующим образом с помощью зависимости впрыск:

// app.js
class App {
    constructor(options) {
        this.options = options;
        this.router = options.router;
        this.track = options.track;

        this.init();
    }

    init() {
        window.addEventListener('DOMContentLoaded', () => {
            this.router.to('home');
            this.track.tracking();
            this.options.onReady();
        });
    }
}

// index.js
import App from 'path/to/App';
import Router from './modules/Router';
import Track from './modules/Track';

new App({
    router: new Router(),
    track: new Track(),
    onReady() {
        // do something here...
    },
});

Как видите, вышеописанное решается внедрением зависимостейINNER BREAKINGпроблема, вы можете напрямуюAppИзменения вносятся в каждый модуль снаружи, не затрагивая внутренности.

Все в порядке? Идеал очень полный, а реальность очень худая.В течение двух дней продукт выдвинул новый спрос на вас.AppДобавить модуль обменаShare. Это восходит к упомянутому вышеINNER BREAKINGПроблема: вы должныAppмодуль для изменения и добавления строкиthis.share = options.share, что явно не то, что мы ожидали.

Несмотря на то чтоAppЗависимости от нескольких других модулей в определенной степени отделяются за счет внедрения зависимостей, но этого недостаточно.this.routerиthis.trackи другие атрибуты на самом деле все еще зависят от «конкретной реализации», что явно нарушаетIoCпринципы мышления, как дальше абстрагироватьсяAppНасчет модулей.

Talk is cheap, show you the code:

class App {
    static modules = []
    constructor(options) {
        this.options = options;
        this.init();
    }
    init() {
        window.addEventListener('DOMContentLoaded', () => {
            this.initModules();
            this.options.onReady(this);
        });
    }
    static use(module) {
        Array.isArray(module) ? module.map(item => App.use(item)) : App.modules.push(module);
    }
    initModules() {
        App.modules.map(module => module.init && typeof module.init == 'function' && module.init(this));
    }
}

После ремонтаAppВ нем нет "конкретной реализации", и я не вижу никакого бизнес-кода, так как же его использовать?AppКак насчет управления нашими зависимостями:

// modules/Router.js
import Router from 'path/to/Router';
export default {
    init(app) {
        app.router = new Router(app.options.router);
        app.router.to('home');
    }
};
// modules/Track.js
import Track from 'path/to/Track';
export default {
    init(app) {
        app.track = new Track(app.options.track);
        app.track.tracking();
    }
};

// index.js
import App from 'path/to/App';
import Router from './modules/Router';
import Track from './modules/Track';

App.use([Router, Track]);

new App({
    router: {
        mode: 'history',
    },
    track: {
        // ...
    },
    onReady(app) {
        // app.options ...
    },
});

Его можно найтиAppМодуль также очень удобен в использовании, благодаряApp.use()метод "внедрения" зависимостей, в./modules/some-module.jsв соответствии с определенным «соглашение” для инициализации соответствующей конфигурации, например, в это время нужно добавить новуюShareмодуль, не нужноAppИзмените содержимое внутри:

// modules/Share.js
import Share from 'path/to/Share';
export default {
    init(app) {
        app.share = new Share();
        app.setShare = data => app.share.setShare(data);
    }
};

// index.js
App.use(Share);
new App({
    // ...
    onReady(app) {
        app.setShare({
            title: 'Hello IoC.',
            description: 'description here...',
            // some other data here...
        });
    }
});

прямо вAppвыйти на улицуuseэтоShareМодуля достаточно, а закачка и настройка модуля крайне удобны.

затем вAppКакая работа была проделана внутри, в первую очередьApp.useКак начать:

class App {
    static modules = []
    static use(module) {
        Array.isArray(module) ? module.map(item => App.use(item)) : App.modules.push(module);
    }
}

Можно четко обнаружить, что,App.useДелается очень просто — сохранить зависимости вApp.modulesсвойство, вызываемое при ожидании последующей инициализации модуля.

Далее рассмотрим метод инициализации модуляthis.initModules()Что именно делал:

class App {
    initModules() {
        App.modules.map(module => module.init && typeof module.init == 'function' && module.init(this));
    }
}

Можно обнаружить, что этот метод также делает очень простую вещь, которая заключается в обходеApp.modulesВсе модули в, определить, содержит ли модульinitатрибут и атрибут должен быть функцией.Если решение принято, метод выполнит модульinitметод и поставитьAppэкземплярthisПередайте его, чтобы сослаться на него в модуле.

Из этого метода видно, что для достиженияApp.use()модуль, он должен удовлетворять двум "соглашение":

  1. Модуль должен содержатьinitАтрибуты
  2. initдолжна быть функция

Это на самом делеIoCв мысляхинтерфейсно-ориентированное программированиеХороший пример принципа «не программируй для реализации».AppНеважно, что реализует модуль, если он удовлетворяетинтерфейс initДостаточно "контракта".

вернуться сейчасRouterРеализация модуля позволяет легко понять, зачем и как писать:

// modules/Router.js
import Router from 'path/to/Router';
export default {
    init(app) {
        app.router = new Router(app.options.router);
        app.router.to('home');
    }
};

Суммировать

AppТеперь модуль должен называться "контейнер«Это более уместно, оно не имеет ничего общего с бизнесом, оно просто предоставляет некоторые методы, помогающие управлять внедряемыми зависимостями и контролировать выполнение модуля.

Инверсия контроля (Inversion of Control) является своего рода "Мысль", Внедрение зависимости (Dependency Injection) является специфическимМетод реализации', а здесьAppЭто «вспомогательное управление зависимостями»контейнер".

FEFollow.png