分享个适用cocos单例的写法

同一个sdk传不同包参数, 公共组件/库复用, 类标记等等. 可以初始确定且未来不变的都可以这么搞.
还有很多时候要支持卸载再创建, 项目小人少怎么用都可以, 因为代码要么就是那么1,2个人, 要么就是几乎没什么依赖关系, 要么就是忍忍就这么用.
继承式单例实现方式问题是外部直接赋值了获取实例的function就会因为调用者变更导致实例变化, 还有就是如果需要额外继承其他组件需要多继承一层
导出式的问题是无法控制时机, 要卸载重新重新新建只能内部数据重构非常繁琐, 若还有统一针对单例处理的需求实现起来又非常麻烦.
楼上好多删评论的都是用这上面两个办法的, 问了他们几个场景处理方式就删评论了. 不想讨论我也就一起删了

:+1:1234

这种写法肯定是更严谨, 但是在实际项目中 如果团队中有人写出这种注册式的单例方法 那肯定可以用, 但是如果团队没有人写出 也可以按照正常单例设计模式的方式写也是没有任何问题的, 如果遇到各种需求单例无法满足 正常来说就不要在魔改单例了 换一种思路, 保持代码设计模式的单一性这是一个符合主流的方式, 总结 楼主这种单例光从代码设计和代码思路和以及考虑的各种情况还是可以

要想办法不用单例,凡是在业务逻辑中会频繁出现 xx.inst.yy() 或者 xx.getInstance().yy() 的写法,个人认为都很丑陋。。。

那我很好奇在你的工程里面是如何获取项目唯一的值

直接 new 出来就好了。

// 类定义文件,比如说 GCtr.ts
@ccclass("GCtr")
class GCtr {
    public readonly audio = new AudioMgr();
    public readonly ui = new UIMgr();

    constructor(){
        globalThis["fw"] = this;
    }

    init() {
        this.audio.init();
        this.ui.init();
    }
}

declare global {
    const fw: GCtr;
}

// 入口文件调用
let inst = new GCtr();
inst.init()

其他地方用的时候,直接

fw.ui.open()

fw.audio.play()

还用啥单例?全局就有一个变量 fw 充当单例。

兄弟别搞, 这种古早的写法, 就是当年lua的最大弊端.
要么就是全局名冲突, 这个还可以一定程度解决.
要么就是编写或导出d.ts, 但这样不是吃饱了闲.
如果你习惯所有变量都是any或完全是js的写法我无话可说,咱们也完全没有讨论的必要.
我就问你觉得这种代码可读性有多少后期维护的成本有多高, 这是妄图把所有错误都放在运行时吧.
邪教啊邪教

怎么没代码提示,大哥,你自己试试看有没有代码提示
而且就一个全局变量,怎么冲突:thinking:

你说没提示,我还有哈好说的,那我这边自动提示是怎么回事:thinking:。有一个全局声明,你好好看一下

你不是说cocos 现在又提服务端?

declare global {
const fw: GCtr;
}
这句呀,哥

看懂了, :+1:all in one 是吧.
怪我怪我 第一次看的太快没看到你后面编辑的.

是,all in one 真的好用啊

不过服务端我之前试过,确实是没办法用 declare global,暂时没想到好办法。

可以用的兄弟, 我只是刚刚没看到你后面编辑的声明.
顺便想问下你这种做法, 怎么处理依赖和调用才初始化的需求

反正你所有管理类构造函数里只用初始化自己的内容,如果有依赖的话,要么延迟到 init 阶段,要么管理类实例化的时候传依赖进去。

所有的依赖出问题,只要调整 GCtr 这个类。而所有的外部,都没有文件会引用 GCtr.ts 文件,所以不会有循环引用。

GCtr 本身是Creator 自动 import 的,所以你入口文件可以通过 js.getClassByName(“GCtr”) 后进行实例化,也不会引用到这个GCtr.ts 文件。

这个都是一次性工作,一旦调整好,你开发的时候很爽

我问的详细点
class GCtr {
readonly a = xx
readonly b = xx
readonly c = xx

}

现在我a依赖b,c依赖e,e依赖d
以及首次调用gctr的时候只想初始化f
要怎么做

你想要的是这个?

class GCtr {
    readonly a: ClassA
    readonly b: ClassB
    readonly c: ClassC

    f: ClassF

    constructor() {
        let a = this.a = new ClassA();
        this.b = new ClassB(a);
        this.c = new ClassC(b);
    }

    init() {
        this.f = new ClassF();
    }
}

但是没必要呀, 你 new 一个 ClassF 能有多少性能消耗?它也没做什么内容,干嘛要延迟实例化。

你应该没理解我问的
单例还有一个需求是要首次指定调用的时候才初始化,以及大部分情况下可以自适应处理依赖关系
如果是我现在的做法就是

 // A.ts
 class A {}
 //B.ts
 class B{
     print(){
           Singleton.Instance(A).callA();
     }
 }

//F.ts //F是一个很大玩法, 需要初始大量的资源和数据
class F{}

//Test.ts //初始化B和A, 且B不用修改构造函数 也不会初始化F
Singleton.Instance(B).print()

//Test2.ts
update(){ //首次调用初始化, 并每次update获取进度,完成之后F可用, 并进入F的系统
    Singleton.Instance(F).XX
}

没差呀,你自己试试看就知道了。

// A.ts
class A {
    print() {
        console.log(`A.print`)
    }
}

//B.ts
class B {
    print() {
        fw.a.print();
    }
}
class F {
    /** 特殊初始化接口,只调用一次 */
    onceInit() {
        // 请求服务端数据,或者下载资源
    }

    update() {

    }
}
//Test2.ts
class TestCom extends Component {
    start() {
        fw.f.onceInit();
    }
    update() {
        fw.f.update();
        if(fw.f.done) {
            // 退出 update
        }
    }
}

至于你实例化的时候马上调用初始化,而且依赖 update 的,就应该单独一个函数,要不然很难理解你的这种潜规则的。
而且任务是否完成,理论上要放到内部去做驱动。外部只关心你成功没有,才不关心你要不要驱动。

我看懂你的意思了, 这个在某些情况下是有些问题的. 不适合我说的"开箱后才创建,自动管理依赖"的需求.
假设我有一个预加载的需求, 这个只能继续在GCtr里面继续拆分init列表, 初始化时机从自动(或说统一入口)变成手动, 若哪一天我改动了预加载类的调用关系, 这里有可能还需要手动调整.

是的,时机被移动到了某些业务逻辑中了。