---
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