service-container.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271
  1. import {SERVICE_CLASS_NAME} from './service.js';
  2. import {InvalidArgumentError} from './errors/index.js';
  3. /**
  4. * Service class name.
  5. *
  6. * @type {string}
  7. */
  8. export const SERVICE_CONTAINER_CLASS_NAME = 'ServiceContainer';
  9. /**
  10. * Service container.
  11. */
  12. export class ServiceContainer {
  13. /**
  14. * Kinds.
  15. *
  16. * @type {string[]}
  17. */
  18. static kinds = [SERVICE_CONTAINER_CLASS_NAME];
  19. /**
  20. * Services map.
  21. *
  22. * @type {Map<*, *>}
  23. */
  24. _services = new Map();
  25. /**
  26. * Parent container.
  27. *
  28. * @type {ServiceContainer}
  29. */
  30. _parent;
  31. /**
  32. * Constructor.
  33. *
  34. * @param {ServiceContainer|undefined} parent
  35. */
  36. constructor(parent = undefined) {
  37. if (parent != null) {
  38. if (!(parent instanceof ServiceContainer))
  39. throw new InvalidArgumentError(
  40. 'The provided parameter "parent" of ServicesContainer.constructor ' +
  41. 'must be an instance ServiceContainer, but %v given.',
  42. parent,
  43. );
  44. this._parent = parent;
  45. }
  46. }
  47. /**
  48. * Получить родительский сервис-контейнер или выбросить ошибку.
  49. *
  50. * @returns {ServiceContainer}
  51. */
  52. getParent() {
  53. if (!this._parent)
  54. throw new InvalidArgumentError('The service container has no parent.');
  55. return this._parent;
  56. }
  57. /**
  58. * Проверить наличие родительского сервис-контейнера.
  59. *
  60. * @returns {boolean}
  61. */
  62. hasParent() {
  63. return Boolean(this._parent);
  64. }
  65. /**
  66. * Получить существующий или новый экземпляр.
  67. *
  68. * @param {*} ctor
  69. * @param {*} args
  70. * @returns {*}
  71. */
  72. get(ctor, ...args) {
  73. if (!ctor || typeof ctor !== 'function')
  74. throw new InvalidArgumentError(
  75. 'The first argument of ServicesContainer.get must be ' +
  76. 'a class constructor, but %v given.',
  77. ctor,
  78. );
  79. const isCtorRegistered = this._services.has(ctor);
  80. let service = this._services.get(ctor);
  81. let inheritedCtor = undefined;
  82. // если экземпляр сервиса не найден,
  83. // то выполняется поиск его наследника
  84. if (!service) {
  85. const ctors = Array.from(this._services.keys());
  86. inheritedCtor = ctors.find(v => v.prototype instanceof ctor);
  87. if (inheritedCtor) service = this._services.get(inheritedCtor);
  88. }
  89. // если
  90. // ни экземпляр сервиса (или экземпляр наследника),
  91. // ни указанный конструктор,
  92. // ни конструктор наследника
  93. // не зарегистрированы в текущем контейнере, но определен родительский
  94. // контейнер, где зарегистрирован конструктор запрашиваемого сервиса,
  95. // то поиск передается родителю
  96. if (
  97. !service &&
  98. !isCtorRegistered &&
  99. !inheritedCtor &&
  100. this._parent &&
  101. this._parent.has(ctor)
  102. ) {
  103. return this._parent.get(ctor, ...args);
  104. }
  105. // если указанный конструктор не зарегистрирован,
  106. // но найден конструктор наследника, то для создания
  107. // экземпляра будет использован данный конструктор
  108. // наследника
  109. if (!isCtorRegistered && inheritedCtor) {
  110. ctor = inheritedCtor;
  111. }
  112. // если экземпляр сервиса не найден или переданы
  113. // аргументы, то создается новый экземпляр
  114. if (!service || args.length) {
  115. service =
  116. Array.isArray(ctor.kinds) && ctor.kinds.includes(SERVICE_CLASS_NAME)
  117. ? new ctor(this, ...args)
  118. : new ctor(...args);
  119. this._services.set(ctor, service);
  120. // instantiates from a factory function
  121. } else if (typeof service === 'function') {
  122. service = service();
  123. this._services.set(ctor, service);
  124. }
  125. return service;
  126. }
  127. /**
  128. * Получить существующий или новый экземпляр,
  129. * только если конструктор зарегистрирован.
  130. *
  131. * @param {*} ctor
  132. * @param {*} args
  133. * @returns {*}
  134. */
  135. getRegistered(ctor, ...args) {
  136. if (!this.has(ctor))
  137. throw new InvalidArgumentError(
  138. 'The constructor %v is not registered.',
  139. ctor,
  140. );
  141. return this.get(ctor, ...args);
  142. }
  143. /**
  144. * Проверить существование конструктора в контейнере.
  145. *
  146. * @param {*} ctor
  147. * @returns {boolean}
  148. */
  149. has(ctor) {
  150. if (this._services.has(ctor)) return true;
  151. // если не удалось найти указанный конструктор,
  152. // то выполняется поиск его наследника
  153. const ctors = Array.from(this._services.keys());
  154. const inheritedCtor = ctors.find(v => v.prototype instanceof ctor);
  155. if (inheritedCtor) return true;
  156. // если определен родительский контейнер,
  157. // то выполняется поиск в родителе
  158. if (this._parent) return this._parent.has(ctor);
  159. return false;
  160. }
  161. /**
  162. * Добавить конструктор в контейнер.
  163. *
  164. * @param {*} ctor
  165. * @param {*} args
  166. * @returns {this}
  167. */
  168. add(ctor, ...args) {
  169. if (!ctor || typeof ctor !== 'function')
  170. throw new InvalidArgumentError(
  171. 'The first argument of ServicesContainer.add must be ' +
  172. 'a class constructor, but %v given.',
  173. ctor,
  174. );
  175. const factory = () =>
  176. Array.isArray(ctor.kinds) && ctor.kinds.includes(SERVICE_CLASS_NAME)
  177. ? new ctor(this, ...args)
  178. : new ctor(...args);
  179. this._services.set(ctor, factory);
  180. return this;
  181. }
  182. /**
  183. * Добавить конструктор и создать экземпляр.
  184. *
  185. * @param {*} ctor
  186. * @param {*} args
  187. * @returns {this}
  188. */
  189. use(ctor, ...args) {
  190. if (!ctor || typeof ctor !== 'function')
  191. throw new InvalidArgumentError(
  192. 'The first argument of ServicesContainer.use must be ' +
  193. 'a class constructor, but %v given.',
  194. ctor,
  195. );
  196. const service =
  197. Array.isArray(ctor.kinds) && ctor.kinds.includes(SERVICE_CLASS_NAME)
  198. ? new ctor(this, ...args)
  199. : new ctor(...args);
  200. this._services.set(ctor, service);
  201. return this;
  202. }
  203. /**
  204. * Добавить конструктор и связанный экземпляр.
  205. *
  206. * @param {*} ctor
  207. * @param {*} service
  208. * @returns {this}
  209. */
  210. set(ctor, service) {
  211. if (!ctor || typeof ctor !== 'function')
  212. throw new InvalidArgumentError(
  213. 'The first argument of ServicesContainer.set must be ' +
  214. 'a class constructor, but %v given.',
  215. ctor,
  216. );
  217. if (!service || typeof service !== 'object' || Array.isArray(service))
  218. throw new InvalidArgumentError(
  219. 'The second argument of ServicesContainer.set must be ' +
  220. 'an Object, but %v given.',
  221. service,
  222. );
  223. this._services.set(ctor, service);
  224. return this;
  225. }
  226. /**
  227. * Найти сервис удовлетворяющий условию.
  228. *
  229. * @param {Function} predicate
  230. * @param {boolean} noParent
  231. * @returns {*}
  232. */
  233. find(predicate, noParent = false) {
  234. if (typeof predicate !== 'function') {
  235. throw new InvalidArgumentError(
  236. 'The first argument of ServiceContainer.find ' +
  237. 'must be a function, but %v given.',
  238. predicate,
  239. );
  240. }
  241. const isRecursive = !noParent;
  242. let currentContainer = this;
  243. do {
  244. for (const ctor of currentContainer._services.keys()) {
  245. if (predicate(ctor, currentContainer) === true) {
  246. return this.get(ctor);
  247. }
  248. }
  249. if (isRecursive && currentContainer.hasParent()) {
  250. currentContainer = currentContainer.getParent();
  251. } else {
  252. currentContainer = null;
  253. }
  254. } while (currentContainer);
  255. return undefined;
  256. }
  257. }