QFramework官网地址:Wiki - 木兰确实
1、界面设计
(1)CounterAppController的界面
BtnAdd和BtnSub为Button组件,CountText为Text组件,后续的脚本挂载Canvas上。
(2)OnGUI
OnGUI是Unity中通过代码驱动的GUI系统,主要用来创建调试工具、创建自定义属性面板、创建新的Editor窗口和工具达到扩展编辑器效果。
OnGUI在布局上,坐标系原点在屏幕左上角,所以不建议使用在项目UI当中。
具体使用方式参考:Unity OnGUI 的可视化编辑 - Mr.Cat~ - 博客园
2、最简单的MVC架构
using System.Collections;
using System.Collections.Generic;
using QFramework;
using UnityEngine;
using UnityEngine.UI;
namespace QFrameWork.Example
{
// Controller
public class CounterAppController : MonoBehaviour
{
// View
private Button mBtnAdd;
private Button mBtnSub;
private Text mCountText;
// Model
private int mCount = 0;
private void Start()
{
// View 组件获取
mBtnAdd = transform.Find("BtnAdd").GetComponent<Button>();
mBtnSub = transform.Find("BtnSub").GetComponent<Button>();
mCountText = transform.Find("CountText").GetComponent<Text>();
// 监听输入
mBtnAdd.onClick.AddListener(() =>
{
// 交互逻辑
mCount++;
// 表现逻辑
UpdateView();
});
mBtnSub.onClick.AddListener(() =>
{
// 交互逻辑
mCount--;
// 表现逻辑
UpdateView();
});
UpdateView();
}
void UpdateView()
{
mCountText.text = mCount.ToString();
}
}
}
View在QFramework的MVC定义里就是提供关键组件的引用。
缺点:
mCount在多个Controller中使用,mCount有各种处理方式,即mCount会随着业务增加发展成一个需要共享的数据。
最快的做法是把mCount变量变成静态变量或者单例,但是这样虽然写起来很快,但是后期维护时会产生很多问题。
此时需要引入Model的概念。
3、基于QFramework的第一个程序
(1)代码示例
创建名称为CounterAppController的脚本
注意:文件名必须和继承自MonoBehaviour的类的名称保持一致,否则会报各种奇葩问题。
代码如下:
using UnityEngine;
using UnityEngine.UI;
namespace QFrameWork2.Example
{
// 定义一个Model对象
public class CounterAppModel: AbstractModel
{
public int Count;
protected override void OnInit()
{
Count = 0;
}
}
// 定义一个架构(提供MVC、分层、模块管理等)
public class CounterApp : Architecture<CounterApp>
{
protected override void Init()
{
// 注册Model
this.RegisterModel(new CounterAppModel());
}
}
// Controller,实现IController接口
public class CounterAppController : MonoBehaviour, IController
{
// View
private Button mBtnAdd;
private Button mBtnSub;
private Text mCountText;
// Model
private CounterAppModel mModel;
void Start()
{
// 获取模型
mModel = this.GetModel<CounterAppModel>();
// View 组件获取
mBtnAdd = transform.Find("BtnAdd").GetComponent<Button>();
mBtnSub = transform.Find("BtnSub").GetComponent<Button>();
mCountText = transform.Find("CountText").GetComponent<Text>();
// 监听输入
mBtnAdd.onClick.AddListener(() =>
{
// 交互逻辑
mModel.Count++;
// 表现逻辑
UpdateView();
});
mBtnSub.onClick.AddListener(() =>
{
// 交互逻辑
mModel.Count--;
// 表现逻辑
UpdateView();
});
UpdateView();
}
void UpdateView()
{
mCountText.text = mModel.Count.ToString();
}
// 指定架构
public IArchitecture GetArchitecture()
{
return CounterApp.Interface;
}
private void OnDestroy()
{
mModel = null;
}
}
}
(2)Architecture
Architecture提供了一整套架构的解决方案,包括管理模块。
(3)Model
Model的引入是为了解决数据共享的问题,而不单单是为了让数据和表现分离。
数据共享分两种:空间上的共享和时间上的共享。
空间上的共享:多处代码需要访问Model里的数据。
时间上的共享:就是存储功能,将上一次关闭App之间的数据存储到一个文件里,下次打开时获得上次关闭App之前的数据。
(4)交互逻辑和表现逻辑
交互逻辑:
从用户输入开始到数据变更的逻辑
顺序为:View -> Controller -> Model
表现逻辑:
数据变更到在界面显示的逻辑
顺序为:Model -> Controller -> View
(5)代码缺陷
有一定规模的项目中,表现逻辑和交互逻辑非常多,一个Controller很容易做到上千行代码。
解决方案:引入Command方式,即引入命令模式,分担Controller的交互逻辑的职责。
4、加入Command
(1)代码示例
using QFramework;
using UnityEngine;
using UnityEngine.UI;
namespace QFrameWork2.Example
{
// 定义一个Model对象
public class CounterAppModel: AbstractModel
{
public int Count;
protected override void OnInit()
{
Count = 0;
}
}
// 定义一个架构(提供MVC、分层、模块管理等)
public class CounterApp : Architecture<CounterApp>
{
protected override void Init()
{
// 注册Model
this.RegisterModel(new CounterAppModel());
}
}
// 引入Command
public class IncreaseCountCommand : AbstractCommand // ++
{
protected override void OnExecute()
{
this.GetModel<CounterAppModel>().Count++;
}
}
public class DecreaseCountCommand : AbstractCommand // --
{
protected override void OnExecute()
{
this.GetModel<CounterAppModel>().Count--;
}
}
// Controller,实现IController接口
public class CounterAppController : MonoBehaviour, IController
{
// View
private Button mBtnAdd;
private Button mBtnSub;
private Text mCountText;
// Model
private CounterAppModel mModel;
void Start()
{
// 获取模型
mModel = this.GetModel<CounterAppModel>();
// View 组件获取
mBtnAdd = transform.Find("BtnAdd").GetComponent<Button>();
mBtnSub = transform.Find("BtnSub").GetComponent<Button>();
mCountText = transform.Find("CountText").GetComponent<Text>();
// 监听输入
mBtnAdd.onClick.AddListener(() =>
{
// 交互逻辑
this.SendCommand<IncreaseCountCommand>();
// 表现逻辑
UpdateView();
});
mBtnSub.onClick.AddListener(() =>
{
// 交互逻辑
this.SendCommand<DecreaseCountCommand>();
// 表现逻辑
UpdateView();
});
UpdateView();
}
void UpdateView()
{
mCountText.text = mModel.Count.ToString();
}
// 指定架构
public IArchitecture GetArchitecture()
{
return CounterApp.Interface;
}
private void OnDestroy()
{
mModel = null;
}
}
}
(2)Command的好处
有了Command,帮助分担了Controller的交互逻辑,使得Controller成为一个薄薄的一层,在需要修改Model的时候,Controller只要调用一句简单的Command即可。
其他好处罗列:
- Command可以复用,Command可以调用Command
- Command可以定制Command队列,也可以让Command按照特定的方式执行
- 就算代码再乱,也只是在一个Command对象里乱,而不会影响其他的对象
- 将方法封装成命令对象,可以实现对命令对象的组织、排序、延时等操作
(3)代码缺陷
表现逻辑的代码看起来不智能。
每次调用逻辑之后,表现逻辑部分都需要手动调用一次(UpdateView方法)。
在一个项目中,表现逻辑的调用次数,至少会和交互逻辑的调用次数一样多。因为只要修改了数据,对应地就要把数据地变化在界面上表现出来。
解决方案:引入一个事件机制来解决这个问题,流程示意如下:
通过Command修改数据,当数据发生修改后发送对应地数据变更事件。
5、加入Event事件
(1)代码示例
using QFramework;
using UnityEngine;
using UnityEngine.UI;
namespace QFrameWork2.Example
{
// 定义一个Model对象
public class CounterAppModel: AbstractModel
{
public int Count;
protected override void OnInit()
{
Count = 0;
}
}
// 定义一个架构(提供MVC、分层、模块管理等)
public class CounterApp : Architecture<CounterApp>
{
protected override void Init()
{
// 注册Model
this.RegisterModel(new CounterAppModel());
}
}
// 定义数据变更事件
public struct CountChangeEvent { }
// 引入Command
public class IncreaseCountCommand : AbstractCommand // ++
{
protected override void OnExecute()
{
this.GetModel<CounterAppModel>().Count++;
this.SendEvent<CountChangeEvent>();
}
}
public class DecreaseCountCommand : AbstractCommand // --
{
protected override void OnExecute()
{
this.GetModel<CounterAppModel>().Count--;
this.SendEvent<CountChangeEvent>();
}
}
// Controller,实现IController接口
public class CounterAppController : MonoBehaviour, IController
{
// View
private Button mBtnAdd;
private Button mBtnSub;
private Text mCountText;
// Model
private CounterAppModel mModel;
void Start()
{
// 获取模型
mModel = this.GetModel<CounterAppModel>();
// View 组件获取
mBtnAdd = transform.Find("BtnAdd").GetComponent<Button>();
mBtnSub = transform.Find("BtnSub").GetComponent<Button>();
mCountText = transform.Find("CountText").GetComponent<Text>();
// 监听输入
mBtnAdd.onClick.AddListener(() =>
{
// 交互逻辑
this.SendCommand<IncreaseCountCommand>();
});
mBtnSub.onClick.AddListener(() =>
{
// 交互逻辑
this.SendCommand<DecreaseCountCommand>();
});
UpdateView();
// 表现逻辑
this.RegisterEvent<CountChangeEvent>(e =>
{
UpdateView();
}).UnRegisterWhenGameObjectDestroyed(gameObject);
}
void UpdateView()
{
mCountText.text = mModel.Count.ToString();
}
// 指定架构
public IArchitecture GetArchitecture()
{
return CounterApp.Interface;
}
private void OnDestroy()
{
mModel = null;
}
}
}
引入事件之后,可以在多处发送事件,然后在页面逻辑这一处监听事件进行处理。这样减缓了很多交互逻辑。
加入Command、Event之后处理的流程示意为:
6、加入Utility
(1)引入Utility的原因
如果需要某个类支持存储功能,可以参考如下操作:
存在的问题:
- 如果存储的数据非常多,则Model层会有大量的存储、加载相关代码
- 假如不使用PlayerPrefs,换成EasySave或SQLite,会造成大量的修改工作
(2)示例
using QFramework;
using QFramework.Example;
using UnityEngine;
using UnityEngine.UI;
namespace QFrameWork2.Example
{
// 定义一个Model对象
public class CounterAppModel: AbstractModel
{
public int Count;
protected override void OnInit()
{
var storage = this.GetUtility<Storage>();
Count = storage.LoadInt(nameof(Count));
// 通过CounterApp.Interface监听数据变更事件
CounterApp.Interface.RegisterEvent<CountChangeEvent>(e => {
this.GetUtility<Storage>().SaveInt(nameof(Count), Count);
});
}
}
// 定义Utility层
public class Storage : IUtility
{
public void SaveInt(string key, int value)
{
PlayerPrefs.SetInt(key, value);
}
public int LoadInt(string key, int defaultValue = 0)
{
return PlayerPrefs.GetInt(key, defaultValue);
}
}
// 定义一个架构(提供MVC、分层、模块管理等)
public class CounterApp : Architecture<CounterApp>
{
protected override void Init()
{
// 注册Model
this.RegisterModel(new CounterAppModel());
// 注册存储工具的对象
this.RegisterUtility(new Storage());
}
}
// 定义数据变更事件
public struct CountChangeEvent { }
// 引入Command
public class IncreaseCountCommand : AbstractCommand // ++
{
protected override void OnExecute()
{
this.GetModel<CounterAppModel>().Count++;
this.SendEvent<CountChangeEvent>();
}
}
public class DecreaseCountCommand : AbstractCommand // --
{
protected override void OnExecute()
{
this.GetModel<CounterAppModel>().Count--;
this.SendEvent<CountChangeEvent>();
}
}
// Controller,实现IController接口
public class CounterAppController : MonoBehaviour, IController
{
// View
private Button mBtnAdd;
private Button mBtnSub;
private Text mCountText;
// Model
private CounterAppModel mModel;
void Start()
{
// 获取模型
mModel = this.GetModel<CounterAppModel>();
// View 组件获取
mBtnAdd = transform.Find("BtnAdd").GetComponent<Button>();
mBtnSub = transform.Find("BtnSub").GetComponent<Button>();
mCountText = transform.Find("CountText").GetComponent<Text>();
// 监听输入
mBtnAdd.onClick.AddListener(() =>
{
// 交互逻辑
this.SendCommand<IncreaseCountCommand>();
});
mBtnSub.onClick.AddListener(() =>
{
// 交互逻辑
this.SendCommand<DecreaseCountCommand>();
});
UpdateView();
// 表现逻辑
this.RegisterEvent<CountChangeEvent>(e =>
{
UpdateView();
}).UnRegisterWhenGameObjectDestroyed(gameObject);
}
void UpdateView()
{
mCountText.text = mModel.Count.ToString();
}
// 指定架构
public IArchitecture GetArchitecture()
{
return CounterApp.Interface;
}
private void OnDestroy()
{
mModel = null;
}
}
}
做法概述:
定义一个继承自IUtility的类,实现SaveXX()和LoadXX()的接口,用于存储和载入数据,在函数内部使用自己喜欢的存储方式。
通过监听数据变更进行存储。
7、加入System
像规则类的逻辑,比如分数统计或者成就统计等代码,不适合分散写在Command里,而适合统一写在一个对象里,这个对象就是System对象。
示例:
using QFramework;
using QFramework.Example;
using UnityEngine;
using UnityEngine.UI;
namespace QFrameWork2.Example
{
// 定义一个Model对象
public class CounterAppModel: AbstractModel
{
public int Count;
protected override void OnInit()
{
var storage = this.GetUtility<Storage>();
Count = storage.LoadInt(nameof(Count));
// 通过CounterApp.Interface监听数据变更事件
CounterApp.Interface.RegisterEvent<CountChangeEvent>(e => {
this.GetUtility<Storage>().SaveInt(nameof(Count), Count);
});
}
}
public class AchievementSystem : AbstractSystem
{
protected override void OnInit()
{
var model = this.GetModel<CounterAppModel>();
this.RegisterEvent<CountChangeEvent>(e =>
{
if(model.Count == 10)
{
Debug.Log("触发 点击达人 成就");
}else if(model.Count == 20)
{
Debug.Log("触发 点击专家 成就");
}else if(model.Count == -10)
{
Debug.Log("触发 点击菜鸟 成就");
}
});
}
}
// 定义Utility层
public class Storage : IUtility
{
public void SaveInt(string key, int value)
{
PlayerPrefs.SetInt(key, value);
}
public int LoadInt(string key, int defaultValue = 0)
{
return PlayerPrefs.GetInt(key, defaultValue);
}
}
// 定义一个架构(提供MVC、分层、模块管理等)
public class CounterApp : Architecture<CounterApp>
{
protected override void Init()
{
// 注册Model
this.RegisterModel(new CounterAppModel());
// 注册存储工具的对象
this.RegisterUtility(new Storage());
// 注册System
this.RegisterSystem(new AchievementSystem());
}
}
// 定义数据变更事件
public struct CountChangeEvent { }
// 引入Command
public class IncreaseCountCommand : AbstractCommand // ++
{
protected override void OnExecute()
{
this.GetModel<CounterAppModel>().Count++;
this.SendEvent<CountChangeEvent>();
}
}
public class DecreaseCountCommand : AbstractCommand // --
{
protected override void OnExecute()
{
this.GetModel<CounterAppModel>().Count--;
this.SendEvent<CountChangeEvent>();
}
}
// Controller,实现IController接口
public class CounterAppController : MonoBehaviour, IController
{
// View
private Button mBtnAdd;
private Button mBtnSub;
private Text mCountText;
// Model
private CounterAppModel mModel;
void Start()
{
// 获取模型
mModel = this.GetModel<CounterAppModel>();
// View 组件获取
mBtnAdd = transform.Find("BtnAdd").GetComponent<Button>();
mBtnSub = transform.Find("BtnSub").GetComponent<Button>();
mCountText = transform.Find("CountText").GetComponent<Text>();
// 监听输入
mBtnAdd.onClick.AddListener(() =>
{
// 交互逻辑
this.SendCommand<IncreaseCountCommand>();
});
mBtnSub.onClick.AddListener(() =>
{
// 交互逻辑
this.SendCommand<DecreaseCountCommand>();
});
UpdateView();
// 表现逻辑
this.RegisterEvent<CountChangeEvent>(e =>
{
UpdateView();
}).UnRegisterWhenGameObjectDestroyed(gameObject);
}
void UpdateView()
{
mCountText.text = mModel.Count.ToString();
}
// 指定架构
public IArchitecture GetArchitecture()
{
return CounterApp.Interface;
}
private void OnDestroy()
{
mModel = null;
}
}
}
做法概述:
编写一个类继承AbstractSystem,通过事件触发规则逻辑
8、小结
基于QFramework的架构示例:
QFramework总共分为四个层级,分别是:
- 表现层:IController
- 系统层:ISystem
- 数据层:IModel
- 工具层:IUtility
此外,还提供为Controller的交互逻辑的Command和为表现逻辑的Event。
Command -> Model -> State Changed Event。
9、BindableProperty优化事件
BindableProperty包含 数据 + 数据变更事件 的一个对象。
(1)BindableProperty基本使用
var age = new BindableProperty<int>(10);
age.Register(newAge => {
Debug.Log(newAge);
}).UnregisterWhenGameObjectDestoryed(gameObject);
age++;
age--;
// 输出结果
// 11
// 10
就是当调用age++和age--的时候,就会触发数据变更事件。
BindableProperty除了提供Register这个API之外,还提供了RegisterWithInitValue API,意思是注册时先把当前值返回过来。这个API的作用是,没有任何变化的情况下,age先返回一个当前的值,比较方便用于显示初始界面。
(2)使用BindableProperty优化代码
using QFramework;
using QFramework.Example;
using UnityEngine;
using UnityEngine.UI;
namespace QFrameWork2.Example
{
// 定义一个Model对象
public class CounterAppModel : AbstractModel
{
public BindableProperty<int> Count { get; } = new BindableProperty<int>();
protected override void OnInit()
{
var storage = this.GetUtility<Storage>();
// 设置初始值(不触发事件)
Count.SetValueWithoutEvent(storage.LoadInt(nameof(Count)));
// 当数据变更时 存储数据
Count.Register(newCount => {
storage.SaveInt(nameof(Count), newCount);
});
}
}
public class AchievementSystem : AbstractSystem
{
protected override void OnInit()
{
this.GetModel<CounterAppModel>()
.Count
.Register(newCount => {
if (newCount == 10)
{
Debug.Log("触发 点击达人 成就");
}
else if (newCount == 20)
{
Debug.Log("触发 点击专家 成就");
}
else if (newCount == -10)
{
Debug.Log("触发 点击菜鸟 成就");
}
});
}
}
// 定义Utility层
public class Storage : IUtility
{
public void SaveInt(string key, int value)
{
PlayerPrefs.SetInt(key, value);
}
public int LoadInt(string key, int defaultValue = 0)
{
return PlayerPrefs.GetInt(key, defaultValue);
}
}
// 定义一个架构(提供MVC、分层、模块管理等)
public class CounterApp : Architecture<CounterApp>
{
protected override void Init()
{
// 注册Model
this.RegisterModel(new CounterAppModel());
// 注册存储工具的对象
this.RegisterUtility(new Storage());
// 注册System
this.RegisterSystem(new AchievementSystem());
}
}
// 引入Command
public class IncreaseCountCommand : AbstractCommand // ++
{
protected override void OnExecute()
{
this.GetModel<CounterAppModel>().Count.Value++;
}
}
public class DecreaseCountCommand : AbstractCommand // --
{
protected override void OnExecute()
{
this.GetModel<CounterAppModel>().Count.Value--;
}
}
// Controller,实现IController接口
public class CounterAppController : MonoBehaviour, IController
{
// View
private Button mBtnAdd;
private Button mBtnSub;
private Text mCountText;
// Model
private CounterAppModel mModel;
void Start()
{
// 获取模型
mModel = this.GetModel<CounterAppModel>();
// View 组件获取
mBtnAdd = transform.Find("BtnAdd").GetComponent<Button>();
mBtnSub = transform.Find("BtnSub").GetComponent<Button>();
mCountText = transform.Find("CountText").GetComponent<Text>();
// 监听输入
mBtnAdd.onClick.AddListener(() =>
{
// 交互逻辑
this.SendCommand<IncreaseCountCommand>();
});
mBtnSub.onClick.AddListener(() =>
{
// 交互逻辑
this.SendCommand<DecreaseCountCommand>();
});
// 表现逻辑
mModel.Count.RegisterWithInitValue(newCount =>
{
UpdateView();
}).UnRegisterWhenGameObjectDestroyed(gameObject);
}
void UpdateView()
{
mCountText.text = mModel.Count.ToString();
}
// 指定架构
public IArchitecture GetArchitecture()
{
return CounterApp.Interface;
}
private void OnDestroy()
{
mModel = null;
}
}
}
改动点:
- 将变量改为BindableProperty类型
- 删掉了CountChangeEvent改用监听BindableProperty
- Controller在初始化中去掉一次UpdateView的主动调用
10、用接口设计模块(依赖倒置原则)
依赖倒置原则:所有的模块访问和交互都可以通过接口完成。
示例:
using QFramework;
using QFramework.Example;
using UnityEngine;
using UnityEngine.UI;
namespace QFrameWork2.Example
{
// 定义一个Model对象
public interface ICounterAppModel : IModel
{
BindableProperty<int> Count { get; }
}
public class CounterAppModel : AbstractModel, ICounterAppModel
{
public BindableProperty<int> Count { get; } = new BindableProperty<int>();
protected override void OnInit()
{
var storage = this.GetUtility<IStorage>();
// 设置初始值(不触发事件)
Count.SetValueWithoutEvent(storage.LoadInt(nameof(Count)));
// 当数据变更时 存储数据
Count.Register(newCount => {
storage.SaveInt(nameof(Count), newCount);
});
}
}
public interface IAchievementSystem : ISystem { }
public class AchievementSystem : AbstractSystem, IAchievementSystem
{
protected override void OnInit()
{
this.GetModel<ICounterAppModel>()
.Count
.Register(newCount => {
if (newCount == 10)
{
Debug.Log("触发 点击达人 成就");
}
else if (newCount == 20)
{
Debug.Log("触发 点击专家 成就");
}
else if (newCount == -10)
{
Debug.Log("触发 点击菜鸟 成就");
}
});
}
}
public interface IStorage : IUtility
{
void SaveInt(string key, int value);
int LoadInt(string key, int defaultValue = 0);
}
// 定义Utility层
public class Storage : IStorage
{
public void SaveInt(string key, int value)
{
PlayerPrefs.SetInt(key, value);
}
public int LoadInt(string key, int defaultValue = 0)
{
return PlayerPrefs.GetInt(key, defaultValue);
}
}
// 定义一个架构(提供MVC、分层、模块管理等)
public class CounterApp : Architecture<CounterApp>
{
protected override void Init()
{
// 注册Model
this.RegisterModel<ICounterAppModel>(new CounterAppModel());
// 注册存储工具的对象
this.RegisterUtility<IStorage>(new Storage());
// 注册System
this.RegisterSystem<IAchievementSystem>(new AchievementSystem());
}
}
// 引入Command
public class IncreaseCountCommand : AbstractCommand // ++
{
protected override void OnExecute()
{
this.GetModel<ICounterAppModel>().Count.Value++;
}
}
public class DecreaseCountCommand : AbstractCommand // --
{
protected override void OnExecute()
{
this.GetModel<ICounterAppModel>().Count.Value--;
}
}
// Controller,实现IController接口
public class CounterAppController : MonoBehaviour, IController
{
// View
private Button mBtnAdd;
private Button mBtnSub;
private Text mCountText;
// Model
private ICounterAppModel mModel;
void Start()
{
// 获取模型
mModel = this.GetModel<ICounterAppModel>();
// View 组件获取
mBtnAdd = transform.Find("BtnAdd").GetComponent<Button>();
mBtnSub = transform.Find("BtnSub").GetComponent<Button>();
mCountText = transform.Find("CountText").GetComponent<Text>();
// 监听输入
mBtnAdd.onClick.AddListener(() =>
{
// 交互逻辑
this.SendCommand<IncreaseCountCommand>();
});
mBtnSub.onClick.AddListener(() =>
{
// 交互逻辑
this.SendCommand<DecreaseCountCommand>();
});
// 表现逻辑
mModel.Count.RegisterWithInitValue(newCount =>
{
UpdateView();
}).UnRegisterWhenGameObjectDestroyed(gameObject);
}
void UpdateView()
{
mCountText.text = mModel.Count.ToString();
}
// 指定架构
public IArchitecture GetArchitecture()
{
return CounterApp.Interface;
}
private void OnDestroy()
{
mModel = null;
}
}
}
注意:
在Architecture注册时,需要告诉接口具体的实现类。
// 注册Model
this.RegisterModel<ICounterAppModel>(new CounterAppModel());
// 注册存储工具的对象
this.RegisterUtility<IStorage>(new Storage());
// 注册System
this.RegisterSystem<IAchievementSystem>(new AchievementSystem());
通过接口设计模块可以让我们更容易思考模块之间的交互和职责本身,而不是具体实现,在设计的时候可以减少很多的干扰。
后续:我们要面向接口做开发。
好处展示:
对于Storage,如果想把存储从PlayerPrefs切换成EasySave,那么就不需要修改Storage对象,而是扩展一个IStorage接口即可。
伪代码如下:
public class EasySaveStorage : IStorage
{
public void SaveInt(string key, int value){// todo}
public int LoadInt(string key, int defaultValue = 0){// todo}
}
注册模块的代码:
this.RegisterUtility<IStorage>(new EasySaveStorage());
这样,不需要变更原有PlayerPrefs的Storage的逻辑,底层所有存储的代码都切换成了EasySave的存储,替换一套方案非常简单。
11、Query
(1)作用 / 使用理由 / 和Command区别
作用?
主要在表现逻辑中使用,比如上面的UpdateView()函数中。
为什么要用?
像UpdateView中只查询mModel.Count一个数据不需要使用,假如查询的时候需要多个Model一起查询,查询的数据可能还需要转换一下,此时就需要将查询模块单独剥离出来。
和Command的区别?
Command一般负责数据的增、删、改,而Query负责数据的查。
使用方法?
创建一个类继承自AbstractQuery,使用时sendQuery即可。
(2)示例
通过OnGUI画界面,所以界面只需要放一个调用脚本的空对象即可。
using System.Collections;
using System.Collections.Generic;
using QFramework;
using UnityEngine;
public class StudentModel : AbstractModel
{
public List<string> StudentNames = new List<string>()
{
"张三","李四"
};
protected override void OnInit()
{
}
}
public class TeacherModel : AbstractModel
{
public List<string> TeacherNames = new List<string>()
{
"王五","赵六"
};
protected override void OnInit()
{
}
}
public class QueryExampleApp : Architecture<QueryExampleApp>
{
protected override void Init()
{
this.RegisterModel(new StudentModel());
this.RegisterModel(new TeacherModel());
}
}
public class SchoolAllPersonCountQuery : AbstractQuery<int>
{
protected override int OnDo()
{
return this.GetModel<StudentModel>().StudentNames.Count + this.GetModel<TeacherModel>().TeacherNames.Count;
}
}
public class QueryController : MonoBehaviour, IController
{
private int mAllPersonCount = 0;
private void OnGUI()
{
GUILayout.Label(mAllPersonCount.ToString());
if (GUILayout.Button("查询学校总人数"))
{
mAllPersonCount = this.SendQuery(new SchoolAllPersonCountQuery());
}
}
public IArchitecture GetArchitecture()
{
return QueryExampleApp.Interface;
}
}
效果:
12、架构规范与推荐用法
QFramework架构的四个层级:
- 表现层:IController
- 系统层:ISystem
- 数据层:IModel
- 工具层:IUtility
除了四个层级,还提供了Command、Query、Event、BindableProperty等概念和工具。
层级规则:
1)表现层
ViewController层,IController接口,负责接收输入和状态变化时的表现。
一般情况下,MonoBehaviour均为表现层。
- 可以获取System、Model
- 可以发送Command、Query
- 可以监听Event
2)系统层
System层,ISystem接口,帮助IController承担一部分逻辑,在多个表现层共享的逻辑,比如计时系统、商城系统、成就系统等
- 可以获取System、Model
- 可以监听Event
- 可以发送Event
3)数据层
Model层,IModel接口,负责数据的定义、数据的增删改查方法的提供
- 可以获取Utility
- 可以发送Event
4)工具层
Utility层,IUtility接口,负责提供基础设施,比如存储方法、序列化方法、网络连接方法、蓝牙方法、SDK、框架继承等。
5)Command
命令,负责数据的增删改
- 可以获取System、Model
- 可以发送Event、Command
6)Query
查询,负责数据的查询
- 可以获取System、Model
- 可以发送Query
----------------------------------------------------------------------------------------------
上面6点中的可以,指的是可以通过this.xxx(可以的API)直接调用,未提供的则不能通过API直接调用。
----------------------------------------------------------------------------------------------
7)通用规则
- IController更改ISystem、IModel的状态必须用Command
- ISystem、IModel状态发生变更后通知IController必须用事件或BindableProperty
- IController可以获取ISystem、IModel对象来进行数据查询
- ICommand、IQuery不能有状态
- 上层可以直接获取下层,下层不能获取上层对象
- 下层向上层通信用事件
- 上层向下层通信用方法调用(只是做查询,状态变更用Command),IController的交互逻辑为特别情况,只能用Command
13、光速实现EditorCounterApp
在已经实现好的CounterApp的基础上,快速实现一个编辑器版本的CounterApp。
代码:
using System.Collections;
using System.Collections.Generic;
using QFramework;
using QFramework.Example;
using UnityEditor;
using UnityEngine;
public class EditorCounterWindow : EditorWindow, IController
{
[MenuItem("QFramework/Example/EditorCounterAppWindow")]
static void Open()
{
GetWindow<EditorCounterWindow>().Show();
}
private ICounterAppModel mCounterAppModel;
private void OnEnable()
{
mCounterAppModel = this.GetModel<ICounterAppModel>();
}
private void OnDisable()
{
mCounterAppModel = null;
}
private void OnGUI()
{
if (GUILayout.Button("+"))
{
this.SendCommand<IncreaseCountCommand>();
}
GUILayout.Label(mCounterAppModel.Count.Value.ToString());
if(GUILayout.Button("-"))
{
this.SendCommand<DecreaseCountCommand>();
}
}
public IArchitecture GetArchitecture()
{
return CounterApp.Interface;
}
}
效果:
14、Architecture
不管是System、Model还是Utility,都会注册到Architecture中。
典型示例:
public class PointGame : Architecture<PointGame>
{
protected override void Init()
{
RegisterSystem<IScoreSystem>(new ScoreSystem());
RegisterSystem<ICountDownSystem>(new CountDownSystem());
RegisterSystem<IAchievementSystem>(new AchievementSystem());
RegisterModel<IGameModel>(new GameModel());
RegisterUtility<IStorage>(new PlayerPrefsStorage());
}
}
15、内置工具TypeEventSystem
简化版的事件机制。
(1)基本使用
代码:
using QFramework;
using UnityEngine;
public class TypeEventSystemBasicExample : MonoBehaviour
{
public struct TestEventA
{
public int Age;
}
private void Start()
{
TypeEventSystem.Global.Register<TestEventA>(e =>
{
Debug.Log(e.Age);
}).UnRegisterWhenGameObjectDestroyed(gameObject);
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
TypeEventSystem.Global.Send(new TestEventA
{
Age = 18
});
}
if(Input.GetMouseButtonDown(0))
{
TypeEventSystem.Global.Send<TestEventA>();
}
}
}
事件的定义最好使用struct,因为struct的gc更少,可以获得更好的性能。
(2)事件继承支持
using QFramework;
using UnityEngine;
public class TypeEventSystemBasicExample : MonoBehaviour
{
public interface IEventA { }
public struct EventB : IEventA { }
private void Start()
{
TypeEventSystem.Global.Register<IEventA>(e => {
Debug.Log(e.GetType().Name);
}).UnRegisterWhenGameObjectDestroyed(gameObject);
}
private void Update()
{
if(Input.GetMouseButtonDown(0))
{
TypeEventSystem.Global.Send<IEventA>(new EventB());
}
}
}
16、内置工具IOCContainer
QFramework架构的模块注册与获取是通过IOCContainer实现的。
IOC的意思是控制反转,即控制反转容器。
其技术的本质很简单,本质就是一个字典,Key是Type,Value是Object,即:Dictionary<Type, Object>
QFramework架构中IOCContainer是一个非常简易版本的控制反转容器,仅支持了注册对象为单例的模式。
过程分为:Register注册和Get使用。
using QFramework;
using UnityEngine;
public class IOCContainerExample : MonoBehaviour
{
public class SomeService
{
public void Say()
{
Debug.Log("SomeService Say Hi");
}
}
public interface INetworkService
{
void Connect();
}
public class NetworkService : INetworkService
{
public void Connect()
{
Debug.Log("NetworkService Connect Succeed");
}
}
private void Start()
{
var container = new IOCContainer();
container.Register(new SomeService());
container.Register<INetworkService>(new NetworkService());
container.Get<SomeService>().Say();
container.Get<INetworkService>().Connect();
}
}