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

chore: allows to use a parent container

e22m4u 1 год назад
Родитель
Сommit
722b091432
3 измененных файлов с 134 добавлено и 2 удалено
  1. 7 0
      src/service-container.d.ts
  2. 36 2
      src/service-container.js
  3. 91 0
      src/service-container.spec.js

+ 7 - 0
src/service-container.d.ts

@@ -4,6 +4,13 @@ import {Constructor} from './types.js';
  * Service container.
  */
 export declare class ServiceContainer {
+  /**
+   * Constructor.
+   *
+   * @param parent
+   */
+  constructor(parent?: ServiceContainer);
+
   /**
    * Получить существующий или новый экземпляр.
    *

+ 36 - 2
src/service-container.js

@@ -13,6 +13,31 @@ export class ServiceContainer {
    */
   _services = new Map();
 
+  /**
+   * Parent container.
+   *
+   * @type {ServiceContainer}
+   * @private
+   */
+  _parent;
+
+  /**
+   * Constructor.
+   *
+   * @param {ServiceContainer|undefined} parent
+   */
+  constructor(parent = undefined) {
+    if (parent != null) {
+      if (!(parent instanceof ServiceContainer))
+        throw new InvalidArgumentError(
+          'The provided parameter "parent" of ServicesContainer.constructor ' +
+            'must be an instance ServiceContainer, but %v given.',
+          parent,
+        );
+      this._parent = parent;
+    }
+  }
+
   /**
    * Получить существующий или новый экземпляр.
    *
@@ -27,8 +52,15 @@ export class ServiceContainer {
           'a class constructor, but %v given.',
         ctor,
       );
+    // если конструктор отсутствует в текущем
+    // контейнере, то пытаемся получить сервис
+    // из родительского контейнера
+    if (!this._services.has(ctor) && this._parent)
+      return this._parent.get(ctor);
     let service = this._services.get(ctor);
-    // instantiates if no service or args given
+    // если экземпляр сервиса не найден
+    // или переданы аргументы, то создаем
+    // новый экземпляр
     if (!service || args.length) {
       service =
         'prototype' in ctor && ctor.prototype instanceof Service
@@ -50,7 +82,9 @@ export class ServiceContainer {
    * @return {boolean}
    */
   has(ctor) {
-    return this._services.has(ctor);
+    if (this._services.has(ctor)) return true;
+    if (this._parent) return this._parent.has(ctor);
+    return false;
   }
 
   /**

+ 91 - 0
src/service-container.spec.js

@@ -4,6 +4,41 @@ import {format} from '@e22m4u/js-format';
 import {ServiceContainer} from './service-container.js';
 
 describe('ServiceContainer', function () {
+  describe('constructor', function () {
+    it('does not require any arguments', function () {
+      const res = new ServiceContainer();
+      expect(res).to.be.instanceof(ServiceContainer);
+    });
+
+    it('sets the first argument as the parent container', function () {
+      const parent = new ServiceContainer();
+      const container = new ServiceContainer(parent);
+      const res = container['_parent'];
+      expect(res).to.be.eq(parent);
+    });
+
+    it('requires the first argument to be an instance of ServiceContainer', function () {
+      const throwable = v => () => new ServiceContainer(v);
+      const error = v =>
+        format(
+          'The provided parameter "parent" of ServicesContainer.constructor ' +
+            'must be an instance ServiceContainer, but %s given.',
+          v,
+        );
+      expect(throwable('str')).to.throw(error('"str"'));
+      expect(throwable('')).to.throw(error('""'));
+      expect(throwable(10)).to.throw(error('10'));
+      expect(throwable(0)).to.throw(error('0'));
+      expect(throwable(true)).to.throw(error('true'));
+      expect(throwable(false)).to.throw(error('false'));
+      expect(throwable([])).to.throw(error('Array'));
+      expect(throwable({})).to.throw(error('Object'));
+      throwable(undefined)();
+      throwable(null)();
+      throwable(new ServiceContainer())();
+    });
+  });
+
   describe('get', function () {
     it('throws an error if no constructor given', function () {
       const container = new ServiceContainer();
@@ -131,6 +166,25 @@ describe('ServiceContainer', function () {
         expect(executed).to.be.eq(2);
         expect(givenArgs).to.be.eql(['foo', 'bar']);
       });
+
+      it('instantiates from the parent container', function () {
+        class MyService extends Service {}
+        const parent = new ServiceContainer();
+        parent.add(MyService);
+        const child = new ServiceContainer(parent);
+        const res = child.get(MyService);
+        expect(res).to.be.instanceof(MyService);
+      });
+
+      it('returns an instance from the parent container', function () {
+        class MyService extends Service {}
+        const parent = new ServiceContainer();
+        const service = new MyService();
+        parent.set(MyService, service);
+        const child = new ServiceContainer(parent);
+        const res = child.get(MyService);
+        expect(res).to.be.eq(service);
+      });
     });
 
     describe('non-Service', function () {
@@ -226,6 +280,25 @@ describe('ServiceContainer', function () {
         expect(executed).to.be.eq(2);
         expect(givenArgs).to.be.eql(['foo', 'bar']);
       });
+
+      it('instantiates from the parent container', function () {
+        class MyService {}
+        const parent = new ServiceContainer();
+        parent.add(MyService);
+        const child = new ServiceContainer(parent);
+        const res = child.get(MyService);
+        expect(res).to.be.instanceof(MyService);
+      });
+
+      it('returns an instance from the parent container', function () {
+        class MyService {}
+        const parent = new ServiceContainer();
+        const service = new MyService();
+        parent.set(MyService, service);
+        const child = new ServiceContainer(parent);
+        const res = child.get(MyService);
+        expect(res).to.be.eq(service);
+      });
     });
   });
 
@@ -246,6 +319,15 @@ describe('ServiceContainer', function () {
         container.add(MyService);
         expect(container.has(MyService)).to.be.true;
       });
+
+      it('returns true if the parent container has the given constructor', function () {
+        class MyService extends Service {}
+        const parent = new ServiceContainer();
+        parent.add(MyService);
+        const child = new ServiceContainer(parent);
+        const res = child.has(MyService);
+        expect(res).to.be.true;
+      });
     });
 
     describe('non-Service', function () {
@@ -264,6 +346,15 @@ describe('ServiceContainer', function () {
         container.add(MyService);
         expect(container.has(MyService)).to.be.true;
       });
+
+      it('returns true if the parent container has the given constructor', function () {
+        class MyService {}
+        const parent = new ServiceContainer();
+        parent.add(MyService);
+        const child = new ServiceContainer(parent);
+        const res = child.has(MyService);
+        expect(res).to.be.true;
+      });
     });
   });