cocos單例工廠和自動裝配
1 單例工廠
1.1 分析
- 實例字典
原理很簡單,只是一個
map
,確保每個類只保留一個實例;private static _instances = new Map<string, any>();
- 獲取與存儲實例
這邊使用的方式是生成一個唯一的
id
存儲在類上,獲取實例的時候通過這個id
值拿到實例對象;這里很多人會疑惑,為什么不直接使用類名作為標識來記錄和獲取實例?原因很簡單,在項目構建之后類名是隨機的、可能重復的;如果還是希望使用類名作為標識,可以參考
cocos 3.x
中@ccclass
裝飾器的寫法,將類名字符串傳入。(ccclass源碼)因設計過程中考慮到可能存在需傳參的構造器,所以為了強化職責性就不考慮傳入類名作為標識了;
public static getInst<T>(classType: { new(...args): T }): T {let key = classType['_singletonId'];if (!key || !this._instances[key]) {console.error("無實例");return null;}return this._instances[key]; } public static setInst(classType, ...args): void {if(classType._singletonId)return;let key = UUID.generate();while(this._instances[key]) {key = UUID.generate();}this._instances[key] = new classType(...args);classType._singletonId = key; }
- 單例注解(裝飾器)
使用方式在需要托管的類上直接添加
Singleton
即可,如果規范是所有單例都無參可以將返回的函數上移(代碼段中可見),標記單例時寫法更簡潔;
checkIsClass
函數用于檢測標記的是否為類;export function Singleton(...args) {return function (target: any) {if (checkIsClass(target)){SingletonFactory.setInst(target, ...args);}} } const checkIsClass = function (target: unknown): boolean {return (typeof target === "function" && "prototype" in target && target.prototype.constructor === target); } //規范是單例都無參的寫法 export function Singleton(target: any) {if (checkIsClass(target)){SingletonFactory.setInst(target, ...args);} } //使用方式有參 @Singleton(info) export default class DataCenter{private info = null;constructor(info) {this.info = info;} } //使用方式無參 @Singleton() export default class EventManager{}
- 自動注入
自動注入也是使用的注解方式,標記主動注入的屬性所屬的類一定要使用單例注解,否則取值為空;
需要注意的是,因為注解的執行時機在創建階段,而循環依賴的類在使用
import
時不能立即獲得類,故傳入的target
會出現未定義的狀況,故希望使用自動注入的注解時一定要避免循環依賴的出現。存在循環依賴的類只能在使用時通過單例工廠取獲取實例;export function Autowired<T>(constructor: new (...args) => T) {return function (target: any, propertyName) {Object.defineProperty(target, propertyName, {configurable: true,get: function () {return SingletonFactory.getInst(constructor);},set: function(value) {}})} } //Autowired使用方式 export default class xxxView {@Autowired(LobbyLogic)lobbyLogic: LobbyLogic = null; } //直接使用單例工廠 SingletonFactory.getInst(單例類);
1.3 源碼
- 單例工廠實現
import UUID from "./UUID";
export default class SingletonFactory {private static _instances = new Map<string, any>();public static getInst<T>(classType: { new(...args): T }): T {let key = classType['_singletonId'];if (!key || !this._instances[key]){console.error("無實例");return null;}return this._instances[key];}static setInst(classType, ...args): void {if(classType._singletonId)return;let key = UUID.generate();while (this._instances[key]) {key = UUID.generate();}this._instances[key] = new classType(...args);classType._singletonId = key;}
}
export function Singleton(...args) {return function (target: any) {if (checkIsClass(target)){SingletonFactory.setInst(target, ...args);}}
}
export function Autowired<T>(constructor: new (...args) => T) {return function (target: any, propertyName) {Object.defineProperty(target, propertyName, {configurable: true,get: function () {return SingletonFactory.getInst(constructor);},set: function(value) {}})}
}
const checkIsClass = function (target: unknown): boolean {return (typeof target === "function" && "prototype" in target && target.prototype.constructor === target);
}
- UUID實現
export default class UUID {static generate(): string {const hexDigits = '0123456789abcdef'const s: string[] = Array(36).fill('')for (let i = 0; i < 36; i++) {s[i] = hexDigits.charAt(Math.floor(Math.random() * 0x10))}s[14] = '4's[19] = hexDigits.charAt((parseInt(s[19], 16) & 0x3) | 0x8)s[8] = s[13] = s[18] = s[23] = '-'return s.join('');}
}