|
|
@@ -1,6 +1,7 @@
|
|
|
## @e22m4u/js-service
|
|
|
|
|
|
-Реализация паттерна «Сервис-локатор» для JavaScript.
|
|
|
+Реализация паттерна «Сервис-локатор» и принципа «Инверсия управления» (IoC)
|
|
|
+для JavaScript.
|
|
|
|
|
|
## Оглавление
|
|
|
|
|
|
@@ -35,25 +36,57 @@ const {Service} = require('@e22m4u/js-service');
|
|
|
|
|
|
## Назначение
|
|
|
|
|
|
-Модуль предлагает классы `ServiceContainer` и `Service`,
|
|
|
-которые можно использовать как по отдельности, так и вместе.
|
|
|
+Модуль предлагает два основных класса, `ServiceContainer` и `Service`,
|
|
|
+которые можно использовать как по отдельности, так и вместе для построения
|
|
|
+слабосвязанной архитектуры.
|
|
|
|
|
|
-- `ServiceContainer` - классическая версия сервис-локатора
|
|
|
-- `Service` - скрывает создание контейнера и его распространение
|
|
|
+- `ServiceContainer` *(ядро IoC-контейнера)*
|
|
|
+ Реализация сервис-контейнера через паттерн «Сервис-локатор»;
|
|
|
+- `Service` *(абстрактный сервис)*
|
|
|
+ Позволяет скрывать работу с контейнером и внедрением зависимостей;
|
|
|
|
|
|
-Класс `Service` удобен, когда приложение имеет единственную точку
|
|
|
-входа, которая создается оператором `new`. Например, если такой
|
|
|
-точкой является класс `Application`, то мы могли бы унаследовать
|
|
|
-его от класса `Service`, и обращаться к другим сервисам методом
|
|
|
-`getService` не заботясь о создании и хранении их экземпляров.
|
|
|
+**Инверсия управления (IoC)**
|
|
|
|
|
|
-Кроме того, если другие сервисы так же наследуют от класса
|
|
|
-`Service`, то они могут обращаться друг к другу используя
|
|
|
-тот же метод `getService`, как если бы мы передавали
|
|
|
-сервис-контейнер между ними.
|
|
|
+В основе данной архитектуры лежит принцип **Inversion of Control**.
|
|
|
+Вместо того, чтобы сервис сам создавал свои зависимости (например,
|
|
|
+`const api = new ApiClient()`), он запрашивает их у **сервис-контейнера**
|
|
|
+**(IoC-контейнера)**. Контроль над созданием объектов и управлением
|
|
|
+их жизненным циклом *инвертируется* от сервиса к контейнеру.
|
|
|
+
|
|
|
+**Ключевые преимущества**
|
|
|
+
|
|
|
+- **Слабая связанность (Loose Coupling)**
|
|
|
+ Сервисы не зависят от конкретных реализаций своих зависимостей. Они просто
|
|
|
+ запрашивают другие сервисы по классу-конструктору.
|
|
|
+- **Централизованное управление**
|
|
|
+ Вся конфигурация зависимостей находится в одном месте (в контейнере),
|
|
|
+ что делает архитектуру более прозрачной и управляемой.
|
|
|
+- **Упрощение тестирования**
|
|
|
+ Поскольку зависимости не создаются внутри класса, их легко подменить
|
|
|
+ на мок-объекты в тестах.
|
|
|
+
|
|
|
+```js
|
|
|
+// в тестах легко подменить реальный сервис на мок
|
|
|
+import {ApiService} from './api-service';
|
|
|
+import {MockApiService} from './mock-api-service';
|
|
|
+import {ServiceContainer} from '@e22m4u/js-service';
|
|
|
+
|
|
|
+const container = new ServiceContainer();
|
|
|
+// подмена реализации ApiService
|
|
|
+container.set(ApiService, new MockApiService());
|
|
|
+
|
|
|
+// любой сервис, который запросит ApiService
|
|
|
+// из этого контейнера, получит MockApiService
|
|
|
+
|
|
|
+// MyService зависит от ApiService
|
|
|
+const myService = container.get(MyService);
|
|
|
+```
|
|
|
|
|
|
## ServiceContainer
|
|
|
|
|
|
+В роли IoC-контейнера выступает класс `ServiceContainer`. Он отвечает
|
|
|
+за регистрацию, создание и предоставление экземпляров сервисов (зависимостей).
|
|
|
+
|
|
|
Методы:
|
|
|
|
|
|
- `get(ctor, ...args)` получить существующий или новый экземпляр;
|
|
|
@@ -61,9 +94,9 @@ const {Service} = require('@e22m4u/js-service');
|
|
|
экземпляр, только если указанный конструктор зарегистрирован
|
|
|
в контейнере, в противном случае выбрасывается ошибка;
|
|
|
- `has(ctor)` проверить существование конструктора в контейнере;
|
|
|
-- `add(ctor, ...args)` добавить конструктор в контейнер;
|
|
|
-- `use(ctor, ...args)` добавить конструктор и создать экземпляр;
|
|
|
-- `set(ctor, service)` добавить конструктор и его экземпляр;
|
|
|
+- `add(ctor, ...args)` добавить конструктор в контейнер (ленивая инициализация);
|
|
|
+- `use(ctor, ...args)` добавить конструктор и сразу создать экземпляр;
|
|
|
+- `set(ctor, service)` добавить конструктор и связанный с ним готовый экземпляр;
|
|
|
- `getParent()` получить родительский сервис-контейнер;
|
|
|
- `hasParent()` проверить наличие родительского сервис-контейнера;
|
|
|
|
|
|
@@ -71,7 +104,7 @@ const {Service} = require('@e22m4u/js-service');
|
|
|
|
|
|
Метод `get` класса `ServiceContainer` создает экземпляр
|
|
|
полученного конструктора и сохраняет его для последующих
|
|
|
-обращений по принципу "одиночки".
|
|
|
+обращений по принципу "одиночки" (Singleton).
|
|
|
|
|
|
Пример:
|
|
|
|
|
|
@@ -81,18 +114,16 @@ import {ServiceContainer} from '@e22m4u/js-service';
|
|
|
// создание контейнера
|
|
|
const container = new ServiceContainer();
|
|
|
|
|
|
-// в качестве сервиса используем класс Date
|
|
|
-const myDate1 = container.get(Date); // создание экземпляра
|
|
|
-const myDate2 = container.get(Date); // возврат существующего
|
|
|
+// в качестве сервиса используется класс Date (как пример)
|
|
|
+const myDate1 = container.get(Date); // создает и кэширует экземпляр
|
|
|
+const myDate2 = container.get(Date); // возвращает существующий экземпляр
|
|
|
|
|
|
-console.log(myDate1); // Tue Sep 12 2023 19:50:16
|
|
|
-console.log(myDate2); // Tue Sep 12 2023 19:50:16
|
|
|
console.log(myDate1 === myDate2); // true
|
|
|
```
|
|
|
|
|
|
-Метод `get` может принимать аргументы конструктора. При этом,
|
|
|
-если контейнер уже имеет экземпляр данного конструктора, то
|
|
|
-он будет пересоздан с новыми аргументами.
|
|
|
+Метод `get` может принимать аргументы конструктора. При этом, если контейнер
|
|
|
+уже имеет экземпляр данного конструктора, то он будет пересоздан с новыми
|
|
|
+аргументами.
|
|
|
|
|
|
Пример:
|
|
|
|