liangxiegame / QFramework

Godot/Unity3D System Design Architecture
https://qframework.cn
MIT License
4.41k stars 774 forks source link

优化TypeEventSystem类,移除 EasyEvents #129

Closed Yuumi-Zeus closed 6 months ago

Yuumi-Zeus commented 8 months ago

原本的 QFramework 在架构设计上没有维护全局唯一事件字典(因为运行过程中至少存在三个事件字典),删除冗余的 EasyEvents 类,调整TypeEventSystem为静态类,移除 Global 字段,直接使用类名获取全局唯一静态字典,项目过程中维护TypeEventSystem类的全局唯一静态字典,此修改会导致Example中的 Global 无效,为了维持原本的 QF 使用习惯,可选不移除 Global 变量。

EasyEvents 和 TypeEventSystem 疑点或问题点

为什么 TypeEventSystem 内部要 new 一个 EasyEvents 对象 ?

public class TypeEventSystem
    {
        // 这是 TypeEventSystem 静态实例 Global
        public static readonly TypeEventSystem Global = new();
        // 这里却 new 了一个 EasyEvents
        readonly EasyEvents _easyEvents = new();
    }

[!CAUTION]

  • Architecture 抽象类中,同时 new 了一个 TypeEventSystem 类对象,对象名为: _typeEventSystem
// 事件系统
readonly TypeEventSystem _typeEventSystem = new();

_typeEventSystem实例对象生成的过程中也会生成一个只读 EasyEvents 实例对象,可以表示为 _typeEventSystem._easyEvents

根据以上可以得知,到目前为止,当程序运行,且使用了架构时,内存中实际会存在两个 EasyEvents类型的实例对象,这两个实例对象是相互独立的。


现在再来看 EasyEvents

它也创造了一个全局静态实例对象,GlobalEasyEvents,可以表示为 EasyEvents.GlobalEasyEvents

// 全局唯一的 EasyEvents 实例
static readonly EasyEvents GlobalEasyEvents = new();

现在可以得知:当程序运行,且使用了架构时,内存中至少有三个 EasyEvents实例对象,其中一个静态对象,两个非静态对象


然后又可以看到 EasyEvents 类中存在一个非静态的事件字典,那么代表了内存中一共包含三个

_typeEventDictionary字典,因为每次对象生成时都会生成一份非静态的字典

// 存储不同类型事件的字典
readonly Dictionary<Type, IEasyEvent> _typeEventDictionary = new();

现在重要的问题点在于 TypeEventSystem 的公共方法和EasyEvents的公共方法

TypeEventSystem

public void Send<T>() where T : new()
        {
            _easyEvents.GetEvent<EasyEvent<T>>()?.Trigger(new T());
        }

        public void Send<T>(T e)
        {
            _easyEvents.GetEvent<EasyEvent<T>>()?.Trigger(e);
        }

        /// <summary>
        /// 默认注册带一个参数的委托 
        /// </summary>
        /// <remarks>注册时执行 GetOrAddEvent ,如果存在就注册给具体 EasyEvent 实例对象,不存在则创建一个新的并添加到 EasyEvents 的字典</remarks>
        /// <returns></returns>
        public IUnRegister Register<T>(Action<T> onEvent) =>
            _easyEvents.GetOrAddEvent<EasyEvent<T>>().Register(onEvent);

        public void UnRegister<T>(Action<T> onEvent)
        {
            EasyEvent<T> e = _easyEvents.GetEvent<EasyEvent<T>>();
            e?.UnRegister(onEvent);
        }

EasyEvents

// 获取指定类型的全局事件实例
        public static T Get<T>() where T : IEasyEvent => GlobalEasyEvents.GetEvent<T>();

        // 注册指定类型的事件
        public static void Register<T>() where T : IEasyEvent, new()
        {
            GlobalEasyEvents.AddEvent<T>();
        }

        // 添加指定类型的事件实例到字典中
        public void AddEvent<T>() where T : IEasyEvent, new()
        {
            _typeEventDictionary.Add(typeof(T), new T());
        }

        // 获取指定类型的事件实例
        public T GetEvent<T>() where T : IEasyEvent => _typeEventDictionary.TryGetValue(typeof(T), out var e) ? (T)e : default;

        // 获取或添加指定类型的事件实例
        public T GetOrAddEvent<T>() where T : IEasyEvent, new()
        {
            var eType = typeof(T);
            if (_typeEventDictionary.TryGetValue(eType, out var e)) return (T)e;

            var t = new T();
            _typeEventDictionary.Add(eType, t);
            return t;
        }

首先我们走一遍 注册方法 的路径

可以很明显看到使用的是TypeEventSystem.Global内部的非静态字段,也就是说,通常 QF 维护的字典是TypeEventSystem.Global._easyEvents._typeEventDictionary,和 EasyEvents.GlobalEasyEvents这个静态对象没有任何关系,但是它依旧存在于内存中,同时也和架构中创建的_typeEventSystem也没有关系


