Реализация принципа инверсии управления для JavaScript
|
|
2 months ago | |
|---|---|---|
| .husky | 1 year ago | |
| dist | 2 months ago | |
| src | 2 months ago | |
| .c8rc | 2 years ago | |
| .commitlintrc | 2 years ago | |
| .editorconfig | 2 years ago | |
| .gitignore | 1 year ago | |
| .mocharc.cjs | 1 year ago | |
| .prettierrc | 2 years ago | |
| LICENSE | 4 months ago | |
| README.md | 3 months ago | |
| build-cjs.js | 1 year ago | |
| eslint.config.js | 2 months ago | |
| package.json | 2 months ago | |
| tsconfig.json | 2 years ago |
Реализация паттерна «Сервис-локатор» для JavaScript.
npm install @e22m4u/js-service
Модуль поддерживает ESM и CommonJS стандарты.
ESM
import {Service} from '@e22m4u/js-service';
CommonJS
const {Service} = require('@e22m4u/js-service');
Модуль предлагает классы ServiceContainer и
Service, которые можно использовать как по отдельности, так
и вместе.
ServiceContainer - классическая версия
сервис-локатораService - скрывает создание контейнера и его
распространениеКласс Service удобен, когда приложение имеет
единственную точку входа, которая создается оператором new.
Например, если такой точкой является класс Application, то
мы могли бы унаследовать его от класса Service, и
обращаться к другим сервисам методом getService не заботясь
о создании и хранении их экземпляров.
Кроме того, если другие сервисы так же наследуют от класса
Service, то они могут обращаться друг к другу используя тот
же метод getService, как если бы мы передавали
сервис-контейнер между ними.
Методы:
get(ctor, ...args) получить существующий или новый
экземпляр;getRegistered(ctor, ...args) получить существующий или
новый экземпляр, только если указанный конструктор зарегистрирован в
контейнере, в противном случае выбрасывается ошибка;has(ctor) проверить существование конструктора в
контейнере;add(ctor, ...args) добавить конструктор в
контейнер;use(ctor, ...args) добавить конструктор и создать
экземпляр;set(ctor, service) добавить конструктор и его
экземпляр;getParent() получить родительский
сервис-контейнер;hasParent() проверить наличие родительского
сервис-контейнера;Метод get класса ServiceContainer создает
экземпляр полученного конструктора и сохраняет его для последующих
обращений по принципу "одиночки".
Пример:
import {ServiceContainer} from '@e22m4u/js-service';
// создание контейнера
const container = new ServiceContainer();
// в качестве сервиса используем класс 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 может принимать аргументы конструктора. При
этом, если контейнер уже имеет экземпляр данного конструктора, то он
будет пересоздан с новыми аргументами.
Пример:
const myDate1 = container.get(Date, '2025-01-01'); // создание экземпляра
const myDate2 = container.get(Date); // возврат существующего
const myDate3 = container.get(Date, '2025-05-05'); // пересоздание
console.log(myDate1); // Wed Jan 01 2025 03:00:00
console.log(myDate2); // Wed Jan 01 2025 03:00:00
console.log(myDate3); // Sun May 05 2030 03:00:00
Конструктор ServiceContainer первым параметром принимает
родительский контейнер, который используется как альтернативный, если
конструктор запрашиваемого экземпляра (сервиса) не зарегистрирован в
текущем.
class MyService {}
// создание контейнера и регистрация
// в нем сервиса MyService
const parentContainer = new ServiceContainer();
parentContainer.add(MyService);
// использование предыдущего контейнера в качестве родителя,
// и проверка доступности сервиса MyService
const childContainer = new ServiceContainer(parentContainer);
const hasService = childContainer.has(MyService);
console.log(hasService); // true
Методы:
getService(ctor, ...args) получить существующий или
новый экземпляр;getRegisteredService(ctor, ...args) получить
существующий или новый экземпляр, только если указанный конструктор
зарегистрирован в контейнере, в противном случае выбрасывается
ошибка;hasService(ctor) проверить существование конструктора в
контейнере;addService(ctor, ...args) добавить конструктор в
контейнер;useService(ctor, ...args) добавить конструктор и
создать экземпляр;setService(ctor, service) добавить конструктор и его
экземпляр;Сервисом может являться совершенно любой класс. Однако, если это
наследник класса Service, то такой сервис позволяет
инкапсулировать создание сервис-контейнера, его хранение и передачу
другим сервисам.
Пример:
import {Service} from '@e22m4u/js-service';
// сервис Foo
class Foo extends Service {
method() {
// доступ к сервису Bar
const bar = this.getService(Bar);
// ...
}
}
// сервис Bar
class Bar extends Service {
method() {
// доступ к сервису Foo
const foo = this.getService(Foo);
// ...
}
}
// сервис App (точка входа)
class App extends Service {
method() {
// доступ к сервисам Foo и Bar
const foo = this.getService(Foo);
const bar = this.getService(Bar);
// ...
}
}
const app = new App();
В примере выше мы не заботились о создании сервис-контейнера и его
передачу между сервисами, так как эта логика инкапсулирована в классе
Service и его методе getService
Метод getService обеспечивает существование
единственного экземпляра запрашиваемого сервиса, а не создает каждый раз
новый. Тем не менее при передаче дополнительных аргументов, сервис будет
пересоздан с передачей этих аргументов конструктору.
Пример:
const foo1 = this.getService(Foo, 'arg'); // создание экземпляра
const foo2 = this.getService(Foo); // возврат существующего
console.log(foo1 === foo2); // true
const foo3 = this.getService(Foo, 'arg'); // пересоздание экземпляра
const foo4 = this.getService(Foo); // возврат уже пересозданного
console.log(foo3 === foo4); // true
Класс-сервис расширенный возможностями по отладке.
(см. подробнее @e22m4u/js-debug
раздел «Класс Debuggable»)
import {apiClient} from './path/to/apiClient';
import {DebuggableService} from '@e22m4u/js-service';
process.env['DEBUGGER_NAMESPACE'] = 'myApp';
process.env['DEBUG'] = 'myApp*';
class UserService extends DebuggableService {
async getUserById(userId) {
// получение отладчика для данного метода
// (для каждого вызова генерируется хэш)
const debug = this.getDebuggerFor(this.getUserById);
debug('Fetching user with ID %v...', userId);
try {
const user = await apiClient.get(`/users/${userId}`);
debug.inspect('User data received:', user);
return user;
} catch (error) {
debug('Failed to fetch user. Error: %s', error.message);
throw error;
}
}
}
const userService = new UserService();
await userService.getUserById(123);
// myApp:userService:constructor:a4f1 Instantiated.
// myApp:userService:getUserById:b9c2 Fetching user with ID 123...
// myApp:userService:getUserById:b9c2 User data received:
// myApp:userService:getUserById:b9c2 {
// myApp:userService:getUserById:b9c2 id: 123,
// myApp:userService:getUserById:b9c2 name: 'John Doe',
// myApp:userService:getUserById:b9c2 email: 'john.doe@example.com'
// myApp:userService:getUserById:b9c2 }
npm run test
MIT