Browse Source

fix: inherited class lookup

e22m4u 2 months ago
parent
commit
61e93d26f2
3 changed files with 278 additions and 60 deletions
  1. 10 8
      dist/cjs/index.cjs
  2. 31 17
      src/service-container.js
  3. 237 35
      src/service-container.spec.js

+ 10 - 8
dist/cjs/index.cjs

@@ -101,17 +101,19 @@ var _ServiceContainer = class _ServiceContainer {
         "The first argument of ServicesContainer.get must be a class constructor, but %v given.",
         ctor
       );
-    if (!this._services.has(ctor) && this._parent && this._parent.has(ctor)) {
-      return this._parent.get(ctor);
-    }
+    const isCtorRegistered = this._services.has(ctor);
     let service = this._services.get(ctor);
+    let inheritedCtor = void 0;
     if (!service) {
       const ctors = this._services.keys();
-      const inheritedCtor = ctors.find((v) => v.prototype instanceof ctor);
-      if (inheritedCtor) {
-        service = this._services.get(inheritedCtor);
-        ctor = inheritedCtor;
-      }
+      const inheritedCtor2 = ctors.find((v) => v.prototype instanceof ctor);
+      if (inheritedCtor2) service = this._services.get(inheritedCtor2);
+    }
+    if (!service && !isCtorRegistered && !inheritedCtor && this._parent && this._parent.has(ctor)) {
+      return this._parent.get(ctor, ...args);
+    }
+    if (!service && !isCtorRegistered && inheritedCtor) {
+      ctor = inheritedCtor;
     }
     if (!service || args.length) {
       service = Array.isArray(ctor.kinds) && ctor.kinds.includes(SERVICE_CLASS_NAME) ? new ctor(this, ...args) : new ctor(...args);

+ 31 - 17
src/service-container.js

@@ -86,29 +86,43 @@ export class ServiceContainer {
           'a class constructor, but %v given.',
         ctor,
       );
-    // если конструктор отсутствует в текущем
-    // контейнере, но имеется в родительском,
-    // то запрашиваем сервис именно из него
-    if (!this._services.has(ctor) && this._parent && this._parent.has(ctor)) {
-      return this._parent.get(ctor);
-    }
+    const isCtorRegistered = this._services.has(ctor);
     let service = this._services.get(ctor);
+    let inheritedCtor = undefined;
     // если экземпляр сервиса не найден,
-    // то пытаемся найти его наследников
+    // то выполняется поиск его наследника
     if (!service) {
       const ctors = this._services.keys();
       const inheritedCtor = ctors.find(v => v.prototype instanceof ctor);
-      if (inheritedCtor) {
-        service = this._services.get(inheritedCtor);
-        // если наследник найден, но экземпляр отсутствует,
-        // то подменяем конструктор наследником, чтобы
-        // экземпляр создавался с помощью него
-        ctor = inheritedCtor;
-      }
+      if (inheritedCtor) service = this._services.get(inheritedCtor);
+    }
+    // если
+    //   ни экземпляр сервиса (или экземпляр наследника),
+    //   ни указанный конструктор,
+    //   ни конструктор наследника
+    // не зарегистрированы в текущем контейнере, но определен родительский
+    // контейнер, где зарегистрирован конструктор запрашиваемого сервиса,
+    // то поиск передается родителю
+    if (
+      !service &&
+      !isCtorRegistered &&
+      !inheritedCtor &&
+      this._parent &&
+      this._parent.has(ctor)
+    ) {
+      return this._parent.get(ctor, ...args);
+    }
+    // если
+    //   ни экземпляр сервиса (или экземпляр наследника),
+    //   ни указанный конструктор
+    // не зарегистрированы, но найден конструктор наследника,
+    // то для создания экземпляра будет использован найденный
+    // конструктор наследника
+    if (!service && !isCtorRegistered && inheritedCtor) {
+      ctor = inheritedCtor;
     }
-    // если экземпляр сервиса не найден
-    // или переданы аргументы, то создаем
-    // новый экземпляр
+    // если экземпляр сервиса не найден или переданы
+    // аргументы, то создается новый экземпляр
     if (!service || args.length) {
       service =
         Array.isArray(ctor.kinds) && ctor.kinds.includes(SERVICE_CLASS_NAME)

+ 237 - 35
src/service-container.spec.js

@@ -240,6 +240,86 @@ describe('ServiceContainer', function () {
           expect(childService).to.be.instanceof(ChildService);
           expect(childService).to.be.not.eq(parentService);
         });
+
+        it('should prioritize the given registered constructor even its inherited class is registered too', function () {
+          class ParentService extends Service {}
+          class ChildService extends ParentService {}
+          const container = new ServiceContainer();
+          container.add(ParentService);
+          container.add(ChildService);
+          const res1 = container.get(ParentService);
+          const res2 = container.get(ChildService);
+          expect(res1).to.be.instanceOf(ParentService);
+          expect(res2).to.be.instanceOf(ChildService);
+          expect(res1).to.be.not.eq(res2);
+        });
+
+        describe('when the 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 {}
+              class ChildService extends ParentService {}
+              const parentContainer = new ServiceContainer();
+              const childContainer = new ServiceContainer(parentContainer);
+              parentContainer.add(ChildService);
+              const inst = new ChildService();
+              childContainer.set(ChildService, inst);
+              const res = childContainer.get(ParentService);
+              expect(res).to.be.eq(inst);
+            });
+
+            it('should create its own instance when the service constructor is registered', function () {
+              class ParentService extends Service {
+                constructor(container, value) {
+                  super(container);
+                  this.value = value;
+                }
+              }
+              class ChildService extends ParentService {}
+              const parentContainer = new ServiceContainer();
+              const childContainer = new ServiceContainer(parentContainer);
+              parentContainer.add(ChildService, 1);
+              childContainer.add(ChildService, 2);
+              const res = childContainer.get(ParentService);
+              expect(res.value).to.be.eq(2);
+            });
+          });
+
+          describe('when a parent container has an existing instance', function () {
+            it('should prioritize its own existing instance when available', function () {
+              class ParentService extends Service {}
+              class ChildService extends ParentService {}
+              const parentContainer = new ServiceContainer();
+              const childContainer = new ServiceContainer(parentContainer);
+              const inst1 = new ChildService();
+              const inst2 = new ChildService();
+              parentContainer.set(ChildService, inst1);
+              const res1 = childContainer.get(ParentService);
+              expect(res1).to.be.eq(inst1);
+              childContainer.set(ChildService, inst2);
+              const res2 = childContainer.get(ParentService);
+              expect(res2).to.be.eq(inst2);
+            });
+
+            it('should create its own instance when the service constructor is registered', function () {
+              class ParentService extends Service {
+                constructor(container, value) {
+                  super(container);
+                  this.value = value;
+                }
+              }
+              class ChildService extends ParentService {}
+              const parentContainer = new ServiceContainer();
+              const childContainer = new ServiceContainer(parentContainer);
+              parentContainer.use(ChildService, 1);
+              const res1 = childContainer.get(ParentService);
+              expect(res1.value).to.be.eq(1);
+              childContainer.add(ChildService, 2);
+              const res2 = childContainer.get(ParentService);
+              expect(res2.value).to.be.eq(2);
+            });
+          });
+        });
       });
 
       describe('in case of a parent container', function () {
@@ -455,6 +535,84 @@ describe('ServiceContainer', function () {
           expect(childService).to.be.instanceof(ChildService);
           expect(childService).to.be.not.eq(parentService);
         });
+
+        it('should prioritize the given registered constructor even its inherited class is registered too', function () {
+          class ParentService {}
+          class ChildService extends ParentService {}
+          const container = new ServiceContainer();
+          container.add(ParentService);
+          container.add(ChildService);
+          const res1 = container.get(ParentService);
+          const res2 = container.get(ChildService);
+          expect(res1).to.be.instanceOf(ParentService);
+          expect(res2).to.be.instanceOf(ChildService);
+          expect(res1).to.be.not.eq(res2);
+        });
+
+        describe('when the 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 {}
+              class ChildService extends ParentService {}
+              const parentContainer = new ServiceContainer();
+              const childContainer = new ServiceContainer(parentContainer);
+              parentContainer.add(ChildService);
+              const inst = new ChildService();
+              childContainer.set(ChildService, inst);
+              const res = childContainer.get(ParentService);
+              expect(res).to.be.eq(inst);
+            });
+
+            it('should create its own instance when the service constructor is registered', function () {
+              class ParentService {
+                constructor(value) {
+                  this.value = value;
+                }
+              }
+              class ChildService extends ParentService {}
+              const parentContainer = new ServiceContainer();
+              const childContainer = new ServiceContainer(parentContainer);
+              parentContainer.add(ChildService, 1);
+              childContainer.add(ChildService, 2);
+              const res = childContainer.get(ParentService);
+              expect(res.value).to.be.eq(2);
+            });
+          });
+
+          describe('when a parent container has an existing instance', function () {
+            it('should prioritize its own existing instance when available', function () {
+              class ParentService {}
+              class ChildService extends ParentService {}
+              const parentContainer = new ServiceContainer();
+              const childContainer = new ServiceContainer(parentContainer);
+              const inst1 = new ChildService();
+              const inst2 = new ChildService();
+              parentContainer.set(ChildService, inst1);
+              const res1 = childContainer.get(ParentService);
+              expect(res1).to.be.eq(inst1);
+              childContainer.set(ChildService, inst2);
+              const res2 = childContainer.get(ParentService);
+              expect(res2).to.be.eq(inst2);
+            });
+
+            it('should create its own instance when the service constructor is registered', function () {
+              class ParentService {
+                constructor(value) {
+                  this.value = value;
+                }
+              }
+              class ChildService extends ParentService {}
+              const parentContainer = new ServiceContainer();
+              const childContainer = new ServiceContainer(parentContainer);
+              parentContainer.use(ChildService, 1);
+              const res1 = childContainer.get(ParentService);
+              expect(res1.value).to.be.eq(1);
+              childContainer.add(ChildService, 2);
+              const res2 = childContainer.get(ParentService);
+              expect(res2.value).to.be.eq(2);
+            });
+          });
+        });
       });
 
       describe('in case of a parent container', function () {
@@ -596,44 +754,46 @@ describe('ServiceContainer', function () {
           expect(res).to.be.false;
         });
 
-        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 {}
-          const parentContainer = new ServiceContainer();
-          const childContainer = new ServiceContainer(parentContainer);
-          parentContainer.add(ChildService);
-          const res = childContainer.has(ParentService);
-          expect(res).to.be.true;
-        });
+        describe('when the 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 {}
+            const parentContainer = new ServiceContainer();
+            const childContainer = new ServiceContainer(parentContainer);
+            parentContainer.add(ChildService);
+            const res = childContainer.has(ParentService);
+            expect(res).to.be.true;
+          });
 
