找回密码
 立即注册
查看: 736|回复: 16

Unity里滥用单例模式有弊端吗?

[复制链接]
发表于 2021-2-23 15:08 | 显示全部楼层 |阅读模式
最近用Unity做游戏时学习到了单例模式,感觉很方便,甚至把大部分脚本都变成了单例模式,但我觉得这样滥用单例模式应该有弊端吧……比如我现在有ABCD脚本,这些脚本里很多变量经常要互相传递,以前都是直接申明变量,然后在start里获取调用,但如果全部变成单例模式之后,就不需要申明和获取了,直接A.instance.x来调用……
发表于 2021-2-23 15:15 | 显示全部楼层
在知乎上直接询问抽象的概念,很难得到满意的答案,因为需要具体问题具体分析。何况在Unity中“单例”的写法不止一种。“C#单例”与“Unity单例”的写法和原理区别非常大。


滥用单例肯定有弊端,但建议现在不用过多考虑,放心大胆的写就好了。
当我们继续做下去的时候,迟早会发现一些设计不合理的地方。很多写法直到第一次遭遇BUG的时候,才会知道自己的思路哪里有问题。
看题主的描述,有一些思路跑偏的地方。这里帮题主指出一些问题:
1、脚本本身只是一个class,并非实例(实例就是对象的意思)。脚本只有挂载到物体上,才是一个组件实例。那么题中所说的ABCD挂载在哪个物体上呢,是不是确定ABCD都只有一个对象?
2、如果A、B、C、D脚本都只有一个实例化的组件,那么用单例是不会出任何问题的;反过来说,只要用了单例模式,ABCD组件的实例都有且只能存在一个。
3、如果是“C#单例”,也就是不需要挂载到物体上、不继承Monobehaviour的单例,又是另一码事了。
4、在单个场景中,用单例不会有任何问题。当场景来回切换的时候,可能就会遇到BUG。具体会不会出问题,取决于逻辑是怎么写的。
5、当脚本需要挂载到多个物体上、有多个实例时,用单例是必然不行的。比如玩家和NPC都要用同样的角色控制器脚本,那么单例模式就不适用了。
这些问题不需要立即想明白,建议在实践中勤于思考、慢慢琢磨。


一个游戏中需要创建的对象可能非常多,有很多物体还有更多的组件,有两个问题涉及到编程的基本思路:
某个脚本组件,它的职责是什么,应至少包含哪些数据?
当一个功能由多个脚本协作完成,由谁调用谁?参数如何传递?
我们刚开始写代码的时候,很难很难想清楚具体的逻辑关系,只能先把功能实现出来,再整理和反思,最后通过调整代码结构让思路更清晰。
学习阶段本来就是实践和试错的过程,无论新手老手,都不可能一下写出完美的代码,我们只能在实践的指导下,反复改良,不断优化代码和思路。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
发表于 2021-2-23 15:20 | 显示全部楼层
除非一些架构层面的管理器类,否则应该很少会出现某个类在全局只有一个对象的情况。
比如AssetManager,在全局只有一个,正常。
某个配表的字典,在全局只有一个,也对劲。
这些东西也不会出现别的回答说的gc问题:他们本来就应该一直存在于内存里。


那么其他有些地方,尤其是游戏业务逻辑相关的东西,用单例就奇怪了,
最常见就是说挂载在某个GameObject上的组件,
比如说你有个管理游戏人物的组件用了单例,就应该得思考:1. 是不是代码的设计出了问题,只要你游戏里会出现不止一个角色,那这个类肯定应该是设计成可复用的情况,这是与单例不可共存的。2. 有没有管理好生命周期问题,如果外部调用的时候这个对象还不存在怎么办,如果这个对象已经不需要了,有没有释放它的单例,等等。
同理,如果管理某一页具体UI的代码出现了单例,多半是设计出了问题。




以下是一些适度使用单例,以及够好的组织代码的建议:
    基础架构层面的管理器类,可以使用单例,也可以使用依赖注入。业务逻辑层面的管理器,可以使用单例(我们游戏用lua的基本全是单例),更推荐使用依赖注入。业务逻辑的具体代码,如操作某一页UI的组件,操作场景中某个对象的组件,务必不要使用单例。推荐使用消息事件广播。配表相关的字典对象,虽然是全局唯一,但推荐使用工厂模式(顺带还可以实现惰性加载)
发表于 2021-2-23 15:29 | 显示全部楼层
单例是把双刃剑。用起来确实很方便,比起GetComponent还能提升查找速度;对变量合理的归类,还能增强代码的可维护性(不用到处去找接口了)。缺点也很多,使用的时候多注意一下:
    被引用的对象不能被GC回收。比如引用了资源对象,被Destroy后,虽然c++端的内存被释放了,但是c#端的对象引用却还在。如果不小心引用了一个组件,组件又引用了一大堆其他组件,则引用链上的所有对象都将无法被GC回收。代码耦合度变高。如果大量单例对象间交叉引用,会让代码变的很乱,不方便后续维护;而且工程如果大了之后,也不方便拆分工程,引起编译时间过长。可以适当的拆分重组一下功能,让引用尽可能变成单向的。也可以引入事件中心,来解耦合。功能安全性。成员变量容易被其他地方随意修改,不好排查。某些方法可能是特定场合才使用的,但是其他人并不知道。线程安全。涉及到线程相关的操作,非常容易出事。
发表于 2021-2-23 15:33 | 显示全部楼层
自己都知道是滥用了,肯定就知道有弊端啦
比如逻辑高度耦合,有些东西需求变了,改起来那是想当刺激。( *` * )
发表于 2021-2-23 15:42 | 显示全部楼层
所以我只用两个单例,一个读取配置文件,一个充当web系统里的数据库。要任何数据直接从这个数据库里取,类似web里的sql语句。
发表于 2021-2-23 15:51 | 显示全部楼层
有,1.很容易造成内存得不到释放 2.代码依赖性,耦合性太强。
 楼主| 发表于 2021-2-23 15:53 | 显示全部楼层
之前c++项目用了太多的单件模式,现在我的观点是尽量不用。只用一个全局对象gameapp来管理那些唯一对象,都比直接用单件好,而大多数单件都是没必要的。
弊端的话 1.单件的生命周期完全不受框架管理。2.单件的调用不受管理,容易变成网状耦合。3.单件太方便,导致会被滥用。
发表于 2021-2-23 15:58 | 显示全部楼层
如果忘记了某个单例的存在就很尴尬。
发表于 2021-2-23 15:59 | 显示全部楼层
首先将功能模块化,模块和模块之间不要耦合,如果都要单例的话,耦合严重,不利于维护和功能扩展,团队人多的时候,每个人负责的模块都不想有其他人模块的代码吧
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-9-20 15:07 , Processed in 0.095940 second(s), 26 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表