技术笔记(1)QFramework
-
希望实现的功能或目标:
- 了解学习游戏开发中的架构演化过程
- 了解学习IOC容器、DI等相关概念
-
学习笔记:
-
BindableProperty类
- 实际上是数据+事件
- 我理解为将模型层中的一个数据整合升级成一个类,并将修改和获取其的具体方法放在属性的get和set方法中
- 比如killCount每次被修改时,在它的set方法中顺带调用一个事件去修改表现层
-
ICommand接口
-
实现了Execute()方法
-
实现了该接口的实例被调用的形式举例:
new KillEnemyCommand .Execute();
-
可被表现层用以修改模型层的数据
-
-
底层系统层的元素
- Command
- Event
- Model
-
架构使用原则
- 事件(Event)由系统层向表现层发送
- 表现层只能用Command改变底层系统层的状态和数据
- 表现层可以直接查询数据
-
单例工具类 Singleton
-
实现如下:
public calss Singleton<T> where T :Singleton<T> { private static T mInstance; public static T Instance { get { if(mInstance == null) { var type = typeof(T); var ctors = type.GerConstructors(BindingFlags.Instance | BindingFlags.NonPublic); var ctor = Array.Find(ctors,c => c.GetParameters().Length == 0); if(ctor == null) { throw new Exception("Non Public Constructor Not Found in" + type.Name); } mInstance = ctor.Inboke(null) as T; } return mInstance; } } }
-
-
IOC容器
-
其逻辑为:简单理解为一个字典,这个字典以类型为Key,Object为Value
-
最少有两个核心API,根据Type注册实例,根据Type获取实例
-
具体实现:
-
public class IOCContainer { private Dictionary<Type, object> mInstances = new Dictionary<Type, object>(); public void Register<T>(T instance) { var key = typeof(T); if(mInstances.ContainsKey(key)) { mInstances[key] = instance; } else { mInstances.Add(key, instance); } } public T Get<T>() where T : class { var key = typeof(T); if(mInstances.TryGetValue(key, out var retInstance)) { return retInstance as T; } return null; } }
-
-
-
模块化
-
表现层模块化:直接使用平台提供的概念即可,比如Scene或Prefab
-
底层系统层模块化:需要考虑模块对象如何获取?如何增加一个模块?
-
当前在底层系统层已经模块化的为模型层,如下:
-
public static class CounterModel { public static BindableProperty<int> Count = new BindableProperty<int>(){ Value = 0; }; }
-
-
此处展示的CounterModel为一个静态类,其优势为:
- 静态类可以直接获取
- 扩展一个静态类只需要加上一个static
-
相应的,其缺点为:
- 静态类没有访问限制,会留下隐患
- 模块识别度不高,无法分清其到底是模块还是工具类还是常量类
-
解决方式:
-
一种方法是将其设置为单例类,必须通过 模块类.Instance 获取,增加模块获取难度。继承单例工具类,并取消静态后,模型层变为:
-
public class CounterModel :Singleton<CounterModel> { public CounterModel() {} //补上一个无参构造函数 public BindableProperty<int> Count = new BindableProperty<int>(){ Value = 0; }; }
-
-
另一种方法是不用单例,把CounterModel注册进IOC容器之中,通过 CountApp.Get<CounterModel>() 获取:
-
public class CounterApp { private static IOCContainer mContainer; static void MakeSureContainer() { if(mContainer == null) { mContainer = new IOCContainer(); Init(); } } static void Init() { mContainer.Register(new CounterModel()); } public static T Get<T>() where T : class { MakeSureContainer(); return mContainer.Get<T>(); } }
-
-
-
-
-
产生的疑惑:
-
Command、Event初次接触,还有点模糊不清
-
面向组件编程?ESC架构?
-
单例类中所涉及的反射的相关概念?
-
要用单例模式的话不是直接在类内设置一个公开静态变量引用自身就行吗?为什么还要去弄个单例工具类?其好处和优点是什么?
-
单例类没有访问限制这一点会引发什么风险?
-
引入单例类和IOC之后还是只看到了变复杂的流程和结构,暂时没看到其带来的好处。
-
IOC容器里只装模块类吗?还是能装哪些类?
-
-
思考和解惑:
-
关于QFramework里的Command和Event:
-
定义:
-
Command指客户端发起的改变服务器端状态的请求,比如:增加计数、减少计数等;
-
Event指服务器端发布的已经发生的状态变化,比如计数增加事件、计数减少事件等。
-
-
好处:
- 降低Controller的复杂度,交互逻辑分离到Command中,表现逻辑分离到Event中,使Controller更加简洁和清晰;
- 提高可扩展性和可维护性,增减功能只需要注册和删改Command和Event,不需要修改其余代码
-
对比与区别:
- Command是主动的行为,需要有一个明确的接受者来执行;而Event是一种被动的行为,不关心有没有监听者来响应。
- Command有返回值,返回成功或失败的结果;而Event无返回值,只负责通知发生甚么事了,不需要等待任何反馈
-
-
面向组件编程:将程序看作是由一系列可重用的组件组成的,这些组件通过组合和配置来构建应用程序。Unity就属于一个典型例子。
-
ESC架构:将游戏对象分为实体Entity、逐渐Component和系统System三个部分,其中:
- 实体是游戏对象的容器,它没有任何行为或属性,只是一个标识符。
- 组件是游戏系统对象的属性活性位,例如位置、速度、生命值等。
- 系统是游戏对象的行为逻辑,例如移动、攻击、碰撞检测等。
-
反射:作为一种机制,运行程序在运行时动态地获取和操作类的信息,比如类的名称、属性、方法、构造函数等。通过反射可以访问和修改类的私有成员,包括私有的构造函数。
-
在上述泛型单例类中,用到反射概念的语句主要有以下几个:
- var type = typeof(T); //获取泛型类型T的类型信息
- var ctors = type.GetConstructors(BindingFlags.Instance|BindingFlags.NonPublic); //获取T类型的所有非公共的实力构造函数
- var ctor = Array.Find(ctors, c=>c.GetParameters().Length==0); //从ctors数组中查找一个没有参数的构造函数
- mInstance = ctor.Invoke(null) as T; //传入null参数,调用T类型的非公共无参构造函数,创造一个T类型的实例
-
-
对于这种简单的单例模式:
public class MyClass myclass : monobehavior { public static MyClass instance; private void awake() { instance = this; } }
-
其存在的问题是:
- 该单例类必须继承自MonoBehaviour,这意味着它必须挂载到一个场景中的游戏对象上,而不能作为一个普通的C#类
- 其实例化依赖于Awake方法的调用顺序,如果有其他脚本在Awake方法中调用该单例类,可能会出现空引用的异常
- 该单例类没有保证线程安全,如果有多个线程同时访问,可能会出现静态条件
-
相比之下,用泛型单例工具类来实现单例模式的话,有以下优势:
- 不必继承自MonoBehaviour
- 保证实例创建时机,保证线程安全
- 使用私有的无参构造函数,防止外部创建,也防止反射破坏单例类特性
-
-
单例类没有访问限制的风险:
- 破坏唯一性,如果没有将构造函数设为私有,那么外部可以new出很多个单例类的实例
- 多线程不安全,可能出现多个线程同时创建或访问单例类的实例
-
好处:
- 单例类可以保证一个类只有一个实例,从而节省内存,提高访问速度,控制资源的使用,方便数据的共享等。适用于那些需要全局唯一的对象,如配置文件、日志对象、数据库连接池等。
- IOC可以降低类之间的耦合,提高类的可复用性和可维护性,实现控制反转和依赖注入,方便测试和扩展等。适用于那些需要灵活配置和组合的对象,如业务逻辑层、数据访问层、控制器层等。
-
目前已知可以装模型层、系统层和工具层的类
-