--- prev: text: '基础计数器实现' link: './03-counter-basic' next: text: '命令系统优化' link: './05-command-system' --- # 第 4 章:引入 Model 重构 在上一章中,我们实现了基础功能但也发现了一些设计问题。本章将通过引入 **Model 层** 和 **事件系统**,重构我们的计数器应用。 ## 理解 Model 的作用 在 GFramework 中,**Model(模型)** 负责: ✅ **存储应用状态**:保存数据,如计数器的值 ✅ **提供数据访问接口**:通过方法暴露数据操作 ✅ **发送状态变化事件**:当数据改变时通知监听者 Model **不应该**: ❌ 包含 UI 逻辑(如更新 Label) ❌ 直接调用 Controller 方法 ❌ 知道谁在使用它 ## 创建 Model ### 1. 定义 Model 接口 在 `scripts/model/` 创建 `ICounterModel.cs`: ```csharp using GFramework.Core.Abstractions.Model; namespace MyGFrameworkGame.scripts.Model; /// /// 计数器模型接口,定义计数器的基本操作 /// public interface ICounterModel : IModel { /// /// 获取当前计数器的值 /// int Count { get; } /// /// 将计数器的值增加 1 /// void Increment(); /// /// 将计数器的值减少 1 /// void Decrement(); } ``` ::: tip 为什么要定义接口? 使用接口的好处: - **依赖倒置**:依赖抽象而不是具体实现 - **易于测试**:可以创建 Mock 实现 - **灵活替换**:可以切换不同的实现 这是 SOLID 原则中的 **依赖倒置原则(DIP)**。 ::: ### 2. 实现 Model 类 在 `scripts/model/` 创建 `CounterModel.cs`: ```csharp using GFramework.Core.Extensions; using GFramework.Core.Model; namespace MyGFrameworkGame.scripts.Model; /// /// 计数器模型实现 /// public class CounterModel : AbstractModel, ICounterModel { /// /// 获取当前计数器的值 /// public int Count { get; private set; } /// /// 初始化 Model(可选) /// protected override void OnInit() { // 可以在这里初始化默认值或加载数据 } /// /// 计数变化事件 /// public sealed record ChangedCountEvent { public int Count { get; init; } } /// /// 增加计数器的值 /// public void Increment() { Count++; // 发送事件通知监听者 this.SendEvent(new ChangedCountEvent { Count = Count }); } /// /// 减少计数器的值 /// public void Decrement() { Count--; // 发送事件通知监听者 this.SendEvent(new ChangedCountEvent { Count = Count }); } } ``` ::: tip 事件驱动设计 注意 `Increment` 和 `Decrement` 方法的最后一行: ```csharp this.SendEvent(new ChangedCountEvent { Count = Count }); ``` 这是 **事件驱动架构** 的核心: - Model 只负责改变状态并发送事件 - **不关心谁在监听** - **不直接调用 UI 更新** 这实现了完全解耦! ::: ### 3. 注册 Model 编辑 `scripts/module/ModelModule.cs`: ```csharp using GFramework.Core.Abstractions.Architecture; using GFramework.Game.Architecture; using MyGFrameworkGame.scripts.Model; namespace MyGFrameworkGame.scripts.module; /// /// 模型模块,负责注册所有的数据模型 /// public class ModelModule : AbstractModule { /// /// 安装模型到架构中 /// public override void Install(IArchitecture architecture) { // 注册 CounterModel 实例 architecture.RegisterModel(new CounterModel()); } } ``` ::: warning 注意泛型参数 ```csharp architecture.RegisterModel(new CounterModel()); ``` 第一个泛型参数 `ICounterModel` 是接口类型,用于后续获取时指定类型。 ::: ## 重构 Controller ### 1. 启用上下文感知 编辑 `App.cs`,添加 `IController` 接口和特性: ```csharp using GFramework.Core.Abstractions.Controller; using GFramework.Core.Extensions; using GFramework.SourceGenerators.Abstractions.Rule; using Godot; using MyGFrameworkGame.scripts.Model; namespace MyGFrameworkGame.scripts.app; /// /// 计数器应用的主控制器 /// [ContextAware] // ← 启用上下文感知(由源码生成器处理) public partial class App : Control, IController // ← 实现 IController 接口 { // ... 其他代码 } ``` ::: tip ContextAware 特性 `[ContextAware]` 特性由 **源码生成器** 处理,它会自动生成: - `IArchitecture Architecture { get; }` 属性 - 依赖注入相关的辅助代码 这使得我们可以使用 `this.GetModel()` 等扩展方法。 ::: ### 2. 获取 Model 并监听事件 修改 `_Ready` 方法: ```csharp using GFramework.Core.Abstractions.Controller; using GFramework.Core.Extensions; using GFramework.SourceGenerators.Abstractions.Rule; using Godot; using MyGFrameworkGame.scripts.Model; namespace MyGFrameworkGame.scripts.app; [ContextAware] public partial class App : Control, IController { private Button AddButton => GetNode