Просмотр исходного кода

chore: updates REAMDE.md and package description

e22m4u 2 месяцев назад
Родитель
Сommit
a920122390
3 измененных файлов с 63 добавлено и 31 удалено
  1. 57 26
      README.md
  2. 2 1
      package.json
  3. 4 4
      src/service-container.spec.js

+ 57 - 26
README.md

@@ -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` может принимать аргументы конструктора. При этом, если контейнер
+уже имеет экземпляр данного конструктора, то он будет пересоздан с новыми
+аргументами.
 
 Пример:
 

+ 2 - 1
package.json

@@ -1,11 +1,12 @@
 {
   "name": "@e22m4u/js-service",
   "version": "0.4.2",
-  "description": "Реализация сервис-локатора для JavaScript",
+  "description": "Реализация IoC-контейнера через паттерн Сервис-локатор для JavaScript",
   "author": "Mikhail Evstropov <e22m4u@yandex.ru>",
   "license": "MIT",
   "keywords": [
     "DI",
+    "IoC",
     "Service",
     "Locator",
     "Container"

+ 4 - 4
src/service-container.spec.js

@@ -254,7 +254,7 @@ describe('ServiceContainer', function () {
           expect(res1).to.be.not.eq(res2);
         });
 
-        describe('when the container has a parent', function () {
+        describe('when a container has a parent', function () {
           describe('when a parent container has a registered constructor', function () {
             it('should prioritize its own existing instance when available', function () {
               class ParentService extends Service {}
@@ -549,7 +549,7 @@ describe('ServiceContainer', function () {
           expect(res1).to.be.not.eq(res2);
         });
 
-        describe('when the container has a parent', function () {
+        describe('when a container has a parent', function () {
           describe('when a parent container has a registered constructor', function () {
             it('should prioritize its own existing instance when available', function () {
               class ParentService {}
@@ -754,7 +754,7 @@ describe('ServiceContainer', function () {
           expect(res).to.be.false;
         });
 
-        describe('when the container has a parent', function () {
+        describe('when a container has a parent', function () {
           it('returns true for the child container if the child class is registered in the parent container', function () {
             class ParentService extends Service {}
             class ChildService extends ParentService {}
@@ -845,7 +845,7 @@ describe('ServiceContainer', function () {
           expect(res).to.be.false;
         });
 
-        describe('when the container has a parent', function () {
+        describe('when a container has a parent', function () {
           it('returns true for the child container if the child class is registered in the parent container', function () {
             class ParentService {}
             class ChildService extends ParentService {}