对象之间的交互(三)小结
学习完对象之间的交互之后,脑海中有个结构就可以了。
这个图中有不全面的地方。自底向上使用委托和事件都可以。跨模块之间除了使用事件,使用委托也是可以的。
决定版补充上面这张图,对于决定版架构来说,已经过时了,在决定版架构中,很少遇到需要跨模块发送事件的情况,因为我们引入了层级这个概念,同时在对象之间交互的额方法中又多了一个Command(包括Query),再加上Command——Model——StateChangedEvent模式,基本上跨模块之间发送消息的情况就比较少见了。
对象之间的交互一开始只专注在两个对象之间,目前可以是:
调用方法
委托
事件
命令(包括Query,包含CQRS原则)
最佳实践就是:
自顶向下可以调用方法和命令
自底向上可以用委托和事件通知
决定版架构的出现其实参照了网络客户端-服务器的开发理念。
服务器可以理解为底层,客户端可以理解为表现层。
客户端与服务器的通信一般如下:
Http请求(如果是增删改,比如说Post、RESTfull API,就有点像Command,如果是查询,就像是Query,有点像委托调用)
TCP长连接(连接之后有点像发 ...
对象之间的交互(二)委托和事件
双向依赖介绍与单向依赖的限制双向依赖依赖除了有单向的,还有双向的,即父节点调用子节点的方法,子节点也调用父节点的方法。
我们要坚决杜绝双向依赖,代码要高内聚,松耦合。耦合指的就是对象之间的依赖程度。双向依赖,则是依赖程度最强的一种情况。
在这里不管是自顶向下或自底向上的双向依赖,还是兄弟之间的双向依赖,只要是双向依赖,都要坚决杜绝的。
自顶向下单向依赖的限制我们知道自顶向下的单向依赖指的是,父节点调用子节点的方法。
那么我们在很多情况下,也有子节点通知父节点的情况。比如当子节点播放一段动画,动画播放完毕后,要通知父节点,动画播放完毕。
在这种情况下,单向依赖就有了限制,而且不可以使用双向依赖。
我们引入委托和事件,来解决这个问题。
子节点使用委托单向依赖的限制,即子节点不能调用父节点的方法。
要解决这个问题,很容易想到C#中的委托。
12345678910111213141516171819202122232425262728using UnityEngine;namespace FrameworkDesign2021{ public class PlayerCtrl ...
对象之间的交互(一)单向依赖
对象之间的交互有三种,它们是:
方法
委托
事件
其实还有一种我们后边学习的:命令模式
在第一季时我们引出对象之间的交互,只是直接给出了结论,并没有介绍对象之间的交互这个概念是如何发展起来的。在这里我们着重介绍。
最开始并没有提出对象之间的交互这个概念,而是从“依赖”这个角度逐步发展出来的。
我们先看一下单向依赖:
1234567891011121314151617namespace FrameworkDesign2021.ObjInteractExample{ public class A { public void Say() { Debug.Log("Hello I'm A"); } } public class B { public B() { new A().Say(); } }}
...
单例专精(五)单例复习与补充
Singleton的使用
Singleton
继承
私有构造
MonoSingleton
继承即可
SingletonProperty
实现ISingleton接口
在属性器中实现
MonoSingletonProperty
实现ISingleton接口
继承MonoBehaviour或其子类
在属性器中实现
MonoSingletonPath
支持MonoSingleton与MonoSingletonProperty
单例的最佳实践
严格控制对外暴露的API
严格控制单例的使用范围
模块命名
职责尽量少
严格控制单例对象的生命周期(创建、初始化、销毁)
设计原理
ISingleton有两个作用,一个是泛型的约束,一个是提供统一的初始化接口(OnSingletonInit)。
MonoSingletonPath是为了让单例模块方便管理。
实现套路
通过反射创建实例
Attribute与反射的使用
补充由于Singleton与SIngletonProperty都需要用到通过反射创建实例的功能,所以把这个功能单独提取出来,放到了SingletonCreato ...
单例专精(四)单例的使用范围
在上一节。我们总结了设计单例时要做到的原则
严格控制对外暴露的API,与外界交互只通过静态方法。
严格控制单例的使用范围
严格控制单例对象的生命周期(创建、初始化、销毁)
这一节我们重点讨论第二条
严格控制单例的使用范围严格控制单例的使用范围,要做到这一点,只需要记住一句话就好:不要让单例类的职责太多
在设计单例类不要提供太多API,要非常谨慎地提供API。如果一个逻辑需要调用3个API完成,那么我们就要考虑,可否把这三个API合并成一个API,而把原来地三个API迁移到mInstance内部实现(参见上一节的示例代码)。
严格控制单例的使用范围,一般体现于类的命名上。
比如GameManager,这个单例的作用范围,可能是玩家开始游戏的阶段(开始、暂停、继续、游戏结束)等,但是它的名字太大了,类的名字应该体现类的职责。GameManager名字太模糊,要进一步明确,比如GameStateManager/GameStatusManager,来做游戏状态的管理,GamePlayManager,游戏玩法的管理等等。
再比如,QFramework的UIKit,其作用范围比较广, ...
单例专精(三)单例的最佳实践
单例的特性就是有风险的同时带来了便利
单例的特性单例有两个特性:
保证一个类仅有一个实例。
提供一个全局访问的访问点(Instance)
从特性我们可以总结出:
单例有生命周期,有状态(内部的数据),因为单例是对象实例。
单例可以在全局的任何一个地方获取。
风险也有两点:
如果是懒加载(第一次访问时创建实例)的单例,则看到单例调用时,不知道哪里做了初始化。
任意位置可以访问,项目规模一旦增大就会容易造成混乱。
全局访问的风险生命周期不确定的风险与全局访问的风险相比,后者造成的危害是慢性的,也是相对严重的。
我们来展示单例的一般使用:
12345678910namespace FrameworkDesign2021.BestSingleton{ public class GameManager : MonoSingleton<GameManager> { public void Play() {} public void Pause() {} publ ...
SingletonKit UML绘制
SingletonISingleton先将重点的ISingleton接口绘制成UML
代码:
12345678910/// <summary>/// 单例接口/// </summary>public interface ISingleton{ /// <summary> /// 单例初始化(继承当前接口的类都需要实现该方法) /// </summary> void OnSingletonInit();}
此图是类图,接口的UML图不是这样的
SingletonCreator12345678910111213141516171819202122232425262728293031323334353637383940/// <summary>/// 普通单例创建类/// </summary>internal static class SingletonCreator{ static T CreateNonPublicConstructorObject& ...
单例专精(二)SingletonKit与接口
SingletonKit中的接口SingletonKit中有一处接口定义,代码如下:
1234public interface ISingleton{ void OnSingletonInit();}
我们的标准C#单例Singleton<T>、Mono单例MonoSingleton<T>都继承了此接口,SingletonProperty<T>和MonoSingletonProperty<T>都约束了T必须继承ISingleton
其中的OnSingletonInit()规定了一个单例在初始化时,使用此方法来初始化,而不是在单例的构造当中初始化。一个标准C#单例仅仅有一个私有构造函数来做一下限制即可,这个私有构造中不包含初始化的代码。需要注意的是Mono单例不能有构造,其在Awake中初始化的时间比OnSingletonInit要早,一般情况下Mono单例不要在Awake里面做初始化。
这种不要在构造函数中初始化,而是在Init方法中初始化的思想在QFramework设计阶段就有体现,不论是System、Mo ...
C#Lock线程锁和Lock语句
C#中的Lock语句将Lock中的语句块视为临界区,让多线程访问临界区代码时,必须顺序访问,它的作用是在多线程环境下,确保临界区中的对象只被一个线程操作,防止出现对象被多次改变的情况。lock属于互斥锁,在编译时此语句通过System.Threading.Monitor.Enter和System.Threading.Monitor.Exit实现线程锁。
注意的地方是,lock参数对象必须是一个不可变对象,否则无法阻止另一个线程进入临界区,常见的传入参数多用private static readonly或者private static修饰。
lock 语句 - C# 参考 | Microsoft Learn
Lock锁演示创建一个控制台应用程序,该应用程序使用lock关键字来控制奇数线程和偶数线程的打印,要求首先执行偶数线程,然后执行奇数线程。 根据题目要求,代码如下。
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051using System;usin ...
单例专精(一)SingletonKit快速入门
单例是我们最常用的设计模式,尤其是在Unity项目的开发中,几乎每个项目都会用到单例模式。
QFramework包含的单例实现
网址:SingletonKit/SingletonKit.cs at main · liangxiegame/SingletonKit (github.com)
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 ...