Respuestas:
Las clases Singleton en TypeScript son generalmente un antipatrón. Simplemente puede usar espacios de nombres en su lugar.
class Singleton {
/* ... lots of singleton logic ... */
public someMethod() { ... }
}
// Using
var x = Singleton.getInstance();
x.someMethod();
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
export default new Singleton()
?
Desde TS 2.0, tenemos la capacidad de definir modificadores de visibilidad en los constructores , por lo que ahora podemos hacer singletons en TypeScript 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.
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() );
https://codebelt.github.io/blog/typescript/typescript-singleton-pattern/
El siguiente enfoque crea una clase Singleton que puede usarse 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 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();
new Class(...)
sintaxis.
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();
Shout
embargo
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();
Agregue las siguientes 6 líneas a cualquier clase para que sea "Singleton".
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
[Editar]: use la respuesta de Alex si prefiere obtener la instancia a través de una propiedad en lugar de un método.
new MySingleton()
, digo 5 veces? ¿Su código reserva una sola instancia?
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();
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;
if (!this.instance)
el constructor? ¿Es solo una precaución adicional en caso de que haya creado varias instancias antes de la exportación?
He encontrado una nueva versión de esto con la que el compilador deScript está totalmente de acuerdo, y creo que es mejor porque no requiere llamar a un getInstance()
método constantemente.
import express, { Application } from 'express';
export class Singleton {
// Define your props here
private _express: Application = express();
private static _instance: Singleton;
constructor() {
if (Singleton._instance) {
return Singleton._instance;
}
// You don't have an instance, so continue
// Remember, to set the _instance property
Singleton._instance = this;
}
}
Esto viene con un inconveniente diferente. Si Singleton
tiene propiedades, el compilador de Script mecanografiará un ajuste a menos que lo inicialice con un valor. Es por eso que incluí una _express
propiedad en mi clase de ejemplo porque a menos que la inicialice con un valor, incluso si la asigna más adelante en el constructor, TypeScript pensará que no se ha definido. Esto podría solucionarse desactivando el modo estricto, pero prefiero no hacerlo si es posible. También hay otro inconveniente de este método que debo señalar, porque en realidad se llama al constructor, cada vez que lo hace se crea técnicamente otra instancia, pero no es accesible. Esto podría, en teoría, causar pérdidas de memoria.
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.
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;
}
}
return Modal._instance
. De esta manera, si new
esa clase, obtienes el objeto existente, no uno nuevo.
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 consultarla 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 intialise()
método 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 el nombre completo de la clase YourSingleton
.
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 demostración
Instance
variable? Podrías simplemente poner la variable y las funciones directamente debajo App.Counter
.
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
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;
}
}
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.
Después de recorrer este hilo y jugar con todas las opciones anteriores, me decidí por un Singleton que se puede crear con los constructores adecuados:
export default class Singleton {
private static _instance: Singleton
public static get instance(): Singleton {
return Singleton._instance
}
constructor(...args: string[]) {
// Initial setup
Singleton._instance = this
}
work() { /* example */ }
}
Se requeriría una configuración inicial (in main.ts
o index.ts
), que puede implementarse fácilmente mediante
new Singleton(/* PARAMS */)
Luego, en cualquier parte de su código, simplemente llame Singleton.instnace
; en este caso, para work
terminar, llamaríaSingleton.instance.work()