Cómo definir Singleton en TypeScript
(18)
¿Cuál es la forma mejor y más conveniente de implementar un patrón Singleton para una clase en TypeScript? (Tanto con y sin inicialización perezosa).
Agregue las siguientes 6 líneas a cualquier clase para que sea "Singleton". Use la respuesta de Alex si prefiere obtener la instancia a través de una propiedad en lugar de un método.
class MySingleton
{
private constructor(){ /* ... */}
private static _instance:MySingleton;
public static getInstance():MySingleton
{
return this._instance||(this._instance = new this());
};
}
var test = MySingleton.getInstance(); // will create the first instance
var test2 = MySingleton.getInstance(); // will return the first instance
alert(test === test2); // true
Aquí hay otra forma de hacerlo con un enfoque de JavaScript más convencional utilizando un IFFE :
module App.Counter {
export var Instance = (() => {
var i = 0;
return {
increment: (): void => {
i++;
},
getCount: (): number => {
return i;
}
}
})();
}
module App {
export function countStuff() {
App.Counter.Instance.increment();
App.Counter.Instance.increment();
alert(App.Counter.Instance.getCount());
}
}
App.countStuff();
Ver una demo
Desde TS 2.0, tenemos la capacidad de definir modificadores de visibilidad en los constructores , por lo que ahora podemos hacer singletons en TypeScript tal como estamos acostumbrados desde otros lenguajes.
Ejemplo dado:
class MyClass
{
private static _instance: MyClass;
private constructor()
{
//...
}
public static get Instance()
{
// Do you need arguments? Make it a regular static method instead.
return this._instance || (this._instance = new this());
}
}
const myClassInstance = MyClass.Instance;
Gracias @Drenai por señalar que si escribes código usando el javascript compilado sin procesar no tendrás protección contra la creación de instancias múltiples, ya que las restricciones de TS desaparecen y el constructor no estará oculto.
El siguiente enfoque crea una clase Singleton que se puede usar exactamente como una clase convencional:
class Singleton {
private static instance: Singleton;
//Assign "new Singleton()" here to avoid lazy initialisation
constructor() {
if (Singleton.instance) {
return Singleton.instance;
}
this. member = 0;
Singleton.instance = this;
}
member: number;
}
Cada
new Singleton()
operación
new Singleton()
devolverá la misma instancia.
Sin embargo, esto puede ser inesperado por el usuario.
El siguiente ejemplo es más transparente para el usuario pero requiere un uso diferente:
class Singleton {
private static instance: Singleton;
//Assign "new Singleton()" here to avoid lazy initialisation
constructor() {
if (Singleton.instance) {
throw new Error("Error - use Singleton.getInstance()");
}
this.member = 0;
}
static getInstance(): Singleton {
Singleton.instance = Singleton.instance || new Singleton();
return Singleton.instance;
}
member: number;
}
Uso:
var obj = Singleton.getInstance();
En Typecript, uno no necesariamente tiene que seguir la
new instance()
metodología Singleton.
Una clase estática importada sin constructor puede funcionar igualmente.
Considerar:
export class YourSingleton {
public static foo:bar;
public static initialise(_initVars:any):void {
YourSingleton.foo = _initvars.foo;
}
public static doThing():bar {
return YourSingleton.foo
}
}
Puede importar la clase y consultar
YourSingleton.doThing()
en cualquier otra clase.
Pero recuerde, debido a que esta es una clase estática, no tiene constructor, por lo que generalmente uso un método
intialise()
que se llama desde una clase que importa Singleton:
import {YourSingleton} from ''singleton.ts'';
YourSingleton.initialise(params);
let _result:bar = YourSingleton.doThing();
No olvide que en una clase estática, todos los métodos y variables también deben ser estáticos, por lo que, en lugar de
this
, usaría el nombre completo de la clase
YourSingleton
.
Esta es la forma más sencilla
class YourSingletoneClass {
private static instance: YourSingletoneClass;
private constructor(public ifYouHaveAnyParams: string) {
}
static getInstance() {
if(!YourSingletoneClass.instance) {
YourSingletoneClass.instance = new YourSingletoneClass(''If you have any params'');
}
return YourSingletoneClass.instance;
}
}
Este es probablemente el proceso más largo para hacer un singleton en mecanografiado, pero en aplicaciones más grandes es el que mejor me ha funcionado.
Primero necesita una clase Singleton en, digamos, "./utils/Singleton.ts" :
module utils {
export class Singleton {
private _initialized: boolean;
private _setSingleton(): void {
if (this._initialized) throw Error(''Singleton is already initialized.'');
this._initialized = true;
}
get setSingleton() { return this._setSingleton; }
}
}
Ahora imagine que necesita un enrutador singleton "./navigation/Router.ts" :
/// <reference path="../utils/Singleton.ts" />
module navigation {
class RouterClass extends utils.Singleton {
// NOTICE RouterClass extends from utils.Singleton
// and that it isn''t exportable.
private _init(): void {
// This method will be your "construtor" now,
// to avoid double initialization, don''t forget
// the parent class setSingleton method!.
this.setSingleton();
// Initialization stuff.
}
// Expose _init method.
get init { return this.init; }
}
// THIS IS IT!! Export a new RouterClass, that no
// one can instantiate ever again!.
export var Router: RouterClass = new RouterClass();
}
¡Agradable !, ahora inicialice o importe donde lo necesite:
/// <reference path="./navigation/Router.ts" />
import router = navigation.Router;
router.init();
router.init(); // Throws error!.
Lo bueno de hacer singletons de esta manera es que todavía usas toda la belleza de las clases de mecanografía, te da un buen sentido, la lógica singleton se mantiene separada y es fácil de eliminar si es necesario.
La mejor manera que he encontrado es:
class SingletonClass {
private static _instance:SingletonClass = new SingletonClass();
private _score:number = 0;
constructor() {
if(SingletonClass._instance){
throw new Error("Error: Instantiation failed: Use SingletonClass.getInstance() instead of new.");
}
SingletonClass._instance = this;
}
public static getInstance():SingletonClass
{
return SingletonClass._instance;
}
public setScore(value:number):void
{
this._score = value;
}
public getScore():number
{
return this._score;
}
public addPoints(value:number):void
{
this._score += value;
}
public removePoints(value:number):void
{
this._score -= value;
}
}
Así es como lo usas:
var scoreManager = SingletonClass.getInstance();
scoreManager.setScore(10);
scoreManager.addPoints(1);
scoreManager.removePoints(2);
console.log( scoreManager.getScore() );
Las clases Singleton en TypeScript son generalmente un antipatrón. Simplemente puede usar espacios de nombres en su lugar.
Patrón singleton inútil
class Singleton {
/* ... lots of singleton logic ... */
public someMethod() { ... }
}
// Using
var x = Singleton.getInstance();
x.someMethod();
Equivalente de espacio de nombres
export namespace Singleton {
export function someMethod() { ... }
}
// Usage
import { SingletonInstance} from "path/to/Singleton";
SingletonInstance.someMethod();
var x = SingletonInstance; // If you need to alias it for some reason
Me sorprende no ver el siguiente patrón aquí, que en realidad parece muy simple.
// shout.ts
class ShoutSingleton {
helloWorld() { return ''hi''; }
}
export let Shout = new ShoutSingleton();
Uso
import { Shout } from ''./shout'';
Shout.helloWorld();
Mi solución para ello:
export default class Modal {
private static _instance : Modal = new Modal();
constructor () {
if (Modal._instance)
throw new Error("Use Modal.instance");
Modal._instance = this;
}
static get instance () {
return Modal._instance;
}
}
No es un singleton puro (la inicialización puede no ser perezosa), sino un patrón similar con la ayuda de
namespace
de
namespace
.
namespace MyClass
{
class _MyClass
{
...
}
export const instance: _MyClass = new _MyClass();
}
Acceso al objeto de Singleton:
MyClass.instance
Otra opción es usar símbolos en su módulo. De esta manera, puede proteger su clase, también si el usuario final de su API está usando Javascript normal:
let _instance = Symbol();
export default class Singleton {
constructor(singletonToken) {
if (singletonToken !== _instance) {
throw new Error("Cannot instantiate directly.");
}
//Init your class
}
static get instance() {
return this[_instance] || (this[_instance] = new Singleton(_singleton))
}
public myMethod():string {
return "foo";
}
}
Uso:
var str:string = Singleton.instance.myFoo();
Si el usuario está utilizando su archivo compilado API js, también recibirá un error si intenta crear una instancia manual de su clase:
// PLAIN JAVASCRIPT:
var instance = new Singleton(); //Error the argument singletonToken !== _instance symbol
Puede usar expresiones de clase para esto (a partir de 1.6, creo).
var x = new (class {
/* ... lots of singleton logic ... */
public someMethod() { ... }
})();
o con el nombre si su clase necesita acceder internamente a su tipo
var x = new (class Singleton {
/* ... lots of singleton logic ... */
public someMethod(): Singleton { ... }
})();
Otra opción es usar una clase local dentro de su singleton usando algunos miembros estáticos
class Singleton {
private static _instance;
public static get instance() {
class InternalSingleton {
someMethod() { }
//more singleton logic
}
if(!Singleton._instance) {
Singleton._instance = new InternalSingleton();
}
return <InternalSingleton>Singleton._instance;
}
}
var x = Singleton.instance;
x.someMethod();
También puede hacer uso de la función Object.Freeze () . Es simple y fácil:
class Singleton {
instance: any = null;
data: any = {} // store data in here
constructor() {
if (!this.instance) {
this.instance = this;
}
return this.instance
}
}
const singleton: Singleton = new Singleton();
Object.freeze(singleton);
export default singleton;
creo que tal vez usar genéricos sea la masa
class Singleton<T>{
public static Instance<T>(c: {new(): T; }) : T{
if (this._instance == null){
this._instance = new c();
}
return this._instance;
}
private static _instance = null;
}
cómo utilizar
paso 1
class MapManager extends Singleton<MapManager>{
//do something
public init():void{ //do }
}
paso 2
MapManager.Instance(MapManager).init();
class MySingleton {
constructor(message) {
alert(''hello'' + message);
}
private static instance: MySingleton;
private static instance2: MySingleton;
public static getSessionStorageInstance() {
if (!MySingleton.instance) {
MySingleton.instance = new MySingleton("world");
}
return MySingleton.instance;
}
public static getSessionStorageInstance2() {
if (!MySingleton.instance2) {
MySingleton.instance2 = new MySingleton("youu");
}
return MySingleton.instance2;
}
}
const a: MySingleton = MySingleton.getSessionStorageInstance();
const b: MySingleton = MySingleton.getSessionStorageInstance2();
alert(a === b)
namespace MySingleton {
interface IMySingleton {
doSomething(): void;
}
class MySingleton implements IMySingleton {
private usePrivate() { }
doSomething() {
this.usePrivate();
}
}
export var Instance: IMySingleton = new MySingleton();
}
De esta manera podemos aplicar una interfaz, a diferencia de la respuesta aceptada de Ryan Cavanaugh.