service-container.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  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<any, any>}
  23. * @private
  24. */
  25. _services = new Map();
  26. /**
  27. * Parent container.
  28. *
  29. * @type {ServiceContainer}
  30. * @private
  31. */
  32. _parent;
  33. /**
  34. * Constructor.
  35. *
  36. * @param {ServiceContainer|undefined} parent
  37. */
  38. constructor(parent = undefined) {
  39. if (parent != null) {
  40. if (!(parent instanceof ServiceContainer))
  41. throw new InvalidArgumentError(
  42. 'The provided parameter "parent" of ServicesContainer.constructor ' +
  43. 'must be an instance ServiceContainer, but %v given.',
  44. parent,
  45. );
  46. this._parent = parent;
  47. }
  48. }
  49. /**
  50. * Получить родительский сервис-контейнер или выбросить ошибку.
  51. *
  52. * @returns {ServiceContainer}
  53. */
  54. getParent() {
  55. if (!this._parent)
  56. throw new InvalidArgumentError('The service container has no parent.');
  57. return this._parent;
  58. }
  59. /**
  60. * Проверить наличие родительского сервис-контейнера.
  61. *
  62. * @returns {boolean}
  63. */
  64. hasParent() {
  65. return Boolean(this._parent);
  66. }
  67. /**
  68. * Получить существующий или новый экземпляр.
  69. *
  70. * @param {*} ctor
  71. * @param {*} args
  72. * @returns {*}
  73. */
  74. get(ctor, ...args) {
  75. if (!ctor || typeof ctor !== 'function')
  76. throw new InvalidArgumentError(
  77. 'The first argument of ServicesContainer.get must be ' +
  78. 'a class constructor, but %v given.',
  79. ctor,
  80. );
  81. // если конструктор отсутствует в текущем
  82. // контейнере, но имеется в родительском,
  83. // то запрашиваем сервис именно из него
  84. if (!this._services.has(ctor) && this._parent && this._parent.has(ctor)) {
  85. return this._parent.get(ctor);
  86. }
  87. let service = this._services.get(ctor);
  88. // если экземпляр сервиса не найден,
  89. // то пытаемся найти его наследников
  90. if (!service) {
  91. const ctors = this._services.keys();
  92. const inheritedCtor = ctors.find(v => v.prototype instanceof ctor);
  93. if (inheritedCtor) {
  94. service = this._services.get(inheritedCtor);
  95. // если наследник найден, но экземпляр отсутствует,
  96. // то подменяем конструктор наследником, чтобы
  97. // экземпляр создавался с помощью него
  98. ctor = inheritedCtor;
  99. }
  100. }
  101. // если экземпляр сервиса не найден
  102. // или переданы аргументы, то создаем
  103. // новый экземпляр
  104. if (!service || args.length) {
  105. service =
  106. Array.isArray(ctor.kinds) && ctor.kinds.includes(SERVICE_CLASS_NAME)
  107. ? new ctor(this, ...args)
  108. : new ctor(...args);
  109. this._services.set(ctor, service);
  110. // instantiates from a factory function
  111. } else if (typeof service === 'function') {
  112. service = service();
  113. this._services.set(ctor, service);
  114. }
  115. return service;
  116. }
  117. /**
  118. * Проверить существование конструктора в контейнере.
  119. *
  120. * @param {*} ctor
  121. * @returns {boolean}
  122. */
  123. has(ctor) {
  124. if (this._services.has(ctor)) return true;
  125. if (this._parent) return this._parent.has(ctor);
  126. // если не удалось найти указанный конструктор,
  127. // то пытаемся найти его наследника
  128. const ctors = this._services.keys();
  129. const inheritedCtor = ctors.find(v => v.prototype instanceof ctor);
  130. if (inheritedCtor) return true;
  131. return false;
  132. }
  133. /**
  134. * Добавить конструктор в контейнер.
  135. *
  136. * @param {*} ctor
  137. * @param {*} args
  138. * @returns {this}
  139. */
  140. add(ctor, ...args) {
  141. if (!ctor || typeof ctor !== 'function')
  142. throw new InvalidArgumentError(
  143. 'The first argument of ServicesContainer.add must be ' +
  144. 'a class constructor, but %v given.',
  145. ctor,
  146. );
  147. const factory = () =>
  148. Array.isArray(ctor.kinds) && ctor.kinds.includes(SERVICE_CLASS_NAME)
  149. ? new ctor(this, ...args)
  150. : new ctor(...args);
  151. this._services.set(ctor, factory);
  152. return this;
  153. }
  154. /**
  155. * Добавить конструктор и создать экземпляр.
  156. *
  157. * @param {*} ctor
  158. * @param {*} args
  159. * @returns {this}
  160. */
  161. use(ctor, ...args) {
  162. if (!ctor || typeof ctor !== 'function')
  163. throw new InvalidArgumentError(
  164. 'The first argument of ServicesContainer.use must be ' +
  165. 'a class constructor, but %v given.',
  166. ctor,
  167. );
  168. const service =
  169. Array.isArray(ctor.kinds) && ctor.kinds.includes(SERVICE_CLASS_NAME)
  170. ? new ctor(this, ...args)
  171. : new ctor(...args);
  172. this._services.set(ctor, service);
  173. return this;
  174. }
  175. /**
  176. * Добавить конструктор и связанный экземпляр.
  177. *
  178. * @param {*} ctor
  179. * @param {*} service
  180. * @returns {this}
  181. */
  182. set(ctor, service) {
  183. if (!ctor || typeof ctor !== 'function')
  184. throw new InvalidArgumentError(
  185. 'The first argument of ServicesContainer.set must be ' +
  186. 'a class constructor, but %v given.',
  187. ctor,
  188. );
  189. if (!service || typeof service !== 'object' || Array.isArray(service))
  190. throw new InvalidArgumentError(
  191. 'The second argument of ServicesContainer.set must be ' +
  192. 'an Object, but %v given.',
  193. service,
  194. );
  195. this._services.set(ctor, service);
  196. return this;
  197. }
  198. }