QFrameWork学习指南

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();
		
    }

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/908462.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

黄金价格下跌,原油价格激增,小麦价格面临阻力

黄金价格回落 现货黄金价格达到每金衡盎司 $2,790.00 的新纪录高点&#xff0c;接近心理关口 $2,800.00&#xff0c;随后在部分交易员在周五美国非农就业数据&#xff08;NFPs&#xff09;公布前以及下周美国选举前套现利润的回撤中下跌至 $2,732.00。 在 $2,732.00 以下是 10…

DiskGenius一键修复磁盘损坏

下午外接磁盘和U盘都出现扇区损坏&#xff0c;估计就是在开着电脑&#xff0c;可能是电脑运行的软件还在对磁盘进行读写&#xff0c;不小心按到笔记本关机键&#xff0c;重新开机读写磁盘分区变得异常卡顿&#xff0c;估摸就是这个原因导致扇区损坏。在进行读写时&#xff0c;整…

智慧国土空间规划方法探索与实践应用

在数字化时代背景下&#xff0c;国土空间规划正经历着一场深刻的变革。智慧国土空间规划作为一种新兴的规划理念和方法&#xff0c;其核心在于利用现代信息技术&#xff0c;提高规划的科学性、精准性和动态适应性。本文将探讨智慧国土空间规划的方法探索与实践应用。 1. 智慧国…

从零开发操作系统-为什么磁盘的扇区为 512 byte

你好&#xff0c;我是 shengjk1&#xff0c;多年大厂经验&#xff0c;努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注&#xff01;你会有如下收益&#xff1a; 了解大厂经验拥有和大厂相匹配的技术等 希望看什么&#xff0c;评论或者私信告诉我&#xff01; 文章目录 一…

一文了解Android SELinux

在Android系统中&#xff0c;SELinux&#xff08;Security-Enhanced Linux&#xff09;是一个增强的安全机制&#xff0c;用于对系统进行强制访问控制&#xff08;Mandatory Access Control&#xff0c;MAC&#xff09;。它限制了应用程序和进程的访问权限&#xff0c;提供了更…

Redis-持久化(增量模式和全量模式)

文章目录 一、持久化和Redis持久化概念持久化介绍Redis持久化的介绍全量模式持久化技术——RDB增量模式持久化技术——AOF使用RDB还是AOF? 二、RDB配置以及数据恢复的简单实现RDB快照的工作方式如何关闭RDB持久化功能如何模拟Redis服务器数据丢失&#xff1f;然后再如何具体使…

SQL 常用语句

目录 我的测试环境 学习文档 进入数据库 基础通关测验 语句-- 查 展示数据库&#xff1b; 进入某个数据库&#xff1b; 展示表&#xff1a; 展示某个表 desc 查询整个表&#xff1a; 查询特定列&#xff1a; 范围查询 等于特定值 不等于 介于 特定字符查询 Li…

MFC图形函数学习05——画椭圆函数

MFC中有一个专门绘制椭圆的函数&#xff0c;其参数与绘制矩形参数相同&#xff0c;实际上所绘制的椭圆就是矩形的内切圆椭圆。 一、绘制椭圆函数 原型&#xff1a;BOOL Ellipse (int x1,int y1,int x2,int y2); 参数&#xff1a;椭圆内切矩形的左上角&#xff08…

Spring3(代理模式 Spring1案例补充 Aop 面试题)

Spring3 代理模式概述介绍什么是代理模式&#xff1f;为什么要使用代理模式&#xff1f;有哪几种代理模式&#xff1f;静态代理基于接口实现的动态代理(JDK自带)基于子类的动态代理 Spring_AOP_01案例补充(添加事务管理)实现完整代码&#xff1a;常规实现&#xff1a;代理实现 …

开源模型应用落地-Qwen2.5-7B-Instruct与TGI实现推理加速

一、前言 目前&#xff0c;大语言模型已升级至Qwen2.5版本。无论是语言模型还是多模态模型&#xff0c;均在大规模多语言和多模态数据上进行预训练&#xff0c;并通过高质量数据进行后期微调以贴近人类偏好。在本篇学习中&#xff0c;将集成 Hugging Face的TGI框架实现模型推理…

