Photo by kevin laminto
Оригинальная ссылка:Концепция IoC во внешнем интерфейсе — знайте столбец
задний план
В процессе роста клиентских приложений зависимости между внутренними модулями могут становиться все более и более сложными.низкая возможность повторного использованияпривести к применениюсложно поддерживать, но мы можем решить эти проблемы в определенной степени с помощью некоторых отличных концепций программирования в компьютерной области.IoC
является одним из них.
Что такое ИОК
IoC
полное имя называетсяInversion of Control
, что можно перевести как «Инверсия контроля"или"Инверсия зависимости, который в основном содержит три критерия:
- Модули высокого уровня не должны зависеть от модулей низкого уровня, все они должны зависеть от абстракций.
- Абстракция не должна зависеть от конкретной реализации, конкретная реализация должна зависеть от абстракции.
- интерфейсно-ориентированное программированиеа не программирование, ориентированное на реализацию
Понятия всегда абстрактны, поэтому приведенные выше понятия поясняются на примере:
Предположим, вам нужно создать приложение с именем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()
модуль, он должен удовлетворять двум "соглашение":
- Модуль должен содержать
init
Атрибуты -
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
Это «вспомогательное управление зависимостями»контейнер".