Dependency Injection. Javascript.
Сергей Камардин
Задача из жизни
1. Разместить на карте зарегистрированных пользователей;
!
2
«JS Way»Locator.prototype.locateUsers = function(users) { // Инициалиация сервиса карт loadScript("..."); client.init(...); ! // Создание карты var map = new client.Map(...); ! // Отображение на карте маркеров users.forEach(...); }
3
Задача из жизни
1. Разместить на карте зарегистрированных пользователей;
2. Поменять поставщика карт;
4
Решение: АбстракцияAbstractMapService.prototype = { ! createMap: function() { throw new TypeError("Not implemented"); }, ! createMapMarker: function() { throw new TypeError("Not implemented"); } !}
5
ИмплементацияAbstractMapService.extend({ ! createMap: function(id, options) { // }, ! createMapMarker: function(map, options) { // } !});
6
Locator.prototype.locateUsers = function(users) { // Create map service // Could be an Fabric Method, Service Locator, DI var mapService = new ConcreteMapService(...); ! // Create map var map = mapService.createMap(...); ! // Run each users, making markers users.forEach(...); }
7
Single responsibility?Locator.prototype.locateUsers = function(users) { // Create map service // Could be an Fabric Method, Service Locator, DI var mapService = new ConcreteMapService(...); ! // Create map var map = mapService.createMap(...); ! // Run each users, making markers users.forEach(...); }
8
Проблемы
• Невозможно написать юнит тесты;
• Использование сервиса карт во многих местах в коде;
• Избыточная функциональность локатора;
• Reusability.
9
Задача из жизни
1. Разместить на карте зарегистрированных пользователей;
2. Поменять поставщика карт;
3. Перенести локатор в другой проект, как плагин.
10
ResultLocator.prototype = { // injection setMapService: function(mapService) { this.mapService = mapService; }, ! locateUsers: function(users) { // Create map var map = this.mapService.createMap(...); ! // Run each users, making markers users.forEach(...); } }
11
12
ResultLocator.prototype = { // injection setMapService: function(mapService) { this.mapService = mapService; }, ! locateUsers: function(users) { // Create map var map = this.mapService.createMap(...); ! // Run each users, making markers users.forEach(...); } }
13
Вот оно
Routine// Create MapService var mapService = new MapService(...); !// Create Locator var locator = new Locator(); !// Inject mapService locator.setMapService(mapService); !// Use locator locator.locateUsers(users);
14
Inversion of Control
• Dependency Injection
• Service Locator
• Factory Method
IoC container:
15
Dependency Injection
• Constructor injection
• Setter injection
• Interface injection
16
Hard Coupling
17
Coupling
18
Loose Coupling
19
Плюсы• Каждый объект отвечает за свою функцию;
• Соблюден принцип инверсии зависимостей;
• Простая конфигурация объектов;
• Безболезненная смена имплементаций;
• Легко писать юнит тесты.
20
dm.js
• Javascript Реализация IoC;
• Работает в node.js и браузере;
• Легко расширяется (любые загрузчики скриптов и Promise/A+ библиотеки);
• Простая конфигурация (в духе Symfony).
21
Конфигурация "locator": { path: "path/to/locator/implementation", calls: [ ["setMapService", ["@maps"]] ] } "maps": { path: "path/to/map/service/implementation", arguments: [{ id: "my-app-id" }] }
22
Использование
dm .get("locator") .then(function(locator) { locator.locateUsers(users); });
23
Тестированиеit("should create map", function() { var mapStub, locator; ! mapMock = sinon .mock(new AbstractMapService) .expects("createMap") .once(); ! locator = new Locator(); locator.setMapService(mapMock); ! locator.locateUsers(...); ! mapMock.verify(); });
24
Syntax! "example": { "path": "...", "arguments": [{ "service": "@service", "method": "@service:method" "result": "@service:method[1,2,3]", "resource": "#path/to/my.tpl#", "path": "http://%{api_path}" "build": "build_no_#{file.txt}" }] } !
25
Альтернативы
• Wire.js
• Angular’s DI
• Тупо контейнеры
26
Где и для чего это можно использовать?
27