Android 使用ninja加速编译的方法

ninja的简介 随着Android版本的更迭&#xff0c;makefile体系逐渐增多&#xff0c;导致make单编模块的时间越来越长&#xff0c;每次都需要半个小时甚至更长时间&#xff0c;其原因为每次make都会重新加载所有mk文件&#xff0c;再生成ninja编译&#xff0c;此完整过程十分耗时…

javaNIO核心知识.中

Channel&#xff08;通道&#xff09; Channel 是一个通道&#xff0c;它建立了与数据源&#xff08;如文件、网络套接字等&#xff09;之间的连接。我们可以利用它来读取和写入数据&#xff0c;就像打开了一条自来水管&#xff0c;让数据在 Channel 中自由流动。 BIO 中的流…

缓存、注解、分页

一.缓存 作用&#xff1a;应用查询上&#xff0c;内存中的块区域。 缓存查询结果&#xff0c;减少与数据库的交互&#xff0c;从而提高运行效率。 1.SqlSession 缓存 1. 又称为一级缓存&#xff0c;mybatis自动开启。 2. 作用范围&#xff1a;同一…

流畅!HTMLCSS打造网格方块加载动画

效果演示 这个动画的效果是五个方块在网格中上下移动&#xff0c;模拟了一个连续的加载过程。每个方块的动画都是独立的&#xff0c;但是它们的时间间隔和路径被设计为相互协调&#xff0c;以创建出流畅的动画效果。 HTML <div class"loadingspinner"><…

【skywalking 】More than 15,000 ‘grammar‘ tokens have been presented. 【未解决请求答案】

问题 skywalking相关版本信息 jdk&#xff1a;17skywalking&#xff1a;10.1.0apache-skywalking-java-agent&#xff1a;9.3.0ElasticSearch : 8.8.2 问题描述 More than 15,000 grammar tokens have been presented. To prevent Denial Of Service attacks, parsing has b…

docker desktop使用ubuntu18.04带图形化+运行qemu

记录一下docker desktop使用ubuntu18.04带图形化命令和使用步骤 1. 下载镜像 参考&#xff1a;【Docker教程】Docker部署Ubuntu18.04(带图形化界面) 命令&#xff1a; docker pull kasmweb/ubuntu-bionic-desktop:1.10.02. 启动镜像 命令&#xff1a; docker run -d -it …

jmeter压测工具环境搭建(Linux、Mac)

目录 java环境安装 1、anaconda安装java环境&#xff08;推荐&#xff09; 2、直接在本地环境安装java环境 yum方式安装jdk 二进制方式安装jdk jmeter环境安装 1、jmeter单机安装 启动jmeter 配置环境变量 jmeter配置中文 2、jmeter集群搭建 多台机器部署jmeter集群…

ai翻唱部分步骤

模型部署 我是用的RVC进行的训练&#xff0c;也可以使用so-vits-svc。 通过百度网盘分享的文件&#xff1a;RVC-beta 链接&#xff1a;https://pan.baidu.com/s/1c99jR2fLChoqUFqf9gLUzg 提取码&#xff1a;4090 以Nvida显卡为例&#xff0c;分别下载“RVC1006Nvidia”和…

算法笔记-Day09(字符篇)

151. 反转字符串中的单词 class Solution {public String reverseWords(String s) {int lens.length(),count0;StringBuffer tempnew StringBuffer();StringBuffer ansnew StringBuffer();for(int i0;i<len;i){if(s.charAt(i)! &&(i0 || s.charAt(i-1) )){while(i&l…

安科瑞电能质量治理产品在光伏电站的应用有效解决了光伏电站面临的功率因数过低和谐波问题-安科瑞黄安南

1. 概述 随着全球对可再生能源需求的增加&#xff0c;分布式光伏电站的建设和发展迅速。然而&#xff0c;分布式光伏电站的运行过程中面临着一系列问题&#xff0c;比如导致企业关口计量点功率因数过低、谐波污染等。这些问题不仅影响光伏电站自身的运行效率&#xff0c;还会对…