-        it('returns false for the child container if the parent class is registered in the parent container', function () {
-          class ParentService extends Service {}
-          class ChildService extends ParentService {}
-          const parentContainer = new ServiceContainer();
-          const childContainer = new ServiceContainer(parentContainer);
-          parentContainer.add(ParentService);
-          const res = childContainer.has(ChildService);
-          expect(res).to.be.false;
-        });
+          it('returns false for the child container if the parent class is registered in the parent container', function () {
+            class ParentService extends Service {}
+            class ChildService extends ParentService {}
+            const parentContainer = new ServiceContainer();
+            const childContainer = new ServiceContainer(parentContainer);
+            parentContainer.add(ParentService);
+            const res = childContainer.has(ChildService);
+            expect(res).to.be.false;
+          });
 
-        it('returns true for the child container if the child class is registered in the child container', function () {
-          class ParentService extends Service {}
-          class ChildService extends ParentService {}
-          const parentContainer = new ServiceContainer();
-          const childContainer = new ServiceContainer(parentContainer);
-          childContainer.add(ChildService);
-          const res = childContainer.has(ParentService);
-          expect(res).to.be.true;
-        });
+          it('returns true for the child container if the child class is registered in the child container', function () {
+            class ParentService extends Service {}
+            class ChildService extends ParentService {}
+            const parentContainer = new ServiceContainer();
+            const childContainer = new ServiceContainer(parentContainer);
+            childContainer.add(ChildService);
+            const res = childContainer.has(ParentService);
+            expect(res).to.be.true;
+          });
 
