第 7 章:总结与最佳实践
恭喜你完成了 GFramework 基础教程!本章将回顾整个架构设计,总结最佳实践,并解答常见问题。
架构演进回顾
阶段 1:基础实现
代码:
csharp
private int _count;
AddButton.Pressed += () =>
{
_count++;
UpdateView();
};问题:
- ❌ 状态、逻辑、UI 混在一起
- ❌ 无法复用
- ❌ 难以测试
- ❌ 扩展困难
阶段 2:引入 Model + 事件
代码:
csharp
private ICounterModel _counterModel;
AddButton.Pressed += () =>
{
_counterModel.Increment();
};
this.RegisterEvent<ChangedCountEvent>(e =>
{
UpdateView(e.Count);
});改进:
- ✅ 状态抽离到 Model
- ✅ 通过事件更新 UI
- ✅ Model 可复用、可测试
剩余问题:
- ⚠️ 交互逻辑仍在 Controller
阶段 3:引入 Command
代码:
csharp
AddButton.Pressed += () =>
{
this.SendCommand(new IncreaseCountCommand());
};改进:
- ✅ Controller 不关心"如何",只负责"转发"
- ✅ 逻辑封装在 Command 中
- ✅ 命令可复用、可测试
剩余问题:
- ⚠️ 业务规则写在 Command 里
阶段 4:引入 Utility + System
代码:
csharp
// Command 使用 Utility 检查规则
if (!utility.CanIncrease(model.Count)) return;
model.Increment();
// System 响应状态变化
this.RegisterEvent<ChangedCountEvent>(e =>
{
CheckThreshold(e.Count);
});最终架构:
- ✅ 完全的关注点分离
- ✅ 单向数据流
- ✅ 各层可测试、可复用
- ✅ 易于扩展和维护
完整架构图
┌──────────────────────────────────────────────┐
│ View (UI) │
│ Godot Nodes (Label, Button) │
└────────────┬─────────────────────────────────┘
│ 用户输入
┌────────────▼─────────────────────────────────┐
│ Controller │
│ - 接收用户输入 │
│ - 转发命令 │
│ - 监听事件更新 UI │
└────────────┬─────────────────────────────────┘
│ SendCommand
┌────────────▼─────────────────────────────────┐
│ Command │
│ - 获取 Utility 检查规则 │
│ - 调用 Model 修改状态 │
└────────────┬─────────────────────────────────┘
│ GetModel / GetUtility
│
┌─────────┼─────────┐
│ │
┌──▼──────────┐ ┌──────▼───────┐
│ Utility │ │ Model │
│ - 业务规则 │ │ - 存储状态 │
│ - 纯计算 │ │ - 发送事件 │
└─────────────┘ └──────┬───────┘
│ SendEvent
┌──────┴───────┐
│ │
┌───────▼─────┐ ┌──────▼──────┐
│ Controller │ │ System │
│ 更新 UI │ │ 响应状态 │
└─────────────┘ └─────────────┘各层职责速查表
| 层级 | 职责 | 可以做 | 不能做 |
|---|---|---|---|
| View | UI 展示 | 渲染节点、接收输入 | 包含业务逻辑 |
| Controller | 协调层 | 转发命令、监听事件、更新 UI | 直接修改 Model |
| Command | 业务操作 | 调用 Model、使用 Utility | 持有状态、直接更新 UI |
| Model | 数据状态 | 存储数据、发送事件 | 知道 View、调用 Controller |
| Utility | 业务规则 | 无状态计算、验证 | 持有状态、依赖场景 |
| System | 系统逻辑 | 监听事件、协调 Model | 直接修改 Model(应通过 Command) |
设计原则
1. 单一职责原则(SRP)
每个类只做一件事:
csharp
// ✅ Model 只负责状态
public class CounterModel : AbstractModel
{
public int Count { get; private set; }
public void Increment() { /* ... */ }
}
// ✅ Command 只负责操作
public class IncreaseCountCommand : AbstractCommand
{
protected override void OnExecute() { /* ... */ }
}2. 依赖倒置原则(DIP)
依赖抽象,不依赖具体实现:
csharp
// ✅ 依赖接口
private ICounterModel _counterModel;
// ❌ 依赖具体类
private CounterModel _counterModel;3. 开闭原则(OCP)
对扩展开放,对修改封闭:
csharp
// ✅ 新增功能不修改现有代码
architecture.RegisterSystem(new NewFeatureSystem());
// ❌ 修改现有类添加功能
public class CounterModel
{
// 每次新增功能都修改这个类
}4. 事件驱动原则
通过事件解耦组件:
csharp
// ✅ Model 不知道谁在监听
this.SendEvent(new ChangedCountEvent());
// ❌ Model 直接调用
_view.UpdateView();5. 单向数据流
数据总是单向流动:
Action → Command → Model → Event → View/System最佳实践
1. 接口设计
✅ 推荐:
csharp
public interface ICounterModel : IModel
{
int Count { get; }
void Increment();
}❌ 不推荐:
csharp
public class CounterModel // 没有接口
{
public int Count { get; set; } // 可被外部直接修改
}2. 事件命名
✅ 推荐:
csharp
public sealed record ChangedCountEvent // 描述事件
{
public int Count { get; init; }
}❌ 不推荐:
csharp
public class CountEvent { } // 不清晰
public class Data { } // 太泛化3. Command 职责
✅ 推荐:
csharp
protected override void OnExecute()
{
// 1. 获取依赖
var model = this.GetModel<ICounterModel>();
var utility = this.GetUtility<ICounterUtility>();
// 2. 检查规则
if (!utility.CanIncrease(model.Count)) return;
// 3. 执行操作
model.Increment();
}❌ 不推荐:
csharp
protected override void OnExecute()
{
_count++; // 直接修改状态
UpdateUI(); // 直接更新 UI
PlaySound(); // 混入太多逻辑
}4. Utility 设计
✅ 推荐:
csharp
public bool CanIncrease(int current)
{
return current < _maxCount; // 纯函数
}❌ 不推荐:
csharp
private int _state; // 持有状态
public void Increment()
{
_state++; // 修改状态
}5. System 使用
✅ 推荐:
csharp
protected override void OnInit()
{
// 监听事件
this.RegisterEvent<ChangedCountEvent>(e =>
{
CheckThreshold(e.Count);
});
}❌ 不推荐:
csharp
public void UpdateCounter()
{
// 直接修改 Model
model.Count++; // 应该通过 Command
}常见问题 FAQ
Q1: Model 可以调用其他 Model 吗?
❌ 不推荐:
csharp
public class PlayerModel : AbstractModel
{
public void Attack()
{
// 直接调用其他 Model
this.GetModel<EnemyModel>().TakeDamage(10);
}
}✅ 推荐:通过 Command 或 System 协调:
csharp
public class AttackCommand : AbstractCommand
{
protected override void OnExecute()
{
var player = this.GetModel<IPlayerModel>();
var enemy = this.GetModel<IEnemyModel>();
enemy.TakeDamage(player.AttackPower);
}
}Q2: Command 可以嵌套调用吗?
✅ 可以:
csharp
protected override void OnExecute()
{
this.SendCommand(new SaveDataCommand());
this.SendCommand(new UpdateUICommand());
}但要注意:
- 避免循环依赖
- 考虑使用 System 协调复杂流程
Q3: 什么时候用 Utility,什么时候用 System?
| 场景 | 使用 |
|---|---|
| 无状态计算 | Utility |
| 业务规则验证 | Utility |
| 响应状态变化 | System |
| 协调多个 Model | System |
| 触发系统级反应 | System |
示例:
csharp
// Utility:纯计算
utility.CanIncrease(count)
// System:状态响应
if (count > 10) PlaySound();Q4: Controller 可以直接调用 Model 吗?
部分场景可以:
csharp
// ✅ 只读操作
var count = this.GetModel<ICounterModel>().Count;
// ❌ 修改操作(应通过 Command)
this.GetModel<ICounterModel>().Increment();原则:
- 读取数据:可以直接调用
- 修改数据:应该通过 Command
Q5: 如何处理异步操作?
使用 AbstractAsyncCommand:
csharp
public class SaveDataCommand : AbstractAsyncCommand
{
protected override async Task OnExecuteAsync()
{
var model = this.GetModel<ICounterModel>();
await SaveToFileAsync(model.Count);
}
}Q6: 如何在多个场景共享状态?
Model 是全局的:
csharp
// 场景 A
var count = this.GetModel<ICounterModel>().Count;
// 场景 B
var count = this.GetModel<ICounterModel>().Count;
// 两者是同一个 Model 实例如果需要场景独立的状态,考虑:
- 为每个场景创建独立的 Model
- 使用场景参数传递数据
Q7: 如何测试这些组件?
Model 测试:
csharp
[Test]
public void Increment_ShouldIncreaseCount()
{
var model = new CounterModel();
model.Increment();
Assert.AreEqual(1, model.Count);
}Utility 测试:
csharp
[Test]
public void CanIncrease_WhenAtMax_ReturnsFalse()
{
var utility = new CounterUtility(maxCount: 20);
Assert.IsFalse(utility.CanIncrease(20));
}Command 测试(需要 mock):
csharp
[Test]
public void ExecuteCommand_ShouldIncrementModel()
{
// 需要 mock IArchitecture
// 或使用集成测试
}Q8: 项目变大后如何组织代码?
按功能模块划分:
scripts/
├── counter/
│ ├── model/
│ ├── command/
│ └── system/
├── player/
│ ├── model/
│ ├── command/
│ └── system/
└── inventory/
├── model/
├── command/
└── system/按层级划分:
scripts/
├── model/
│ ├── CounterModel.cs
│ ├── PlayerModel.cs
│ └── InventoryModel.cs
├── command/
│ ├── counter/
│ ├── player/
│ └── inventory/
└── system/
├── CounterSystem.cs
└── PlayerSystem.cs选择适合团队的方式。
性能考虑
事件系统开销
问题:频繁发送事件会影响性能吗?
答案:
- GFramework 的事件系统经过优化,开销很小
- 对于游戏逻辑级别的事件(如计数变化),完全没问题
- 如果是高频事件(如每帧更新),考虑批处理
示例:
csharp
// ❌ 高频事件(每帧发送)
public override void _Process(double delta)
{
this.SendEvent(new PositionChangedEvent());
}
// ✅ 批处理或降频
private float _eventTimer;
public override void _Process(double delta)
{
_eventTimer += (float)delta;
if (_eventTimer > 0.1f) // 每 100ms 发送一次
{
this.SendEvent(new PositionChangedEvent());
_eventTimer = 0;
}
}依赖注入开销
问题:GetModel() 会影响性能吗?
答案:
- 第一次调用会查找,之后会缓存
- 建议在
_Ready中获取并缓存
示例:
csharp
// ✅ 缓存引用
private ICounterModel _counterModel;
public override void _Ready()
{
_counterModel = this.GetModel<ICounterModel>();
}
public override void _Process(double delta)
{
var count = _counterModel.Count; // 使用缓存的引用
}下一步学习
进阶主题
高级命令模式
- 异步命令
- 命令队列
- 撤销/重做
复杂事件系统
- 事件优先级
- 事件过滤
- 事件链
高级 System
- System 之间的通信
- System 生命周期管理
- System 优先级
规则系统
- 动态规则
- 规则链
- 规则引擎
状态机
- 使用 GFramework 实现状态机
- 分层状态机
- 状态转换规则
推荐资源
GFramework 文档:
设计模式:
- 命令模式(Command Pattern)
- 观察者模式(Observer Pattern)
- 依赖注入(Dependency Injection)
架构设计:
- Clean Architecture
- MVC / MVVM
- Event-Driven Architecture
项目示例
查看完整的示例项目:
总结
通过本教程,你学到了:
核心概念
- ✅ Model:存储状态,发送事件
- ✅ Command:封装业务逻辑
- ✅ Controller:协调用户输入
- ✅ Utility:提供业务规则
- ✅ System:响应状态变化
设计原则
- ✅ 单一职责
- ✅ 依赖倒置
- ✅ 事件驱动
- ✅ 单向数据流
- ✅ 关注点分离
架构优势
- ✅ 可测试
- ✅ 可复用
- ✅ 可扩展
- ✅ 易维护
- ✅ 解耦合
结语
恭喜你完成了 GFramework 基础教程!🎉
你现在已经掌握了使用 GFramework 构建清晰、可维护的游戏架构的核心知识。
记住:
- 架构是为了解决问题,不是为了炫技
- 从简单开始,逐步优化
- 理解原则比记住代码更重要
继续探索,享受编程的乐趣!
反馈与支持
- 遇到问题?查看 GitHub Issues
- 有建议?欢迎提交 PR 或 Issue
- 加入社区,与其他开发者交流
完整检查清单
环境与项目
- [ ] .NET SDK 和 Godot 已安装
- [ ] GFramework NuGet 包已引入
- [ ] 项目架构已搭建
核心组件
- [ ] Model 已创建并注册
- [ ] Command 已创建
- [ ] Utility 已创建并注册
- [ ] System 已创建并注册
- [ ] Controller 实现了 IController
功能验证
- [ ] 计数器功能正常
- [ ] 事件系统工作正常
- [ ] 上限限制生效
- [ ] 阈值检查触发
理解验证
- [ ] 理解了各层职责
- [ ] 理解了事件驱动架构
- [ ] 理解了单向数据流
- [ ] 理解了设计原则
👏 再次恭喜你完成教程!期待看到你用 GFramework 创造出精彩的项目!