尽管 EasyEvents 类中提供了RegisterGet 两个静态方法,但是很明显,在 Rider 中可以清楚地看到 QF 内部并没有任何时使用到它们的地方

[!NOTE]

QF 内部没有任何使用的地方,那么这两个方法是给谁用的呢?是希望用户直接调用这两个方法吗?如果是这个意思,为什么不再提供一个 Trigger 的方法,反而需要用户使用 Get 后,再调用 EasyEvent 的触发方法,大概使用方法示例:EasyEvents.Get<T>().Trigger();

完全多此一举,而且这样也破坏了全局事件字典的唯一性(不过因为基本上没有人会使用这个静态方法,而手动保持了唯一性),EasyEvent 对象可以注册到不同的字典中。

还有一点,Register 这个静态方法内部调用的是 AddEvent 方法,并不是GetOrAddEventAddEvent方法是没有返回值的,也就意味着注册的时候,无法使用链式方法去设置自动注销。


[!TIP]

  • 可以猜测提供这两个静态方法的意思,是为了让没有使用架构的用户,也能够直接调用集成了EasyEvent的事件方法,此时维护的是EasyEvents.GlobalEasyEvents这个全局静态对象内部的_typeEventDictionary字典
  • 也许是当时为了模块化而做的设计,让事件系统能够独立,但是事实证明,这个系统独立为模块,为不使用架构的用户提供EasyEvent的集成,这个设计没有必要
  • 如果我是一个用户,只想要有一个独立的事件系统,那么我完全可以去找AssetStore 商店中更加完善的可视化事件系统,(如果说是为了自动注销机制,完全可以自己补充一下),没必要使用这个贯穿 QF 运转的,推荐使用的事件系统
  • 相反,如果我是使用 QFramework 的用户,那么我也没有必要去使用独立的事件系统,按照框架规则去注册发布事件即可

总结修改方案

目标: 尽量维持原版 API 的使用方法,删除多余部分,使得结构更加简洁

解决过程


[!TIP]

在此基础上就可以添加全局事件可视化,显示当前程序运转过程中的事件,并且为每个事件新增一个按钮,可以在任何时候点击触发,便于开发过程中随时触发事件调试,同时也可以直观的查看全局事件字典中订阅的方法,在原本的基础上,用户如果自己不小心额外订阅了一个事件,是不易被发现的,一旦新增这个功能,在大型项目中,可以立即定位到多余订阅的方法。



[!IMPORTANT]

专栏原文:

今天的结果⾮常值得庆祝,少了⼀步导出步骤。⽽少的这⼀步假如为我们节省了 5 秒钟时间。那么⽇ 复⼀⽇,我们如果积累了 1000 个知识点,那就是 5000 秒。相当于⼀⼩时⼆⼗多分钟,假设⼤家的时 薪为⼀⼩时 100,那么这⼀个⼩⼩的⼯具,为我们赚了 130 多块钱。

虽然说得有点夸张,但是从⼩⽼师就告诉了我们⼀个简单的道理:“时间就是⾦钱”。


解决方案

TypeEventSystem

#region 新版

        static readonly Dictionary<Type, IEasyEvent> TypeEventDictionary = new();

        static T GetOrAddEvent<T>() where T : IEasyEvent, new()
        {
            var eType = typeof(T);
            if (TypeEventDictionary.TryGetValue(eType, out var e)) return (T)e;

            var t = new T();
            TypeEventDictionary.Add(eType, t);
            return t;
        }

        static T GetEvent<T>() where T : IEasyEvent =>
            TypeEventDictionary.TryGetValue(typeof(T), out var e) ? (T)e : default;

        public static void Send<T>() where T : new()
        {
            GetEvent<EasyEvent<T>>()?.Trigger(new T());
        }

        public static void Send<T>(T e)
        {
            GetEvent<EasyEvent<T>>()?.Trigger(e);
        }

        public static IUnRegister Register<T>(Action<T> onEvent) =>
            GetOrAddEvent<EasyEvent<T>>().Register(onEvent);

        public static void UnRegister<T>(Action<T> onEvent)
        {
            EasyEvent<T> e = GetEvent<EasyEvent<T>>();
            e?.UnRegister(onEvent);
        }

        #endregion

Architecture

#region 新版

        public void SendEvent<TEvent>() where TEvent : new()
        {
            TypeEventSystem.Send<TEvent>();
        }

        public void SendEvent<TEvent>(TEvent e)
        {
            TypeEventSystem.Send(e);
        }

        public IUnRegister RegisterEvent<TEvent>(Action<TEvent> onEvent) => TypeEventSystem.Register(onEvent);

        public void UnRegisterEvent<TEvent>(Action<TEvent> onEvent)
        {
            TypeEventSystem.UnRegister(onEvent);
        }

        #endregion

删除 Architecture 中的 readonly TypeEventSystem _typeEventSystem = new();

删除 EasyEvents