-        it('returns false for the child container if the parent class is registered in the child container', function () {
-          class ParentService extends Service {}
-          class ChildService extends ParentService {}
-          const parentContainer = new ServiceContainer();
-          const childContainer = new ServiceContainer(parentContainer);
-          childContainer.add(ParentService);
-          const res = childContainer.has(ChildService);
-          expect(res).to.be.false;
+          it('returns false for the child container if the parent class is registered in the child container', function () {
+            class ParentService extends Service {}
+            class ChildService extends ParentService {}
+            const parentContainer = new ServiceContainer();
+            const childContainer = new ServiceContainer(parentContainer);
+            childContainer.add(ParentService);
+            const res = childContainer.has(ChildService);
+            expect(res).to.be.false;
+          });
         });
       });
 
@@ -684,6 +844,48 @@ describe('ServiceContainer', function () {
           const res = container.has(ChildService);
           expect(res).to.be.false;
         });
+
+        describe('when the 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 {}
+            const parentContainer = new ServiceContainer();
+            const childContainer = new ServiceContainer(parentContainer);
+            parentContainer.add(ChildService);
+            const res = childContainer.has(ParentService);
+            expect(res).to.be.true;
+          });
+
+          it('returns false for the child container if the parent class is registered in the parent container', function () {
+            class ParentService {}
+            class ChildService extends ParentService {}
+            const parentContainer = new ServiceContainer();
+            const childContainer = new ServiceContainer(parentContainer);
+            parentContainer.add(ParentService);
+            const res = childContainer.has(ChildService);
+            expect(res).to.be.false;
+          });
+
+          it('returns true for the child container if the child class is registered in the child container', function () {
+            class ParentService {}
+            class ChildService extends ParentService {}
+            const parentContainer = new ServiceContainer();
+            const childContainer = new ServiceContainer(parentContainer);
+            childContainer.add(ChildService);
+            const res = childContainer.has(ParentService);
+            expect(res).to.be.true;
+          });
+
+          it('returns false for the child container if the parent class is registered in the child container', function () {
+            class ParentService {}
+            class ChildService extends ParentService {}
+            const parentContainer = new ServiceContainer();
+            const childContainer = new ServiceContainer(parentContainer);
+            childContainer.add(ParentService);
+            const res = childContainer.has(ChildService);
+            expect(res).to.be.false;
+          });
+        });
       });
 
       describe('in case of a parent container', function () {