单例从入门到放弃

 

一、引言

单例,一个入门设计模式,每个码农都熟的烂在心里了。无论设计怎样的框架,你总逃避不了单例的思路。

 

大部分人应该和我一样,讨厌static。最直接的原因是,要多写一个修饰词;在被策划的催逼的混沌状态下,更无奈静态方法中无法访问类成员变量而飙升怒气值,急躁驱生了单例。

 

当项目进行到了高潮,需求突然变化,“改改改…”。变化更大的是程序员的心理,“MMP,老子刚写完就要改。。。”。

能不改么?不能~~~!

努力去翻过去的代码。。。嗡嗡响的不只是苍蝇般的催逼,你膨胀的脑袋里装的也不只是要改的内容。。。

 

到处是长长的 类名 + GetInstance + 方法名,马丹啊…我想静静。。。。

 

我这个功能变了,我想改类名,玛蛋啊,到处有引用,想静静。。。

 

二、单例的用处

1、保证同一个进程中只有一个实例。

单例的初心。有别的想法都是移情别恋,会带来宫斗的。

2、为了方便调用

绝大多数人的用法。一个UI,一个公共类,一个功能类。。。

三、单例的实现

1、懒汉式

线程不安全

2、饿汉式

静态初始化,线程安全。由运行环境负责初始化

3、线程安全懒汉-单锁

同样利用了静态初始的特性,可以在构造中编写复杂逻辑。

单锁实现了线程安全,但是在复杂初始化内容下,性能上有影响。

4、线程安全懒汉-双锁

5、延迟初始

实现线程安全,又可以在初始前进行一些逻辑处理

 

四、Unity对象单例使用注意

a)如果单例是继承自MonoBehaviour, 初始化不能再构造函数中进行,要在Awake或Start中进行,但这只能通过技巧实现运行时单例。

Unity文档提及:“避免使用构造函数 不要在构造函数中初始化任何变量,使用AwakeStart实现这个目的。即使是在编辑模式中Unity也自动调用构造函数,这通常发生在一个脚本被编译之后,因为需要调用构造函数来取向一个脚本的默认值。构造函数不仅会在无法预料的时刻被调用,它也会为预设或未激活的游戏物体调用。”

MonoBehaviour类的实例化已经被Unity托管,并且在Editor模式下启动时,回被调用两次,游戏停止也会调用一次,会导致不必要的逻辑错误。

MonoBehaviour单例实现如下:

19行、44行通过运行时异常的方式,保证单例。好在MonoBehaviour不能运行在子线程。

40行保证该对象不销毁

MonoBehaviour对象避免在子线程访问使用,如果出现子线程访问,尽量报出异常,禁止使用。

如果通过反射来调用私有构造,那就是 无下限的做法了。

单例定义好后,需要挂载到特定的GameObject。如果不需要挂在特定的GameObject上,可以使用通用的单例模板。

b)Unity通用单例模板:

需要单例化的类继承改类,并把类型作为模板参数。

不用考虑线程安全,子线程下实例化GameObject会被Unity报错。(新版本如果支持子线程访问的话,可以改进一下模板)

五、分界线

六、单例带来的便利性

1、方便控制实例数量。禁止了随意实例化,造成不必要的内存申请,同时避免逻辑错误

2、方便数据共享,方便通用代码的调用。

3、编写简单,方便调用。一个项目有很多模块,为了便捷,都使用单例,写起来快速无脑。

七、带来的问题

    1. 单例过多,相互调用,增加模块依赖,增加维护成本,降低代码可读性.
    2. 单行代码偏长:ClassName.Instance.MethodName(Param1…)。降低可读性
    3. 结构随性,不宜控制
    4. 需要转换为多例时,要重新编写调用接口和之前调用的地方
    5. 不方便复用和重构
    6. …..

八、改进方法-引入小三中介

1、分类管理。

比如表格数据,建个表格的管理类。通过该管理器进行创建和访问。

比如UI,建个UI的管理器。

角色管理器、场景管理器、AI管理器、资源管理器、任务管理器。。。。

可以大大减少单例的数量。

2、管理器也很多,需要对管理器进行管理

创建管理器的管理类,ManagerCenter。结构雏形:

九、解决调用获取问题-引入小三

1、基类是基础。引入各模块通用的基类,吧通用的功能在抽象出来。

比如在基类中定义各个管理器的的引用。

比如在基类中定义通用的调用接口。

2、定义项目唯一单例-Façade,简化框架的核心接口的调用。

把访问不方便、最基础最常用的接口。

把GameFacade的实例作为基类的属性方便子类调用,注意析构时的处理。

 

 

3、工具类中使用项目接口

1 避免使用。不建议使用。

2 特殊情况下,通过GameFacade.Instance来调用。

3 定义静态Façade类,包装GameFacade.Instance接口

 

十、解决模块间调用

1、细化模块粒度和大模块粘合

模块过大,不方便模块间的掉用。模块单位除了以Manager方式组织外,需要引入Controller对模块细化的编写。

还可以通过Controller粘合各个大模块。

2、订阅+通知(或隐喻事件)

使用观察者模式实现。

类似事件系统,NotificatinCenter对事件主题Subject(世界分类)进行管理,并提供便捷的接口托管。

Subject事件主题,实现树形结构,实现树形逻辑的交换。

Observer事件接收方,需要注册到事件主题。

Notification事件内容。

基本是Windows事件机制。

3、使用Command模式粘合

当要控制主体或模块较多时,而又需要规避状态的复杂性(无状态),最为适用。也可结合事件系统实现。

但在调用时回产生执行对象,一般不大量使用,或将Command装进对象池。

十一、分割下

十二、复合

回过头发现,虽然描述的系统不多,但使用起来会相互交叉,本来为了简化结构,降低耦合,却加大了编码复杂度。

这时需要对基础框架进行简化。

  1. 前面已经提到过使用Façade模式,封装复杂性。
  2. 使用代理、组合方式,让基类具有多种功能,比如让Manager、Controller托管事件系统的接口

 

十三、结语

自此,我们初略完成了基础框架的搭建,规避了单例的各项缺点,融入单例的优点,便捷编码。同时使结构清晰,也可以无脑编码了。

但真正在编程过程中,会有很多特殊情况,或更细节的内容。

其实大家只要记住内聚、解耦的目的,使用小三的基本方法,多去实验,就可以用实现结构清晰、简单、便于维护的代码结构

  • 后续

后面打算结合实战内容,讲一下MVC这个能撑起整个结构设计思想。                       

在讲一下《设计模式的本质》,《状态模式、表驱动与状态机》。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值