From 9c71b8b54f0bbada0cc81d30d3a4d2c112d51fba Mon Sep 17 00:00:00 2001 From: na2na-p Date: Wed, 7 Feb 2024 23:38:36 +0900 Subject: [PATCH] =?UTF-8?q?singleton=E3=81=AE=E6=B1=8E=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../singleton/internal/singleton.func.ts | 27 +++-- .../singleton/internal/singleton.spec.ts | 112 ++++++++++++++++++ 2 files changed, 132 insertions(+), 7 deletions(-) diff --git a/src/features/others/singleton/internal/singleton.func.ts b/src/features/others/singleton/internal/singleton.func.ts index ff7774fc..356098c1 100644 --- a/src/features/others/singleton/internal/singleton.func.ts +++ b/src/features/others/singleton/internal/singleton.func.ts @@ -1,9 +1,22 @@ -type Getter = () => T; -type Factory = () => T; +type Constructor = new (...args: Args) => T; +type FactoryFunction = (...args: Args) => T; -export const singleton = (factory: Factory): Getter => - ((): Getter => { - let memo: T | null = null; +function isClass( + func: Constructor | FactoryFunction +): func is Constructor { + return /^class\s/.test(Function.prototype.toString.call(func)); +} - return () => (memo ? memo : (memo = factory())); - })(); +export function singleton( + factoryOrConstructor: Constructor | FactoryFunction +): (...args: Args) => T { + let instance: T | null = null; + return (...args: Args) => { + if (!instance) { + instance = isClass(factoryOrConstructor) + ? new factoryOrConstructor(...args) + : factoryOrConstructor(...args); + } + return instance; + }; +} diff --git a/src/features/others/singleton/internal/singleton.spec.ts b/src/features/others/singleton/internal/singleton.spec.ts index 3d28911e..3921ce4a 100644 --- a/src/features/others/singleton/internal/singleton.spec.ts +++ b/src/features/others/singleton/internal/singleton.spec.ts @@ -17,3 +17,115 @@ it('with singleton, identical', () => { expect(x).toBe(y); }); + +it('not identical with args', () => { + const factory = (a: number) => ({ a }); + + const x = factory(1); + const y = factory(1); + + expect(x).not.toBe(y); +}); + +it('with singleton, identical with args', () => { + const factory = (a: number) => ({ a }); + const getter = singleton(factory); + + const x = getter(1); + const y = getter(1); + + expect(x).toBe(y); +}); + +it('not identical with multiple args', () => { + const factory = (a: number, b: number) => ({ a, b }); + + const x = factory(1, 2); + const y = factory(1, 2); + + expect(x).not.toBe(y); +}); + +it('with singleton, identical with multiple args', () => { + const factory = (a: number, b: number) => ({ a, b }); + const getter = singleton(factory); + + const x = getter(1, 2); + const y = getter(1, 2); + + expect(x).toBe(y); +}); + +it('not identical with class', () => { + class TestClass {} + + const x = new TestClass(); + const y = new TestClass(); + + expect(x).not.toBe(y); +}); + +it('with singleton, identical with class', () => { + class TestClass {} + + const getter = singleton(TestClass); + + const x = getter(); + const y = getter(); + + expect(x).toBe(y); +}); + +it('not identical with class and args', () => { + class TestClass { + constructor(public a: number) {} + } + + const x = new TestClass(1); + const y = new TestClass(1); + + expect(x).not.toBe(y); +}); + +it('with singleton, identical with class and args', () => { + class TestClass { + constructor(public a: number) {} + } + + const getter = singleton(TestClass); + + const x = getter(1); + const y = getter(1); + + expect(x).toBe(y); +}); + +it('not identical with class and multiple args', () => { + class TestClass { + constructor( + public a: number, + public b: number + ) {} + } + + const x = new TestClass(1, 2); + const y = new TestClass(1, 2); + + expect(x).not.toBe(y); +}); + +it('with singleton, identical with class and multiple args', () => { + class TestClass { + constructor( + public a: number, + public b: number + ) {} + } + + const getter = singleton(TestClass); + + const x = getter(1, 2); + const y = getter(1, 2); + + expect(x).toBe(y); +});