วิธีใดที่ดีที่สุดและสะดวกที่สุดในการนำรูปแบบ Singleton ไปใช้กับคลาสใน TypeScript คืออะไร (ทั้งที่มีและไม่มีการเริ่มต้นแบบขี้เกียจ)
วิธีใดที่ดีที่สุดและสะดวกที่สุดในการนำรูปแบบ Singleton ไปใช้กับคลาสใน TypeScript คืออะไร (ทั้งที่มีและไม่มีการเริ่มต้นแบบขี้เกียจ)
คำตอบ:
คลาส 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 เรามีความสามารถในการกำหนดตัวปรับการมองเห็นบนตัวสร้างดังนั้นตอนนี้เราสามารถทำ singletons ใน 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
[แก้ไข]: ใช้คำตอบของ Alex หากคุณต้องการรับอินสแตนซ์ผ่านคุณสมบัติแทนที่จะใช้วิธีการ
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; }
}
}
ลองนึกดูว่าคุณต้องการ Router 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();
}
ดี! เริ่มต้นหรือนำเข้าได้ทุกที่ที่คุณต้องการ:
/// <reference path="./navigation/Router.ts" />
import router = navigation.Router;
router.init();
router.init(); // Throws error!.
สิ่งที่ดีเกี่ยวกับการทำเสื้อกล้ามด้วยวิธีนี้คือคุณยังคงใช้ความสวยงามของคลาส typescript ทั้งหมดมันให้ intellisense ที่ดีตรรกะของ singleton ช่วยแยกบางส่วนและง่ายต่อการลบออกหากจำเป็น
ทางออกของฉันสำหรับมัน:
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
นี่เป็นอีกวิธีหนึ่งในการใช้วิธีการใช้จาวาสคริปต์แบบธรรมดาโดยใช้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
ข้างใต้
อีกทางเลือกหนึ่งคือการใช้ Symbols ในโมดูลของคุณ ด้วยวิธีนี้คุณสามารถปกป้องชั้นเรียนของคุณได้เช่นกันหากผู้ใช้ขั้นสุดท้ายของ 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();
หากผู้ใช้กำลังใช้ไฟล์ API js ที่คอมไพล์แล้วจะได้รับข้อผิดพลาดหากเขาพยายามสร้างอินสแตนซ์คลาสของคุณด้วยตนเอง:
// PLAIN JAVASCRIPT:
var instance = new Singleton(); //Error the argument singletonToken !== _instance symbol
ไม่ใช่ซิงเกิลตันที่บริสุทธิ์ (การเริ่มต้นอาจไม่น่าเกียจ) แต่รูปแบบที่คล้ายกันด้วยความช่วยเหลือของnamespace
s
namespace MyClass
{
class _MyClass
{
...
}
export const instance: _MyClass = new _MyClass();
}
การเข้าถึงวัตถุของ Singleton:
MyClass.instance
นี่เป็นวิธีที่ง่ายที่สุด
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();
}
วิธีนี้เราสามารถใช้อินเทอร์เฟซซึ่งแตกต่างจากคำตอบที่ยอมรับของ Ryan Cavanaugh
หลังจากกำจัดสิ่งสกปรกบนเธรดนี้และเล่นกับตัวเลือกทั้งหมดข้างต้น - ฉันตัดสินด้วย 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()