--- prev: text: 'Utility 与 System' link: './06-utility-system' --- # 第 7 章:总结与最佳实践 恭喜你完成了 GFramework 基础教程!本章将回顾整个架构设计,总结最佳实践,并解答常见问题。 ## 架构演进回顾 ### 阶段 1:基础实现 **代码**: ```csharp private int _count; AddButton.Pressed += () => { _count++; UpdateView(); }; ``` **问题**: - ❌ 状态、逻辑、UI 混在一起 - ❌ 无法复用 - ❌ 难以测试 - ❌ 扩展困难 --- ### 阶段 2:引入 Model + 事件 **代码**: ```csharp private ICounterModel _counterModel; AddButton.Pressed += () => { _counterModel.Increment(); }; this.RegisterEvent(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(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(); var utility = this.GetUtility(); // 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(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().TakeDamage(10); } } ``` **✅ 推荐**:通过 Command 或 System 协调: ```csharp public class AttackCommand : AbstractCommand { protected override void OnExecute() { var player = this.GetModel(); var enemy = this.GetModel(); 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().Count; // ❌ 修改操作(应通过 Command) this.GetModel().Increment(); ``` **原则**: - 读取数据:可以直接调用 - 修改数据:应该通过 Command --- ### Q5: 如何处理异步操作? 使用 `AbstractAsyncCommand`: ```csharp public class SaveDataCommand : AbstractAsyncCommand { protected override async Task OnExecuteAsync() { var model = this.GetModel(); await SaveToFileAsync(model.Count); } } ``` --- ### Q6: 如何在多个场景共享状态? **Model 是全局的**: ```csharp // 场景 A var count = this.GetModel().Count; // 场景 B var count = this.GetModel().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(); } public override void _Process(double delta) { var count = _counterModel.Count; // 使用缓存的引用 } ``` ## 下一步学习 ### 进阶主题 1. **高级命令模式** - 异步命令 - 命令队列 - 撤销/重做 2. **复杂事件系统** - 事件优先级 - 事件过滤 - 事件链 3. **高级 System** - System 之间的通信 - System 生命周期管理 - System 优先级 4. **规则系统** - 动态规则 - 规则链 - 规则引擎 5. **状态机** - 使用 GFramework 实现状态机 - 分层状态机 - 状态转换规则 ### 推荐资源 - **GFramework 文档**: - [Core 核心框架](../../core/) - [Game 游戏模块](../../game/) - [Godot 集成](../../godot/) - [源码生成器](../../source-generators/) - **设计模式**: - 命令模式(Command Pattern) - 观察者模式(Observer Pattern) - 依赖注入(Dependency Injection) - **架构设计**: - Clean Architecture - MVC / MVVM - Event-Driven Architecture ## 项目示例 查看完整的示例项目: - [基础教程(本教程)](https://github.com/GeWuYou/GFramework/tree/main/examples/Counter) - [Godot 集成示例](https://github.com/GeWuYou/GFramework/tree/main/examples/GodotIntegration) - [高级模式示例](https://github.com/GeWuYou/GFramework/tree/main/examples/AdvancedPatterns) ## 总结 通过本教程,你学到了: ### 核心概念 - ✅ Model:存储状态,发送事件 - ✅ Command:封装业务逻辑 - ✅ Controller:协调用户输入 - ✅ Utility:提供业务规则 - ✅ System:响应状态变化 ### 设计原则 - ✅ 单一职责 - ✅ 依赖倒置 - ✅ 事件驱动 - ✅ 单向数据流 - ✅ 关注点分离 ### 架构优势 - ✅ 可测试 - ✅ 可复用 - ✅ 可扩展 - ✅ 易维护 - ✅ 解耦合 ## 结语 恭喜你完成了 GFramework 基础教程!🎉 你现在已经掌握了使用 GFramework 构建清晰、可维护的游戏架构的核心知识。 记住: - **架构是为了解决问题,不是为了炫技** - **从简单开始,逐步优化** - **理解原则比记住代码更重要** 继续探索,享受编程的乐趣! --- ::: tip 反馈与支持 - 遇到问题?查看 [GitHub Issues](https://github.com/GeWuYou/GFramework/issues) - 有建议?欢迎提交 PR 或 Issue - 加入社区,与其他开发者交流 ::: ::: details 完整检查清单 **环境与项目** - [ ] .NET SDK 和 Godot 已安装 - [ ] GFramework NuGet 包已引入 - [ ] 项目架构已搭建 **核心组件** - [ ] Model 已创建并注册 - [ ] Command 已创建 - [ ] Utility 已创建并注册 - [ ] System 已创建并注册 - [ ] Controller 实现了 IController **功能验证** - [ ] 计数器功能正常 - [ ] 事件系统工作正常 - [ ] 上限限制生效 - [ ] 阈值检查触发 **理解验证** - [ ] 理解了各层职责 - [ ] 理解了事件驱动架构 - [ ] 理解了单向数据流 - [ ] 理解了设计原则 ::: 👏 再次恭喜你完成教程!期待看到你用 GFramework 创造出精彩的项目!