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); +});