Skip to content

Instantly share code, notes, and snippets.

@422404
Last active June 13, 2018 22:44
Show Gist options
  • Save 422404/e3a6f56e107ad8a5d51ee7a815f2ec91 to your computer and use it in GitHub Desktop.
Save 422404/e3a6f56e107ad8a5d51ee7a815f2ec91 to your computer and use it in GitHub Desktop.
Singletonify™ a class using Typescript decorators

Remember to enable experimentalDecorators in your tsconfig.json !

import Singleton from './singletonify'
@Singleton()
class MyStrictSingleton {}
@Singleton(false)
class MySingleton {}
console.log('MyStrictSingleton instanciation 1:');
try {
let strictSingleton1 = new MyStrictSingleton();
console.log('No error on instanciation - OK');
} catch (e) {
console.log('Error on instanciation - NOK');
}
console.log('MyStrictSingleton instanciation 2:');
try {
let strictSingleton2 = new MyStrictSingleton();
console.log('No error on instanciation - NOK');
} catch (e) {
console.log('Error on instanciation - OK');
}
console.log('\nMySingleton instanciation 1:');
let singleton1;
let singleton2
try {
singleton1 = new MySingleton();
console.log('No error on instanciation - OK');
} catch (e) {
console.log('Error on instanciation - NOK');
}
console.log('MySingleton instanciation 2:');
try {
singleton2 = new MySingleton();
console.log('No error on instanciation - OK');
} catch (e) {
console.log('Error on instanciation - NOK');
}
console.log('MySingleton instance comparison:');
if (singleton1 === singleton2) {
console.log('Same reference - OK');
} else {
console.log('Differents references - NOK');
}
/******************* Typings *******************/
/**
* Represents a constructible class
*/
interface IClass {
new(...args: any[]): {}
}
/******************* Private *******************/
/**
* Modifies a class in a manner that it can be instanciated only once
* Any other "new" will throw an exception
* @param {IClass} constructor Class to strictly singletonify
* @returns {IClass} Singletonified class
*/
function strictlySingletonify(constructor: IClass): IClass {
return (function (klass: IClass) {
let SingletonifiedKlass = class extends klass {
static $$instanciatedOnce: boolean = false;
static $$type: string = 'strict-singleton';
constructor(...args: any[]) {
if (SingletonifiedKlass.$$instanciatedOnce) {
throw 'Cannot instanciate singleton class twice !';
}
SingletonifiedKlass.$$instanciatedOnce = true;
super(...args);
}
}
return SingletonifiedKlass;
})(constructor);
}
/**
* Modifies a class in a manner that it can be instanciated only once
* Any other "new" will return the same first instance
* @param {IClass} constructor Class to strictly singletonify
* @returns {IClass} Singletonified class
*/
function singletonify(constructor: IClass): IClass {
return (function (klass: IClass) {
let SingletonifiedKlass = class extends klass {
static $$instance: Object = null;
static $$type: string = 'singleton';
constructor(...args: any[]) {
if (SingletonifiedKlass.$$instance) {
return SingletonifiedKlass.$$instance;
}
super(...args);
SingletonifiedKlass.$$instance = this;
}
}
return SingletonifiedKlass;
})(constructor);
}
/******************* Public ********************/
/**
* Decorates a class in a manner it cannot be instanciated twice
* making it a true singleton
* @param {boolean} strict Whether or not the "new" syntax makes it throw an exception
* true: if the class is beeing instanciated a second time it throws an exeption
* flase : the "new" syntax just returns the first instance
* Defaults to true
*/
export default function Singleton(strict: boolean = true): Function {
return function (constructor: IClass): IClass {
if (strict) {
return strictlySingletonify(constructor);
}
return singletonify(constructor);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment