skip to Main Content

I have class decorator which ensures that the class remains a singleton.

interface Type<T = any> extends Function {
    new (...args: any[]): T;
}

function Singleton() {
    let instance: Object;
    return function <SingletonFunction extends Type>(
        constructor: SingletonFunction
    ) {
        return <SingletonFunction>class {
            constructor(...args: any[]) {
                if (instance) {
                    return instance;
                } else {
                    instance = new constructor(...args);
                    Object.setPrototypeOf(instance, constructor);
                    return instance;
                }
            }
        };
    };
}

When I use this decorator on a class like this,

@Singleton()
class TestSingleton {
    private field: any;
    private field2 = Math.random();

    constructor(args: any[]) {
        this.field = args.reduce((acc, ele) => {
            return acc + ele;
        }, 0);
    }
}

const x = new TestSingleton([1, 2, 3, 4, 5, 6]);
const y = new TestSingleton([6, 7, 8, 9, 1]);

console.log(x === y); // true
console.log(x instanceof TestSingleton); // false

The instanceof operator does not work, I can not figure out what I am missing here.

2

Answers


  1. Cómo yo voy empezando en esto quisiera saber para que es el decorado ,el síguele ton,args,any,singeletonfunction,super,this,elese ,class

    Login or Signup to reply.
  2. Ignoring the attempt to Object.setPrototypeOf(instance, constructor), the problem is with instance = new constructor(...args);. This calls the old (wrapped, decorated) constructor, which returns an object inheriting from the old .prototype. However your decorator completely replaces the TestSingleton class (which it must, to overwrite the constructor), and instanceof TestSingleton will therefor check inheritance against the new (returned) TestSingleton.prototype object.

    The conventional approach is to just return a class that inherits from the original (decorated) class, and have the constructor return instances of the subclass (as it creates them by default, if you didn’t have your singleton instance code overriding the constructor return value).

    However, this creates an unnecessary extra prototype object and unnecessary extra prototype chain link. I would rather recommend to return a class (or simple function) that has exactly the same prototype as the decorated one:

    function Singleton<Args extends any[], Res extends object>(
        constructor: { new (...args: Args): Res }
    ) {
        let instance: Res;
        function newConstructor(...args: Args) {
            if (new.target != newConstructor) throw new Error('not supported');
            if (instance) {
                return instance;
            } else {
                instance = new constructor(...args);
                return instance;
            }
        };
        newConstructor.prototype = constructor.prototype;
    //  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
        newConstructor.prototype.constructor = newConstructor; // properly hide the decorated original constructor
        return newConstructor as unknown as { new (...args: Args): Res };
    }
    
    @Singleton
    class TestSingleton {
        …
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search