分享个适用cocos单例的写法

export namespace Singleton {

const __Instance__Symbol__ = Symbol("Instance");

const __Create__Symbol__ = Symbol("Create");

export function Build<T>(...argvs: T extends (...argvs: infer R) => T ? R : any[]) {

    let instance: any = null;

    return function <Class>(target: new (...argvs: any[]) => Class) {

        if (!target[__Create__Symbol__]) {

            target[__Create__Symbol__] = () => {

                return (instance = new target(...argvs));

            };

        }

        if (!target[__Instance__Symbol__]) {

            target[__Instance__Symbol__] = () => {

                return instance ?? target[__Create__Symbol__]();

            };

        }

    };

}

export function Instance<T>(type: { prototype: T }): T {

    return type[__Instance__Symbol__]?.();

}

}

注册使用
@Singleton.Build() class SomeClass {}
单例调用
Singleton.Instance(SomeClass)

最近看了下公司项目前端的单例写法, 没有TS类型限制和提示
因为cocos不支持使用属性装饰器修改类成员, 因此把装饰器作用在类上.
保留了传参的需求, 也保留了删除重建的口子,参考create依葫芦画瓢即可.
给大家提供一个思路

1赞

在b站看到的一个用js代理实现的单例感觉也挺好的
https://www.bilibili.com/video/BV1Ah4y157Wz/?share_source=copy_web&vd_source=24e55a9265dd82ec1a2e38bfda7cf0d6

我这种写法如何?
export const UIMgr = new class {
//具体逻辑
}

2赞

可以用UIMgr.constructor.prototype再new一个出来 :grin:

这就是为了追求书本上那种所谓的设计模式 弄出来的方式,本来一行代码就能解决的事 结果就是这样了

因为是Symbol属性吧

比如开发到一定的版本我要对每个调用单例的地方增加单例创建记录, 我感觉你就不会这么说.
我觉得你应该遇到的问题太少了.

对,方法很多, 主要是为了为证有ts限制和提示, 二是在任何条件下返回能保持一致. 我遇到的各类的写法很多, 尽可能还是多方面考虑.

导出式的问题是文件引用或者初始就创建, 而非调用创建.看你自己的需求吧

看了楼主的单例,我觉得自己平时写的单例太简单了。 我一般像这样写:

class XXMgr{
}
export const xxMgr = new XXMgr();

可以直接引用,也可以 facade 模式包装一下。

很怪,尤其是Build装饰器接受参数,装饰器的执行时机决定它只能填写固定参数,那这里传参数好像没啥意义吧,还不如改成这样:

export namespace Singleton {

    const __Instance__Symbol__ = Symbol("Instance");

    export function Instance<T, A extends any[]>(type: { new(...argvs: A): T }, ...argvs: A): T {
        if (!type[__Instance__Symbol__]) {
            type[__Instance__Symbol__] = new type(...argvs);
        }
        return type[__Instance__Symbol__];
    }
}

不过好像单例一般很少接受参数的,不如直接new出来省事。没必要担心一上来new太多内存会膨胀,能有多少个单例呀…

你的这个怪吧, 谁家好人在完成初始化之后还要确定传不传参的. 你是想把传参的责任丢给第三个人,还是你能完全保证单例的调用顺序?
这个时候我假设它有依赖关系且有数据需要初始化请问你new出来之后,你想在哪里完成初始化的时机咧?
我觉得你,
1.没有理解装饰器
2.实例本身带多少数据取决于设计需求(你肯定没试过一个游戏里面有个限时大型玩法, 然后你在不使用的时候就直接完全初始化的酸爽感. 如果只是new,又不构建实例所需的数据我上面就问了时机/调用顺序你能怎么保证?)
3.没看完我发的内容.

build 在脚本加载时被调用,如果构造参数引用了其他模块,容易造成循环引用。这是弊端要用的注意

keep it simple

1赞

感觉题主分享这个写法,有参考和讨论的价值,大家讨论的好热烈,谢谢分享这个,大家都分享一下如何写单例,
下面是我个人观点:我很不喜欢 下面的单例实现风格:

class AAA{
}
export const aaa = new AAA();

理由两点:
第一点:创建对象会在初始化阶段,有些单例实属没有必要过早的创建,既然单例,以前的设计模式都已经早早的说明了,如果没有做到用的首次创建,我觉得没有掌握单例的精髓,另外单例的类还有静态属性和方法呢

第二点:在我看过的大多数优秀的框架都没有这么写的,所以我也不会这么写

感觉做项目就应该:勿以恶小而为之,勿以善小而不为

看看你的simple code

导出式的写法问题有点多,不适合我现在的项目:
1.引用即创建初始化时机不可控.
2.实例创建/卸载不可控
3.无法传参, 开发过公共组件/库的就知道弊端.
4.导出变量名是不确定的, 你难保别人定义了class ItemMgr {}, 然后导出这么写export const MgrItem = new ItemMgr();
5.若导出匿名class, 那该class就难以复用.
情况很多, 列举些常见的
如果只是一个人或小项目我觉得大道至简毫无问题, 只是有多人协作时你永远猜不到别人怎么写代码.

支持楼主,希望大家保持辩证公正态度,有问题就提,越辩越清楚。

Instance传参这里确实看上去确实不太对劲,没多想直接把你那个删减一下就发了,讨论一下。

我以前倒是没有过单例需要传参数的情况,我回忆里一下,以前用到的单例都是工具计算类的东西,基本都是纯函数。像这种需要数据初始化,业务相关的东西都是普通实例,由Manager管理初始化和垃·圾回收。

export default new class {
}
我都是这样,文件名就作为单例名了