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('');}
}