Відповіді:
Класи Singleton в TypeScript, як правило, є антидіаграмою. Ви можете просто використовувати простори імен .
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()
?
З TS 2.0 ми маємо можливість визначати модифікатори видимості на конструкторах , тому тепер ми можемо робити одиночні кнопки в TypeScript так, як ми звикли з інших мов.
Наведений приклад:
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;
Дякую @Drenai за вказівку, що якщо ви будете писати код за допомогою необробленого javascript, у вас не буде захисту від декількох екземплярів, оскільки обмеження TS зникають і конструктор не буде прихований.
Найкращий спосіб, який я знайшов:
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;
}
}
Ось як ви його використовуєте:
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/
Наступний підхід створює клас Singleton, який можна використовувати так, як звичайний клас:
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;
}
Кожна new Singleton()
операція повертає один і той же екземпляр. Однак це може бути несподіваним для користувача.
Наступний приклад є більш прозорим для користувача, але вимагає іншого використання:
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;
}
Використання: var obj = Singleton.getInstance();
new Class(...)
синтаксис.
Я здивований, що не бачу тут такої схеми, яка насправді виглядає дуже просто.
// shout.ts
class ShoutSingleton {
helloWorld() { return 'hi'; }
}
export let Shout = new ShoutSingleton();
Використання
import { Shout } from './shout';
Shout.helloWorld();
Shout
клас
Ви можете використовувати для цього вирази класів (на 1.6 я вважаю).
var x = new (class {
/* ... lots of singleton logic ... */
public someMethod() { ... }
})();
або з назвою, якщо ваш клас потребує внутрішнього доступу до свого типу
var x = new (class Singleton {
/* ... lots of singleton logic ... */
public someMethod(): Singleton { ... }
})();
Іншим варіантом є використання місцевого класу всередині вашого сингтона з використанням деяких статичних членів
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();
Додайте наступні 6 рядків до будь-якого класу, щоб зробити його "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
[Редагувати]: Використовуйте відповідь Алекса, якщо ви бажаєте отримати екземпляр через властивість, а не метод.
new MySingleton()
, скажімо, 5 разів? чи залишає ваш код один екземпляр?
Я думаю, можливо, використання дженерики буде тісто
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;
}
як використовувати
крок 1
class MapManager extends Singleton<MapManager>{
//do something
public init():void{ //do }
}
крок2
MapManager.Instance(MapManager).init();
Ви також можете скористатися функцією Object.Freeze () . Його просто і легко:
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)
конструктором? Це лише додаткова обережність у випадку, якщо ви створили кілька екземплярів до експорту?
Я знайшов нову версію цього, з якою компілятор Typescript повністю в порядку, і я думаю, що це краще, оскільки він не вимагає getInstance()
постійного виклику методу.
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;
}
}
Це має і інший недолік. Якщо у вас Singleton
є які-небудь властивості, компілятор Typescript кине придатність, якщо ви не ініціалізуєте їх зі значенням. Ось чому я включив _express
властивість у свій клас прикладу, оскільки, якщо ви не ініціалізуєте його зі значенням, навіть якщо ви його призначите пізніше у конструкторі, Typescript подумає, що він не визначений. Це можна виправити, відключивши суворий режим, але я вважаю за краще не робити це, якщо можливо. Існує також ще один недолік цього методу, який я повинен зазначити, оскільки конструктор насправді викликає кожен раз, коли він робить інший екземпляр технічно створений, але недоступний. Це теоретично може призвести до витоку пам'яті.
Це, мабуть, найдовший процес створення одиночного шрифту в машинописному тексті, але для великих програм це той, хто працював краще для мене.
Спочатку вам потрібен клас Singleton, скажімо, "./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; }
}
}
Тепер уявіть, що вам потрібен одиночний маршрутизатор "./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();
}
Приємно !, тепер ініціалізуйте чи імпортуйте, де вам потрібно:
/// <reference path="./navigation/Router.ts" />
import router = navigation.Router;
router.init();
router.init(); // Throws error!.
Приємно в тому, що робити одиночні ключі таким чином, що ти все ще використовуєш всю красу класів машинописів, це дає приємну інтелігенцію, логіка сингтона залишається відокремленою, і її легко видалити, якщо потрібно.
Моє рішення для цього:
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
. Таким чином, якщо ви new
цей клас, ви отримаєте існуючий об'єкт, а не новий.
У Typescript не обов’язково слід дотримуватися new instance()
методології Singleton. Імпортний статичний клас без конструкцій може також працювати однаково.
Поміркуйте:
export class YourSingleton {
public static foo:bar;
public static initialise(_initVars:any):void {
YourSingleton.foo = _initvars.foo;
}
public static doThing():bar {
return YourSingleton.foo
}
}
Ви можете імпортувати клас та посилатися на YourSingleton.doThing()
будь-який інший клас. Але пам’ятайте, оскільки це статичний клас, він не має конструктора, тому я зазвичай використовую intialise()
метод, який викликається з класу, який імпортує Singleton:
import {YourSingleton} from 'singleton.ts';
YourSingleton.initialise(params);
let _result:bar = YourSingleton.doThing();
Не забувайте, що в статичному класі кожен метод і змінна також повинні бути статичними, щоб замість this
вас використовувати повне ім'я класу YourSingleton
.
Ось ще один спосіб зробити це за допомогою більш звичайного підходу javascript за допомогою 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();
Перегляд демонстраційної версії
Instance
змінної? Ви coukd просто ставите змінну та функції безпосередньо під App.Counter
.
Інший варіант - використовувати символи у своєму модулі. Таким чином ви можете захистити свій клас, навіть якщо кінцевий користувач вашого API використовує звичайний Javascript:
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";
}
}
Використання:
var str:string = Singleton.instance.myFoo();
Якщо користувач використовує ваш компільований файл js API, він також отримає помилку, якщо спробує вручну створити інстанцію вашого класу:
// PLAIN JAVASCRIPT:
var instance = new Singleton(); //Error the argument singletonToken !== _instance symbol
Це найпростіший спосіб
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();
}
Таким чином ми можемо застосувати інтерфейс, на відміну від прийнятої відповіді Раяна Кавано.
Переглянувши цю тему і розібравшись з усіма варіантами, описаними вище, я вирішив використовувати Singleton, який можна створити за допомогою правильних конструкторів:
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 */ }
}
Це вимагатиме початкової установки (в main.ts
, або index.ts
), яку легко запровадити
new Singleton(/* PARAMS */)
Тоді будь-де в коді просто зателефонуйте Singleton.instnace
; в цьому випадку, щоб work
закінчитися, я б закликавSingleton.instance.work()