docs(core): 更新核心架构文档完善命令事件系统说明

- 重构架构文档增加模块管理和生命周期管理说明
- 更新命令系统文档移除输入参数结构简化命令定义
- 完善事件系统文档添加事件总线和注销功能说明
- 补充IoC容器文档增加多实例注册和单例管理说明
- 优化示例代码展示架构初始化和组件获取方式
- 添加配置选项说明和最佳实践建议
This commit is contained in:
GeWuYou 2026-02-11 20:13:40 +08:00
parent 4be7f7a731
commit 7a18d9459b
7 changed files with 975 additions and 346 deletions

View File

@ -2,24 +2,26 @@
## 概述
Architecture 包是整个框架的核心,提供了基于 MVC 架构模式的应用程序架构基础。它实现了依赖注入(IoC)
容器、组件生命周期管理,以及命令、查询、事件的统一调度机制。
Architecture 包是整个框架的核心,提供了基于分层架构模式的应用程序架构基础。它实现了依赖注入(IoC)
容器、组件生命周期管理以及命令、查询、事件的统一调度机制。
**注意**:本框架的 Core 模块与 Godot 解耦,Godot 相关集成在 GFramework.Godot 包中实现。
**注意**:本框架的 Core 模块与 Godot 解耦,Godot 相关集成在 GFramework.Godot 包中实现。
## 核心接口
### IArchitecture
架构接口,定义了框架的核心功能契约。
架构接口定义了框架的核心功能契约。
**主要职责:**
**主要职责**
- 组件注册:注册 System、Model、Utility
- 组件获取:从容器中获取已注册的组件
- 命令处理:发送并执行命令
- 查询处理:发送并执行查询
- 事件管理:发送、注册、注销事件
- **组件注册**:注册 System、Model、Utility
- **组件获取**:从容器中获取已注册的组件
- **命令处理**:发送并执行命令
- **查询处理**:发送并执行查询
- **事件管理**:发送、注册、注销事件
- **模块管理**:安装和管理架构模块
- **生命周期管理**:管理架构的初始化、运行和销毁阶段
**核心方法:**
```csharp
@ -49,27 +51,27 @@ void UnRegisterEvent<T>(Action<T> onEvent);
### IArchitecturePhaseAware
架构阶段感知接口,允许组件监听架构阶段变化。
架构阶段感知接口允许组件监听架构阶段变化。
**核心方法:**
**核心方法**
```csharp
void OnArchitecturePhase(ArchitecturePhase phase);
```
### IArchitectureModule
架构模块接口,支持模块化架构扩展。
架构模块接口支持模块化架构扩展。
**核心方法:**
**核心方法**
```csharp
void Install(IArchitecture architecture);
```
### IAsyncInitializable
异步初始化接口,支持组件异步初始化。
异步初始化接口支持组件异步初始化。
**核心方法:**
**核心方法**
```csharp
Task InitializeAsync();
```
@ -78,9 +80,9 @@ Task InitializeAsync();
### Architecture 架构基类
架构基类,实现了 `IArchitecture` 接口,提供完整的架构功能实现。
架构基类,实现了 `IArchitecture` 接口,提供完整的架构功能实现。
**构造函数参数:**
**构造函数参数**
```csharp
public abstract class Architecture(
IArchitectureConfiguration? configuration = null,
@ -90,17 +92,19 @@ public abstract class Architecture(
)
```
**特性:**
**特性**
- **阶段式生命周期管理**:支持多个架构阶段(
BeforeUtilityInit、AfterUtilityInit、BeforeModelInit、AfterModelInit、BeforeSystemInit、AfterSystemInit、Ready、Destroying、Destroyed)
- **模块安装系统**:支持通过 `InstallModule` 扩展架构功能
- **异步初始化**:支持同步和异步两种初始化方式
- **IoC 容器集成**:内置依赖注入容器
- **事件系统集成**:集成类型化事件系统
- **与平台无关**:Core 模块不依赖 Godot,可以在任何 .NET 环境中使用
- **阶段式生命周期管理**
支持多个架构阶段BeforeUtilityInit、AfterUtilityInit、BeforeModelInit、AfterModelInit、BeforeSystemInit、AfterSystemInit、Ready、FailedInitialization、Destroying、Destroyed
- **模块安装系统**:支持通过 `InstallModule` 扩展架构功能
- **异步初始化**:支持同步和异步两种初始化方式
- **IoC 容器集成**:内置依赖注入容器
- **事件系统集成**:集成类型化事件系统
- **与平台无关**Core 模块不依赖 Godot可以在任何 .NET 环境中使用
- **严格的阶段验证**:可配置的阶段转换验证机制
- **组件生命周期管理**:自动管理组件的初始化和销毁
**架构阶段:**
**架构阶段**
```csharp
public enum ArchitecturePhase
{
@ -118,28 +122,29 @@ public enum ArchitecturePhase
}
```
**初始化流程:**
**初始化流程**
1. 创建架构实例(传入配置或使用默认配置)
2. 调用用户自定义的 `Init()` 方法
3. 初始化上下文工具(Context Utility)
4. 初始化所有注册的 Model
5. 初始化所有注册的 System
6. 冻结 IOC 容器
7. 进入 Ready 阶段
1. 创建架构实例(传入配置或使用默认配置)
2. 初始化基础上下文和日志系统
3. 调用用户自定义的 `Init()` 方法
4. 按顺序初始化组件:
- 工具初始化BeforeUtilityInit → AfterUtilityInit
- 模型初始化BeforeModelInit → AfterModelInit
- 系统初始化BeforeSystemInit → AfterSystemInit
5. 冻结 IOC 容器
6. 进入 Ready 阶段
**销毁流程:**
**销毁流程**
1. 进入 Destroying 阶段
2. 发送 `ArchitectureDestroyingEvent` 事件
3. 销毁所有 System
4. 进入 Destroyed 阶段
5. 发送 `ArchitectureDestroyedEvent` 事件
2. 按注册逆序销毁所有实现了 `IDisposable` 的组件
3. 进入 Destroyed 阶段
4. 清理所有资源
**使用示例:**
```csharp
// 1. 定义你的架构(继承 Architecture 基类)
// 1. 定义你的架构(继承 Architecture 基类)
public class GameArchitecture : Architecture
{
protected override void Init()
@ -166,31 +171,28 @@ architecture.Initialize();
// var architecture = new GameArchitecture();
// await architecture.InitializeAsync();
// 3. 通过依赖注入使用架构
// 在 Controller 或其他组件中注入架构实例
// 3. 等待架构就绪
await architecture.WaitUntilReadyAsync();
// 4. 通过依赖注入使用架构
// 在 Controller 或其他组件中获取架构实例
public class GameController : IController
{
private readonly IArchitecture _architecture;
// 通过构造函数注入架构
public GameController(IArchitecture architecture)
{
_architecture = architecture;
}
public IArchitecture GetArchitecture() => GameArchitecture.Interface;
public void Start()
{
// 获取 Model
var playerModel = _architecture.GetModel<PlayerModel>();
var playerModel = this.GetModel<PlayerModel>();
// 发送命令
_architecture.SendCommand(new StartGameCommand());
this.SendCommand(new StartGameCommand());
// 发送查询
var score = _architecture.SendQuery(new GetScoreQuery());
var score = this.SendQuery(new GetScoreQuery());
// 注册事件
_architecture.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
this.RegisterEvent<PlayerDiedEvent>(OnPlayerDied);
}
private void OnPlayerDied(PlayerDiedEvent e)
@ -206,52 +208,73 @@ public class GameController : IController
**Initialize()**
同步初始化方法,阻塞当前线程直到初始化完成。
同步初始化方法阻塞当前线程直到初始化完成。
```csharp
public void Initialize()
```
使用示例:
**特点:**
- 阻塞式初始化
- 适用于简单场景或控制台应用
- 初始化失败时抛出异常并进入 `FailedInitialization` 阶段
使用示例:
```csharp
var architecture = new GameArchitecture();
architecture.Initialize(); // 阻塞等待初始化完成
```
异常处理: 如果初始化过程中发生异常,架构会进入 `FailedInitialization` 阶段并发送 `ArchitectureFailedInitializationEvent`
事件。
异常处理:如果初始化过程中发生异常,架构会进入 `FailedInitialization` 阶段。
**InitializeAsync()**
异步初始化方法,返回 Task 以便调用者可以等待初始化完成。
异步初始化方法返回 Task 以便调用者可以等待初始化完成。
```csharp
public async Task InitializeAsync()
```
使用示例:
**特点:**
- 非阻塞式初始化
- 支持异步组件初始化
- 适用于需要异步加载资源的场景
- 初始化失败时抛出异常并进入 `FailedInitialization` 阶段
使用示例:
```csharp
var architecture = new GameArchitecture();
await architecture.InitializeAsync(); // 异步等待初始化完成
```
优势:
优势
- 支持异步初始化 Model 和 System
- 可以利用异步 I/O 操作(如异步加载数据)
- 可以利用异步 I/O 操作(如异步加载数据)
- 提高初始化性能
- 不会阻塞主线程
#### 模块管理
**InstallModule(IArchitectureModule module)**
安装架构模块,用于扩展架构功能。
安装架构模块用于扩展架构功能。
```csharp
public void InstallModule(IArchitectureModule module)
public IArchitectureModule InstallModule(IArchitectureModule module)
```
参数:
**返回值:** 返回安装的模块实例
- `module`:要安装的模块实例
**特点:**
使用示例:
- 模块安装时会自动注册生命周期钩子
- 模块可以注册额外的组件到架构中
- 只能在架构进入 Ready 阶段之前安装模块
参数:
- `module`:要安装的模块实例
使用示例:
```csharp
// 定义模块
@ -267,23 +290,31 @@ public class NetworkModule : IArchitectureModule
// 安装模块
var architecture = new GameArchitecture();
architecture.InstallModule(new NetworkModule());
var installedModule = architecture.InstallModule(new NetworkModule());
```
#### 生命周期钩子
**RegisterLifecycleHook(IArchitectureLifecycle hook)**
注册生命周期钩子,用于在架构阶段变化时执行自定义逻辑。
注册生命周期钩子用于在架构阶段变化时执行自定义逻辑。
```csharp
public void RegisterLifecycleHook(IArchitectureLifecycle hook)
public IArchitectureLifecycle RegisterLifecycleHook(IArchitectureLifecycle hook)
```
参数:
**返回值:** 返回注册的生命周期钩子实例
- `hook`:生命周期钩子实例
**特点:**
使用示例:
- 生命周期钩子可以监听所有架构阶段变化
- 只能在架构进入 Ready 阶段之前注册
- 架构会按注册顺序通知所有钩子
参数:
- `hook`:生命周期钩子实例
使用示例:
```csharp
// 定义生命周期钩子
@ -308,11 +339,11 @@ public class PerformanceMonitorHook : IArchitectureLifecycle
// 注册生命周期钩子
var architecture = new GameArchitecture();
architecture.RegisterLifecycleHook(new PerformanceMonitorHook());
var registeredHook = architecture.RegisterLifecycleHook(new PerformanceMonitorHook());
architecture.Initialize();
```
**注意:** 生命周期钩子只能在架构进入 Ready 阶段之前注册。
**注意** 生命周期钩子只能在架构进入 Ready 阶段之前注册。
#### 属性
@ -320,10 +351,16 @@ architecture.Initialize();
获取当前架构的阶段。
```csharp
public ArchitecturePhase CurrentPhase { get; }
public ArchitecturePhase CurrentPhase { get; private set; }
```
使用示例:
**特点:**
- 只读属性,外部无法直接修改
- 实时反映架构的当前状态
- 可用于条件判断和状态检查
使用示例:
```csharp
var architecture = new GameArchitecture();
@ -347,12 +384,18 @@ await Task.Run(async () =>
**Context**
获取架构上下文,提供对架构服务的访问。
获取架构上下文提供对架构服务的访问。
```csharp
public IArchitectureContext Context { get; }
```
使用示例:
**特点:**
- 提供对架构核心服务的访问
- 包含事件总线、命令总线、查询总线等
- 是架构内部服务的统一入口
使用示例:
```csharp
// 通过 Context 访问服务
@ -367,7 +410,14 @@ var environment = context.Environment;
```csharp
// 1. 使用自定义配置
var config = new ArchitectureConfiguration();
var config = new ArchitectureConfiguration
{
ArchitectureProperties = new ArchitectureProperties
{
StrictPhaseValidation = true, // 启用严格阶段验证
AllowLateRegistration = false // 禁止就绪后注册组件
}
};
var architecture = new GameArchitecture(configuration: config);
// 2. 模块安装
@ -382,10 +432,10 @@ public class GamePhaseListener : IArchitecturePhaseAware
switch (phase)
{
case ArchitecturePhase.Ready:
GD.Print("架构已就绪,可以开始游戏了");
Console.WriteLine("架构已就绪,可以开始游戏了");
break;
case ArchitecturePhase.Destroying:
GD.Print("架构正在销毁");
Console.WriteLine("架构正在销毁");
break;
}
}
@ -396,24 +446,57 @@ public class LifecycleHook : IArchitectureLifecycle
{
public void OnPhase(ArchitecturePhase phase, IArchitecture architecture)
{
GD.Print($"架构阶段变化: {phase}");
Console.WriteLine($"架构阶段变化: {phase}");
}
}
// 5. 等待架构就绪
public async Task WaitForArchitectureReady()
{
var architecture = new GameArchitecture();
var initTask = architecture.InitializeAsync();
// 可以在其他地方等待架构就绪
await architecture.WaitUntilReadyAsync();
Console.WriteLine("架构已就绪");
}
```
### ArchitectureConfiguration 架构配置类
架构配置类,用于配置架构的行为。
架构配置类用于配置架构的行为。
**使用示例:**
**主要配置项:**
```csharp
public class ArchitectureConfiguration : IArchitectureConfiguration
{
public IArchitectureProperties ArchitectureProperties { get; set; } = new ArchitectureProperties();
public IEnvironmentProperties EnvironmentProperties { get; set; } = new EnvironmentProperties();
public ILoggerProperties LoggerProperties { get; set; } = new LoggerProperties();
}
public class ArchitectureProperties
{
public bool StrictPhaseValidation { get; set; } = false; // 严格阶段验证
public bool AllowLateRegistration { get; set; } = true; // 允许延迟注册
}
```
**使用示例:**
```csharp
var config = new ArchitectureConfiguration
{
// 严格阶段验证
StrictPhaseValidation = true,
// 允许延迟注册
AllowLateRegistration = false
ArchitectureProperties = new ArchitectureProperties
{
StrictPhaseValidation = true, // 启用严格阶段验证
AllowLateRegistration = false // 禁止就绪后注册组件
},
LoggerProperties = new LoggerProperties
{
LoggerFactoryProvider = new ConsoleLoggerFactoryProvider() // 自定义日志工厂
}
};
var architecture = new GameArchitecture(configuration: config);
@ -421,15 +504,34 @@ var architecture = new GameArchitecture(configuration: config);
### ArchitectureServices 架构服务类
架构服务类,管理命令总线、查询总线、IOC容器和类型事件系统。
架构服务类管理命令总线、查询总线、IOC容器和类型事件系统。
**核心服务:**
- `IIocContainer Container`:依赖注入容器
- `IEventBus EventBus`:事件总线
- `ICommandBus CommandBus`:命令总线
- `IQueryBus QueryBus`:查询总线
### ArchitectureContext 架构上下文类
架构上下文类,提供对架构服务的访问。
架构上下文类,提供对架构服务的访问。
**功能:**
- 统一访问架构核心服务
- 管理服务实例的生命周期
- 提供服务解析功能
### GameContext 游戏上下文类
游戏上下文类,管理架构上下文与类型的绑定关系。
游戏上下文类,管理架构上下文与类型的绑定关系。
**功能:**
- 维护架构类型与上下文实例的映射
- 提供全局上下文访问
- 支持多架构实例管理
## 设计模式
@ -459,20 +561,31 @@ var architecture = new GameArchitecture(configuration: config);
### 7. 组合优于继承
通过接口组合获得不同能力,而不是深层继承链。
通过接口组合获得不同能力,而不是深层继承链。
### 8. 模块化设计
通过 `IArchitectureModule` 实现架构的可扩展性。
### 9. 工厂模式
通过配置对象和工厂方法创建架构实例。
## 最佳实践
1. **保持架构类简洁**:只在 `Init()` 中注册组件,不要包含业务逻辑
2. **合理划分职责**:
- Model:数据和状态
- System:业务逻辑和规则
- Utility:无状态的工具方法
3. **使用依赖注入**:通过构造函数注入架构实例,便于测试
4. **事件命名规范**:使用过去式命名事件类,如 `PlayerDiedEvent`
5. **避免循环依赖**:System 不应直接引用 System,应通过事件通信
6. **使用模块扩展**:通过 `IArchitectureModule` 实现架构的可扩展性
7. **Core 模块与平台解耦**:GFramework.Core 不包含 Godot 相关代码,Godot 集成在单独模块中
1. **保持架构类简洁**:只在 `Init()` 中注册组件,不要包含业务逻辑
2. **合理划分职责**
- Model数据和状态
- System业务逻辑和规则
- Utility无状态的工具方法
3. **使用依赖注入**:通过构造函数注入架构实例,便于测试
4. **事件命名规范**:使用过去式命名事件类,如 `PlayerDiedEvent`
5. **避免循环依赖**System 不应直接引用 System应通过事件通信
6. **使用模块扩展**:通过 `IArchitectureModule` 实现架构的可扩展性
7. **Core 模块与平台解耦**GFramework.Core 不包含 Godot 相关代码Godot 集成在单独模块中
8. **合理配置阶段验证**:根据项目需求配置 `StrictPhaseValidation``AllowLateRegistration`
9. **及时处理初始化异常**:捕获并处理架构初始化过程中的异常
10. **使用异步初始化**:对于需要加载大量资源的场景,优先使用 `InitializeAsync()`
## 相关包
@ -483,8 +596,11 @@ var architecture = new GameArchitecture(configuration: config);
- **model** - 数据模型
- **system** - 业务系统
- **utility** - 工具类
- **GFramework.Godot** - Godot 特定集成(GodotNode 扩展、GodotLogger 等)
- **GFramework.Godot** - Godot 特定集成GodotNode 扩展、GodotLogger 等)
- **extensions** - 扩展方法
- **logging** - 日志系统
- **environment** - 环境管理
---
**许可证**: Apache 2.0
**许可证**Apache 2.0

View File

@ -4,9 +4,11 @@
Command 包实现了命令模式Command Pattern用于封装用户操作和业务逻辑。通过命令模式可以将请求封装为对象实现操作的参数化、队列化、日志记录、撤销等功能。
命令系统是 GFramework CQRS 架构的重要组成部分,与事件系统和查询系统协同工作,实现完整的业务逻辑处理流程。
## 核心接口
### 1. [`ICommand`](./command.md)
### ICommand
无返回值命令接口,定义了命令的基本契约。
@ -16,7 +18,7 @@ Command 包实现了命令模式Command Pattern用于封装用户操作
void Execute(); // 执行命令
```
### 2. [`ICommand<TResult>`](./command.md)
### ICommand`<TResult>`
带返回值的命令接口,用于需要返回执行结果的命令。
@ -28,37 +30,41 @@ TResult Execute(); // 执行命令并返回结果
## 核心类
### 1. [`AbstractCommand<TInput>`](./command.md)
### AbstractCommand
无返回值命令的抽象基类,提供了命令的基础实现。它继承自 [ContextAwareBase](./rule.md)
,具有上下文感知能力。
无返回值命令的抽象基类,提供了命令的基础实现。它继承自 ContextAwareBase具有上下文感知能力。
**核心方法:**
```csharp
void ICommand.Execute(); // 实现 ICommand 接口
protected abstract void OnExecute(); // 抽象执行方法,由子类实现
```
**使用示例:**
```csharp
// 定义一个命令输入参数
public struct StartGameCommandInput : ICommandInput
{
public int LevelId { get; set; }
public string PlayerName { get; set; }
}
// 定义一个开始游戏的命令
public class StartGameCommand : AbstractCommand<StartGameCommandInput>
public class StartGameCommand : AbstractCommand
{
public StartGameCommand(StartGameCommandInput input) : base(input)
private readonly int _levelId;
private readonly string _playerName;
public StartGameCommand(int levelId, string playerName)
{
_levelId = levelId;
_playerName = playerName;
}
protected override void OnExecute(StartGameCommandInput input)
protected override void OnExecute()
{
// 获取需要的模型
var playerModel = this.GetModel<PlayerModel>();
var gameModel = this.GetModel<GameModel>();
// 执行业务逻辑
playerModel.PlayerName.Value = input.PlayerName;
gameModel.CurrentLevel.Value = input.LevelId;
playerModel.PlayerName.Value = _playerName;
gameModel.CurrentLevel.Value = _levelId;
gameModel.GameState.Value = GameState.Playing;
// 发送事件通知其他模块
@ -74,43 +80,44 @@ public class GameController : IController
public void OnStartButtonClicked()
{
// 发送命令实例
this.SendCommand(new StartGameCommand(new StartGameCommandInput
{
LevelId = 1,
PlayerName = "Player1"
}));
this.SendCommand(new StartGameCommand(1, "Player1"));
}
}
```
### 2. [`AbstractCommand<TInput, TResult>`](./command.md)
### AbstractCommandWithResult`<TResult>`
带返回值命令的抽象基类,同样继承自 [ContextAwareBase](./rule.md)。
带返回值命令的抽象基类,同样继承自 ContextAwareBase。
**核心方法:**
```csharp
TResult ICommand<TResult>.Execute(); // 实现 ICommand<TResult> 接口
protected abstract TResult OnExecute(); // 抽象执行方法,由子类实现
```
**使用示例:**
```csharp
// 定义一个计算伤害的命令输入
public struct CalculateDamageCommandInput : ICommandInput
{
public int AttackerAttackPower { get; set; }
public int DefenderDefense { get; set; }
}
// 定义一个计算伤害的命令
public class CalculateDamageCommand : AbstractCommand<CalculateDamageCommandInput, int>
public class CalculateDamageCommand : AbstractCommandWithResult<int>
{
public CalculateDamageCommand(CalculateDamageCommandInput input) : base(input)
private readonly int _attackerAttackPower;
private readonly int _defenderDefense;
public CalculateDamageCommand(int attackerAttackPower, int defenderDefense)
{
_attackerAttackPower = attackerAttackPower;
_defenderDefense = defenderDefense;
}
protected override int OnExecute(CalculateDamageCommandInput input)
protected override int OnExecute()
{
// 获取游戏配置
var config = this.GetModel<GameConfigModel>();
// 计算最终伤害
var baseDamage = input.AttackerAttackPower - input.DefenderDefense;
var baseDamage = _attackerAttackPower - _defenderDefense;
var finalDamage = Math.Max(1, baseDamage * config.DamageMultiplier);
return (int)finalDamage;
@ -125,11 +132,10 @@ public class CombatSystem : AbstractSystem
public void Attack(Character attacker, Character defender)
{
// 发送命令并获取返回值
var damage = this.SendCommand(new CalculateDamageCommand(new CalculateDamageCommandInput
{
AttackerAttackPower = attacker.AttackPower,
DefenderDefense = defender.Defense
}));
var damage = this.SendCommand(new CalculateDamageCommand(
attacker.AttackPower,
defender.Defense
));
// 应用伤害
defender.Health -= damage;
@ -147,6 +153,12 @@ public class CombatSystem : AbstractSystem
3. **返回结果**:对于带返回值的命令,返回执行结果
4. **命令销毁**:命令执行完毕后可以被垃圾回收
**注意事项:**
- 命令应该是无状态的,执行完即可丢弃
- 避免在命令中保存长期引用
- 命令执行应该是原子操作
## CommandBus - 命令总线
### 功能说明
@ -156,64 +168,122 @@ public class CombatSystem : AbstractSystem
**主要方法:**
```csharp
void Send(ICommand command); // 发送无返回值命令
void Send(ICommand command); // 发送无返回值命令
TResult Send<TResult>(ICommand<TResult> command); // 发送带返回值命令
```
**特点:**
- 统一的命令执行入口
- 支持同步命令执行
- 与架构上下文集成
### 使用示例
```csharp
var commandBus = new CommandBus();
// 通过架构获取命令总线
var commandBus = architecture.Context.CommandBus;
// 发送无返回值命令
commandBus.Send(new StartGameCommand(new StartGameCommandInput()));
commandBus.Send(new StartGameCommand(1, "Player1"));
// 发送带返回值命令
var result = commandBus.Send(new CalculateDamageCommand(new CalculateDamageCommandInput()));
var damage = commandBus.Send(new CalculateDamageCommand(100, 50));
```
## EmptyCommandInput - 空命令输入
## 命令基类变体
当命令不需要输入参数时,可以使用 `EmptyCommandInput` 类:
框架提供了多种命令基类以满足不同需求:
### AbstractCommand
最基础的无返回值命令类
### AbstractCommandWithResult`<TResult>`
带返回值的命令基类
### AbstractCommandWithInput`<TInput>`
带输入参数的无返回值命令类
### AbstractCommandWithInputAndResult`<TInput, TResult>`
既带输入参数又带返回值的命令类
### AbstractAsyncCommand
支持异步执行的命令基类
### AbstractAsyncCommandWithResult`<TResult>`
支持异步执行的带返回值命令基类
```csharp
public class SimpleActionCommand : AbstractCommand<EmptyCommandInput>
// 异步命令示例
public class LoadSaveDataCommand : AbstractAsyncCommandWithResult<SaveData>
{
public SimpleActionCommand(EmptyCommandInput input) : base(input)
private readonly string _saveSlot;
public LoadSaveDataCommand(string saveSlot)
{
_saveSlot = saveSlot;
}
protected override void OnExecute(EmptyCommandInput input)
protected override async Task<SaveData> OnExecuteAsync()
{
// 执行简单操作,无需额外参数
this.SendEvent(new SimpleActionEvent());
var storage = this.GetUtility<IStorageUtility>();
return await storage.LoadSaveDataAsync(_saveSlot);
}
}
```
## 命令处理器执行
所有发送给命令总线的命令最终都会通过 `CommandExecutor` 来执行:
```csharp
public class CommandExecutor
{
public static void Execute(ICommand command)
{
command.Execute();
}
public static TResult Execute<TResult>(ICommand<TResult> command)
{
return command.Execute();
}
}
```
**特点:**
- 提供统一的命令执行机制
- 支持同步和异步命令执行
- 可以扩展添加中间件逻辑
## 使用场景
### 1. 用户交互操作
```csharp
public struct SaveGameCommandInput : ICommandInput
public class SaveGameCommand : AbstractCommand
{
public string SaveSlot { get; set; }
}
private readonly string _saveSlot;
public class SaveGameCommand : AbstractCommand<SaveGameCommandInput>
{
public SaveGameCommand(SaveGameCommandInput input) : base(input)
public SaveGameCommand(string saveSlot)
{
_saveSlot = saveSlot;
}
protected override void OnExecute(SaveGameCommandInput input)
protected override void OnExecute()
{
var saveSystem = this.GetSystem<SaveSystem>();
var playerModel = this.GetModel<PlayerModel>();
saveSystem.SavePlayerData(playerModel, input.SaveSlot);
this.SendEvent(new GameSavedEvent(input.SaveSlot));
saveSystem.SavePlayerData(playerModel, _saveSlot);
this.SendEvent(new GameSavedEvent(_saveSlot));
}
}
```
@ -221,18 +291,16 @@ public class SaveGameCommand : AbstractCommand<SaveGameCommandInput>
### 2. 业务流程控制
```csharp
public struct LoadLevelCommandInput : ICommandInput
public class LoadLevelCommand : AbstractCommand
{
public int LevelId { get; set; }
}
private readonly int _levelId;
public class LoadLevelCommand : AbstractCommand<LoadLevelCommandInput>
{
public LoadLevelCommand(LoadLevelCommandInput input) : base(input)
public LoadLevelCommand(int levelId)
{
_levelId = levelId;
}
protected override void OnExecute(LoadLevelCommandInput input)
protected override void OnExecute()
{
var levelSystem = this.GetSystem<LevelSystem>();
var uiSystem = this.GetSystem<UISystem>();
@ -241,10 +309,10 @@ public class LoadLevelCommand : AbstractCommand<LoadLevelCommandInput>
uiSystem.ShowLoadingScreen();
// 加载关卡
levelSystem.LoadLevel(input.LevelId);
levelSystem.LoadLevel(_levelId);
// 发送事件
this.SendEvent(new LevelLoadedEvent(input.LevelId));
this.SendEvent(new LevelLoadedEvent(_levelId));
}
}
```
@ -255,45 +323,32 @@ public class LoadLevelCommand : AbstractCommand<LoadLevelCommandInput>
2. **命令无状态**:命令不应该保存长期状态,执行完即可丢弃
3. **参数通过构造函数传递**:命令需要的参数应在创建时传入
4. **避免命令嵌套**:命令内部尽量不要发送其他命令,使用事件通信
5. **合理使用返回值**:只在确实需要返回结果时使用 `AbstractCommand<TInput, TResult>`
5. **合理使用返回值**:只在确实需要返回结果时使用带返回值的命令
6. **命令命名规范**:使用动词+名词形式,如 `StartGameCommand``SavePlayerCommand`
7. **输入参数结构化**:使用 `ICommandInput` 接口的实现类来组织命令参数
7. **单一职责原则**:每个命令只负责一个特定的业务操作
8. **使用异步命令**:对于需要长时间执行的操作,使用异步命令避免阻塞
9. **命令验证**:在命令执行前验证输入参数的有效性
10. **错误处理**:在命令中适当处理异常情况
## 扩展功能
## 命令模式优势
### 命令撤销/重做(可扩展)
### 1. 可扩展性
```csharp
public struct MoveCommandInput : ICommandInput
{
public Vector3 NewPosition { get; set; }
}
- 命令可以被序列化和存储
- 支持命令队列和批处理
- 便于实现撤销/重做功能
// 实现可撤销命令
public class MoveCommand : AbstractCommand<MoveCommandInput>, IUndoableCommand
{
private Vector3 _oldPosition;
private Vector3 _newPosition;
public MoveCommand(MoveCommandInput input) : base(input)
{
_newPosition = input.NewPosition;
}
### 2. 可测试性
protected override void OnExecute(MoveCommandInput input)
{
var player = this.GetModel<PlayerModel>();
_oldPosition = player.Position;
player.Position = input.NewPosition;
}
public void Undo()
{
var player = this.GetModel<PlayerModel>();
player.Position = _oldPosition;
}
}
```
- 命令逻辑独立,易于单元测试
- 可以模拟命令执行结果
- 支持行为驱动开发
### 3. 可维护性
- 业务逻辑集中管理
- 降低组件间耦合度
- 便于重构和扩展
## 相关包
@ -306,4 +361,4 @@ public class MoveCommand : AbstractCommand<MoveCommandInput>, IUndoableCommand
---
**许可证**: Apache 2.0
**许可证**Apache 2.0

View File

@ -4,9 +4,11 @@
Events 包提供了一套完整的事件系统实现了观察者模式Observer Pattern。通过事件系统可以实现组件间的松耦合通信支持无参和带参事件、事件注册/注销、以及灵活的事件组合。
事件系统是 GFramework 架构中组件间通信的核心机制,与命令模式和查询模式共同构成了完整的 CQRS 架构。
## 核心接口
### 1. IEvent
### IEvent
基础事件接口,定义了事件注册的基本功能。
@ -16,7 +18,7 @@ Events 包提供了一套完整的事件系统实现了观察者模式Obse
IUnRegister Register(Action onEvent); // 注册事件处理函数
```
### 2. IUnRegister
### IUnRegister
注销接口,用于取消事件注册。
@ -26,7 +28,7 @@ IUnRegister Register(Action onEvent); // 注册事件处理函数
void UnRegister(); // 执行注销操作
```
### 3. IUnRegisterList
### IUnRegisterList
注销列表接口,用于批量管理注销对象。
@ -36,7 +38,7 @@ void UnRegister(); // 执行注销操作
IList<IUnRegister> UnregisterList { get; } // 获取注销列表
```
### 4. IEventBus
### IEventBus
事件总线接口,提供基于类型的事件发送和注册。
@ -46,14 +48,22 @@ IList<IUnRegister> UnregisterList { get; } // 获取注销列表
IUnRegister Register<T>(Action<T> onEvent); // 注册类型化事件
void Send<T>(T e); // 发送事件实例
void Send<T>() where T : new(); // 发送事件(自动创建实例)
void UnRegister<T>(Action<T> onEvent); // 注销事件监听器
```
## 核心类
### 1. EasyEvent
### EasyEvent
无参事件类,支持注册、注销和触发无参事件。
**核心方法:**
```csharp
IUnRegister Register(Action onEvent); // 注册事件监听器
void Trigger(); // 触发事件
```
**使用示例:**
```csharp
@ -63,7 +73,7 @@ var onClicked = new EasyEvent();
// 注册监听
var unregister = onClicked.Register(() =>
{
GD.Print("Button clicked!");
Console.WriteLine("Button clicked!");
});
// 触发事件
@ -73,10 +83,17 @@ onClicked.Trigger();
unregister.UnRegister();
```
### 2. Event`<T>`
### Event`<T>`
单参数泛型事件类,支持一个参数的事件。
**核心方法:**
```csharp
IUnRegister Register(Action<T> onEvent); // 注册事件监听器
void Trigger(T eventData); // 触发事件并传递参数
```
**使用示例:**
```csharp
@ -86,17 +103,24 @@ var onScoreChanged = new Event<int>();
// 注册监听
onScoreChanged.Register(newScore =>
{
GD.Print($"Score changed to: {newScore}");
Console.WriteLine($"Score changed to: {newScore}");
});
// 触发事件并传递参数
onScoreChanged.Trigger(100);
```
### 3. Event<T, TK>
### Event<T, TK>
双参数泛型事件类。
**核心方法:**
```csharp
IUnRegister Register(Action<T, TK> onEvent); // 注册事件监听器
void Trigger(T param1, TK param2); // 触发事件并传递两个参数
```
**使用示例:**
```csharp
@ -105,16 +129,24 @@ var onDamageDealt = new Event<string, int>();
onDamageDealt.Register((attacker, damage) =>
{
GD.Print($"{attacker} dealt {damage} damage!");
Console.WriteLine($"{attacker} dealt {damage} damage!");
});
onDamageDealt.Trigger("Player", 50);
```
### 4. `EasyEvents`
### EasyEvents
全局事件管理器,提供类型安全的事件注册和获取。
**核心方法:**
```csharp
static void Register<T>() where T : IEvent, new(); // 注册事件类型
static T Get<T>() where T : IEvent, new(); // 获取事件实例
static T GetOrAddEvent<T>() where T : IEvent, new(); // 获取或创建事件实例
```
**使用示例:**
```csharp
@ -127,16 +159,25 @@ var gameStartEvent = EasyEvents.Get<GameStartEvent>();
// 注册监听
gameStartEvent.Register(() =>
{
GD.Print("Game started!");
Console.WriteLine("Game started!");
});
// 触发事件
gameStartEvent.Trigger();
```
### 5. `EventBus`
### EventBus
类型化事件系统,支持基于类型的事件发送和注册。
类型化事件系统,支持基于类型的事件发送和注册。这是架构中默认的事件总线实现。
**核心方法:**
```csharp
IUnRegister Register<T>(Action<T> onEvent); // 注册类型化事件
void Send<T>(T e); // 发送事件实例
void Send<T>() where T : new(); // 发送事件(自动创建实例)
void UnRegister<T>(Action<T> onEvent); // 注销事件监听器
```
**使用示例:**
@ -147,7 +188,7 @@ var eventBus = new EventBus();
// 注册类型化事件
eventBus.Register<PlayerDiedEvent>(e =>
{
GD.Print($"Player died at position: {e.Position}");
Console.WriteLine($"Player died at position: {e.Position}");
});
// 发送事件(传递实例)
@ -158,26 +199,35 @@ eventBus.Send(new PlayerDiedEvent
// 发送事件(自动创建实例)
eventBus.Send<PlayerDiedEvent>();
// 注销事件监听器
eventBus.UnRegister<PlayerDiedEvent>(OnPlayerDied);
```
### 6. `DefaultUnRegister`
### DefaultUnRegister
默认注销器实现,封装注销回调。
**使用示例:**
```csharp
Action onUnregister = () => GD.Print("Unregistered");
Action onUnregister = () => Console.WriteLine("Unregistered");
var unregister = new DefaultUnRegister(onUnregister);
// 执行注销
unregister.UnRegister();
```
### 7. `OrEvent`
### OrEvent
事件或运算组合器,当任意一个事件触发时触发。
**核心方法:**
```csharp
OrEvent Or(IEvent @event); // 添加要组合的事件
```
**使用示例:**
```csharp
@ -189,14 +239,21 @@ var onAnyInput = new OrEvent()
// 当上述任意事件触发时,执行回调
onAnyInput.Register(() =>
{
GD.Print("Input detected!");
Console.WriteLine("Input detected!");
});
```
### 8. `UnRegisterList`
### UnRegisterList
批量管理注销对象的列表。
**核心方法:**
```csharp
void Add(IUnRegister unRegister); // 添加注销器到列表
void UnRegisterAll(); // 批量注销所有事件
```
**使用示例:**
```csharp
@ -209,7 +266,7 @@ someEvent.Register(OnEvent).AddToUnregisterList(unregisterList);
unregisterList.UnRegisterAll();
```
### 9. `ArchitectureEvents`
### ArchitectureEvents
定义了架构生命周期相关的事件。
@ -317,18 +374,18 @@ public partial class GameController : Node, IController
private void OnGameStarted(GameStartedEvent e)
{
GD.Print("Game started!");
Console.WriteLine("Game started!");
}
private void OnPlayerDied(PlayerDiedEvent e)
{
GD.Print($"Player died at {e.Position}: {e.Cause}");
Console.WriteLine($"Player died at {e.Position}: {e.Cause}");
ShowGameOverScreen();
}
private void OnLevelCompleted(LevelCompletedEvent e)
{
GD.Print($"Level {e.LevelId} completed! Score: {e.Score}");
Console.WriteLine($"Level {e.LevelId} completed! Score: {e.Score}");
ShowVictoryScreen(e);
}
@ -503,6 +560,11 @@ public override void _Ready()
- 事件处理器保持轻量
- 使用结构体事件减少 GC
7. **事件设计原则**
- 高内聚:事件应该代表一个完整的业务概念
- 低耦合:事件发送者不需要知道接收者
- 可测试:事件应该易于模拟和测试
## 事件 vs 其他通信方式
| 方式 | 适用场景 | 优点 | 缺点 |
@ -512,6 +574,23 @@ public override void _Ready()
| **Query** | 查询数据 | 职责清晰、有返回值 | 同步调用 |
| **BindableProperty** | UI 数据绑定 | 自动更新、响应式 | 仅限单一属性 |
## 事件系统架构
事件系统在 GFramework 中的架构位置:
```
Architecture (架构核心)
├── EventBus (事件总线)
├── CommandBus (命令总线)
├── QueryBus (查询总线)
└── IocContainer (IoC容器)
Components (组件)
├── Model (发送事件)
├── System (发送/接收事件)
└── Controller (接收事件)
```
## 相关包
- [`architecture`](./architecture.md) - 提供全局事件系统
@ -519,4 +598,6 @@ public override void _Ready()
- [`property`](./property.md) - 可绑定属性基于事件实现
- [`controller`](./controller.md) - 控制器监听事件
- [`model`](./model.md) - 模型发送事件
- [`system`](./system.md) - 系统发送和监听事件
- [`system`](./system.md) - 系统发送和监听事件
- [`command`](./command.md) - 与事件配合实现 CQRS
- [`query`](./query.md) - 与事件配合实现 CQRS

View File

@ -5,12 +5,20 @@
IoCInversion of Control控制反转包提供了一个轻量级的依赖注入容器用于管理框架中各种组件的注册和获取。通过 IoC
容器,可以实现组件间的解耦,便于测试和维护。
IoC 容器是 GFramework 架构的核心组件之一,为整个框架提供依赖管理和组件解析服务。
## 核心类
### `IocContainer`
### IocContainer
IoC 容器类,负责管理对象的注册和获取。
**继承关系:**
```csharp
public class IocContainer : ContextAwareBase, IIocContainer
```
**主要功能:**
- 注册实例到容器
@ -18,13 +26,16 @@ IoC 容器类,负责管理对象的注册和获取。
- 类型安全的依赖管理
- 线程安全操作
- 容器冻结保护
- 多实例注册支持
## 核心方法
### 1. Register`<T>` 和 Register(Type, object)
### Register`<T>` 和 Register(Type, object)
注册一个实例到容器中。
**方法签名:**
```csharp
public void Register<T>(T instance)
public void Register(Type type, object instance)
@ -35,6 +46,13 @@ public void Register(Type type, object instance)
- `instance`: 要注册的实例对象
- `type`: 要注册的类型(重载方法)
**特点:**
- 支持泛型和非泛型注册
- 自动注册到实例的所有接口类型
- 线程安全操作
- 容器冻结后抛出异常
**使用示例:**
```csharp
@ -44,12 +62,17 @@ var container = new IocContainer();
container.Register<IPlayerModel>(new PlayerModel());
container.Register<IGameSystem>(new GameSystem());
container.Register<IStorageUtility>(new StorageUtility());
// 非泛型注册
container.Register(typeof(ICustomService), new CustomService());
```
### 2. RegisterSingleton`<T>`
### RegisterSingleton`<T>`
注册单例实例到容器中。一个类型只允许一个实例。
**方法签名:**
```csharp
public void RegisterSingleton<T>(T instance)
```
@ -58,6 +81,12 @@ public void RegisterSingleton<T>(T instance)
- `instance`: 要注册的单例实例
**特点:**
- 严格单例约束:同一类型只能注册一个实例
- 重复注册会抛出 `InvalidOperationException`
- 适用于全局服务和配置对象
**使用示例:**
```csharp
@ -65,12 +94,18 @@ var container = new IocContainer();
// 注册单例
container.RegisterSingleton<IPlayerModel>(new PlayerModel());
container.RegisterSingleton<IGameConfiguration>(new GameConfiguration());
// 以下会抛出异常(重复注册)
// container.RegisterSingleton<IPlayerModel>(new AnotherPlayerModel());
```
### 3. RegisterPlurality
### RegisterPlurality
注册多个实例,将实例注册到其实现的所有接口和具体类型上。
**方法签名:**
```csharp
public void RegisterPlurality(object instance)
```
@ -79,10 +114,34 @@ public void RegisterPlurality(object instance)
- `instance`: 要注册的实例
### 4. RegisterSystem
**特点:**
- 自动注册到所有实现的接口
- 同时注册具体类型
- 支持多态注册
- 常用于 System 和 Utility 注册
**使用示例:**
```csharp
var container = new IocContainer();
// 假设 GameSystem 实现了 IGameSystem 和 IUpdateable 接口
var gameSystem = new GameSystem();
container.RegisterPlurality(gameSystem);
// 现在可以通过任一接口获取
var system1 = container.Get<IGameSystem>(); // 返回 gameSystem
var system2 = container.Get<IUpdateable>(); // 返回 gameSystem
var system3 = container.Get<GameSystem>(); // 返回 gameSystem
```
### RegisterSystem
注册系统实例,将其绑定到其所有实现的接口上。
**方法签名:**
```csharp
public void RegisterSystem(ISystem system)
```
@ -91,10 +150,29 @@ public void RegisterSystem(ISystem system)
- `system`: 系统实例对象
### 5. Get`<T>` 和 GetAll`<T>`
**特点:**
- 专门为 System 设计的注册方法
- 内部调用 `RegisterPlurality`
- 提供语义化的 API
**使用示例:**
```csharp
var container = new IocContainer();
// 注册系统
container.RegisterSystem(new CombatSystem());
container.RegisterSystem(new InventorySystem());
container.RegisterSystem(new QuestSystem());
```
### Get`<T>` 和 GetAll`<T>`
从容器中获取指定类型的实例。
**方法签名:**
```csharp
public T? Get<T>() where T : class
public IReadOnlyList<T> GetAll<T>() where T : class
@ -105,6 +183,12 @@ public IReadOnlyList<T> GetAll<T>() where T : class
- `Get<T>`: 返回指定类型的实例,如果未找到则返回 `null`
- `GetAll<T>`: 返回指定类型的所有实例列表,如果未找到则返回空数组
**特点:**
- 线程安全读取
- 支持接口和具体类型查询
- 返回值快照(线程安全)
**使用示例:**
```csharp
@ -114,12 +198,15 @@ var gameSystems = container.GetAll<IGameSystem>();
// 如果类型未注册Get 返回 nullGetAll 返回空数组
var unknownService = container.Get<IUnknownService>(); // null
var allUnknownServices = container.GetAll<IUnknownService>(); // 空数组
```
### 6. GetRequired`<T>`
### GetRequired`<T>`
获取指定类型的必需实例,如果没有注册或注册了多个实例会抛出异常。
**方法签名:**
```csharp
public T GetRequired<T>() where T : class
```
@ -128,10 +215,30 @@ public T GetRequired<T>() where T : class
- 返回找到的唯一实例
### 7. GetAllSorted`<T>`
**特点:**
- 严格模式获取
- 无实例时抛出 `InvalidOperationException`
- 多实例时抛出 `InvalidOperationException`
- 适用于必需的单例依赖
**使用示例:**
```csharp
// 获取必需的服务
var config = container.GetRequired<IGameConfiguration>(); // 必须存在且唯一
// 以下情况会抛出异常:
// 1. IGameConfiguration 未注册
// 2. IGameConfiguration 注册了多个实例
```
### GetAllSorted`<T>`
获取并排序(系统调度专用)。
**方法签名:**
```csharp
public IReadOnlyList<T> GetAllSorted<T>(Comparison<T> comparison) where T : class
```
@ -144,24 +251,71 @@ public IReadOnlyList<T> GetAllSorted<T>(Comparison<T> comparison) where T : clas
- 按指定方式排序后的实例列表
**特点:**
- 支持自定义排序
- 适用于需要按优先级执行的场景
- 常用于 System 更新顺序控制
**使用示例:**
```csharp
// 按优先级排序系统
var sortedSystems = container.GetAllSorted<ISystem>((a, b) =>
{
var priorityA = GetSystemPriority(a);
var priorityB = GetSystemPriority(b);
return priorityA.CompareTo(priorityB);
});
```
## IoC 容器架构
```
Architecture (架构层)
├── IocContainer (IoC容器)
│ ├── Register<T>() // 泛型注册
│ ├── Register(Type, obj) // 非泛型注册
│ ├── RegisterSingleton<T>() // 单例注册
│ ├── RegisterPlurality() // 多态注册
│ ├── RegisterSystem() // 系统注册
│ ├── Get<T>() // 获取实例
│ ├── GetAll<T>() // 获取所有实例
│ ├── GetRequired<T>() // 获取必需实例
│ ├── GetAllSorted<T>() // 排序获取
│ ├── Contains<T>() // 检查存在性
│ ├── ContainsInstance() // 检查实例
│ ├── Clear() // 清空容器
│ └── Freeze() // 冻结容器
├── Components (组件)
│ ├── Model (数据模型)
│ ├── System (业务系统)
│ └── Utility (工具类)
└── Context (上下文)
└── 提供容器访问
```
## 在框架中的使用
### Architecture 中的应用
IoC 容器是 [`Architecture`](./architecture.md) 类的核心组件,用于管理所有的 System、Model 和 Utility。
```
```csharp
public abstract class Architecture : IArchitecture
{
// 内置 IoC 容器
private readonly IocContainer _mContainer = new();
// 注册系统
public void RegisterSystem<TSystem>(TSystem system) where TSystem : ISystem
public TSystem RegisterSystem<TSystem>(TSystem system) where TSystem : ISystem
{
system.SetArchitecture(this);
_mContainer.Register(system); // 注册到容器
// ...
system.SetContext(Context);
_mContainer.RegisterPlurality(system); // 注册到容器
RegisterLifecycleComponent(system); // 处理生命周期
return system;
}
// 获取系统
@ -169,6 +323,16 @@ public abstract class Architecture : IArchitecture
=> _mContainer.Get<TSystem>(); // 从容器获取
// Model 和 Utility 同理
public TModel RegisterModel<TModel>(TModel model) where TModel : IModel
{
model.SetContext(Context);
_mContainer.RegisterPlurality(model);
RegisterLifecycleComponent(model);
return model;
}
public TModel GetModel<TModel>() where TModel : class, IModel
=> _mContainer.Get<TModel>();
}
```

View File

@ -4,9 +4,11 @@
Property 包提供了可绑定属性BindableProperty的实现支持属性值的监听和响应式编程。这是实现数据绑定和响应式编程的核心组件。
BindableProperty 是 GFramework 中 Model 层数据管理的基础,通过事件机制实现属性变化的通知。
## 核心接口
### [`IReadonlyBindableProperty<T>`](./property.md)
### IReadonlyBindableProperty`<T>`
只读可绑定属性接口,提供属性值的读取和变更监听功能。
@ -26,7 +28,7 @@ IUnRegister RegisterWithInitValue(Action<T> action);
void UnRegister(Action<T> onValueChanged);
```
### [`IBindableProperty<T>`](./property.md)
### IBindableProperty`<T>`
可绑定属性接口,继承自只读接口,增加了修改能力。
@ -42,10 +44,33 @@ void SetValueWithoutEvent(T newValue);
## 核心类
### [`BindableProperty<T>`](./property.md)
### BindableProperty`<T>`
可绑定属性的完整实现。
**核心方法:**
```csharp
// 构造函数
BindableProperty(T defaultValue = default!);
// 属性值
T Value { get; set; }
// 注册监听
IUnRegister Register(Action<T> onValueChanged);
IUnRegister RegisterWithInitValue(Action<T> action);
// 取消监听
void UnRegister(Action<T> onValueChanged);
// 设置值但不触发事件
void SetValueWithoutEvent(T newValue);
// 设置自定义比较器
BindableProperty<T> WithComparer(Func<T, T, bool> comparer);
```
**使用示例:**
```csharp
@ -55,7 +80,7 @@ var health = new BindableProperty<int>(100);
// 监听值变化(不会立即触发)
var unregister = health.Register(newValue =>
{
GD.Print($"Health changed to: {newValue}");
Console.WriteLine($"Health changed to: {newValue}");
});
// 设置值(会触发监听器)
@ -74,7 +99,7 @@ health.SetValueWithoutEvent(75);
// 1. 注册并立即获得当前值
health.RegisterWithInitValue(value =>
{
GD.Print($"Current health: {value}"); // 立即输出当前值
Console.WriteLine($"Current health: {value}"); // 立即输出当前值
// 后续值变化时也会调用
});
@ -84,9 +109,13 @@ BindableProperty<int>.Comparer = (a, b) => Math.Abs(a - b) < 1;
// 3. 使用实例方法设置比较器
var position = new BindableProperty<Vector3>(Vector3.Zero)
.WithComparer((a, b) => a.DistanceTo(b) < 0.01f); // 距离小于0.01认为相等
// 4. 字符串比较器示例
var name = new BindableProperty<string>("Player")
.WithComparer((a, b) => string.Equals(a, b, StringComparison.OrdinalIgnoreCase));
```
### [`BindablePropertyUnRegister<T>`](./property.md)
### BindablePropertyUnRegister`<T>`
可绑定属性的注销器,负责清理监听。
@ -98,6 +127,15 @@ var unregister = health.Register(OnHealthChanged);
unregister.UnRegister();
```
## BindableProperty 工作原理
BindableProperty 基于事件系统实现属性变化通知:
1. **值设置**:当设置 `Value` 属性时,首先进行值比较
2. **变化检测**:使用 `EqualityComparer<T>.Default` 或自定义比较器检测值变化
3. **事件触发**:如果值发生变化,调用所有注册的回调函数
4. **内存管理**:通过 `IUnRegister` 机制管理监听器的生命周期
## 在 Model 中使用
### 定义可绑定属性
@ -110,6 +148,7 @@ public class PlayerModel : AbstractModel
public BindableProperty<int> Level { get; } = new(1);
public BindableProperty<int> Health { get; } = new(100);
public BindableProperty<int> MaxHealth { get; } = new(100);
public BindableProperty<Vector3> Position { get; } = new(Vector3.Zero);
// 只读属性(外部只能读取和监听)
public IReadonlyBindableProperty<int> ReadonlyHealth => Health;
@ -123,7 +162,33 @@ public class PlayerModel : AbstractModel
{
this.SendEvent(new PlayerDiedEvent());
}
else if (hp < MaxHealth.Value * 0.3f)
{
this.SendEvent(new LowHealthWarningEvent());
}
});
// 监听等级变化
Level.Register(newLevel =>
{
this.SendEvent(new PlayerLevelUpEvent { NewLevel = newLevel });
});
}
// 业务方法
public void TakeDamage(int damage)
{
Health.Value = Math.Max(0, Health.Value - damage);
}
public void Heal(int amount)
{
Health.Value = Math.Min(MaxHealth.Value, Health.Value + amount);
}
public float GetHealthPercentage()
{
return (float)Health.Value / MaxHealth.Value;
}
}
```
@ -137,6 +202,7 @@ public partial class PlayerUI : Control, IController
{
[Export] private Label _healthLabel;
[Export] private Label _nameLabel;
[Export] private ProgressBar _healthBar;
private IUnRegisterList _unregisterList = new UnRegisterList();
@ -151,6 +217,15 @@ public partial class PlayerUI : Control, IController
.RegisterWithInitValue(health =>
{
_healthLabel.Text = $"HP: {health}/{playerModel.MaxHealth.Value}";
_healthBar.Value = (float)health / playerModel.MaxHealth.Value * 100;
})
.AddToUnregisterList(_unregisterList);
// 绑定最大生命值
playerModel.MaxHealth
.RegisterWithInitValue(maxHealth =>
{
_healthBar.MaxValue = maxHealth;
})
.AddToUnregisterList(_unregisterList);
@ -161,6 +236,17 @@ public partial class PlayerUI : Control, IController
_nameLabel.Text = name;
})
.AddToUnregisterList(_unregisterList);
// 绑定位置(仅用于调试显示)
playerModel.Position
.RegisterWithInitValue(pos =>
{
// 仅在调试模式下显示
#if DEBUG
Console.WriteLine($"Player position: {pos}");
#endif
})
.AddToUnregisterList(_unregisterList);
}
public override void _ExitTree()

View File

@ -5,6 +5,8 @@
Query 包实现了 CQRS命令查询职责分离模式中的查询部分。Query 用于封装数据查询逻辑,与 Command 不同的是Query
有返回值且不应该修改系统状态。
查询系统是 GFramework CQRS 架构的重要组成部分,专门负责数据读取操作,与命令系统和事件系统协同工作。
## 核心接口
### IQuery`<TResult>`
@ -19,22 +21,28 @@ TResult Do(); // 执行查询并返回结果
## 核心类
### [`AbstractQuery<TInput, TResult>`](./query.md)
### AbstractQuery`<TResult>`
抽象查询基类,提供了查询的基础实现。它接受一个泛型输入参数 TInput该参数必须实现 IQueryInput 接口。
抽象查询基类,提供了查询的基础实现。
**核心方法:**
```csharp
TResult IQuery<TResult>.Do(); // 实现 IQuery 接口
protected abstract TResult OnDo(); // 抽象查询方法,由子类实现
```
**使用方式:**
```csharp
public abstract class AbstractQuery<TInput, TResult>(TInput input) : ContextAwareBase, IQuery<TResult>
where TInput : IQueryInput
public abstract class AbstractQuery<TResult> : ContextAwareBase, IQuery<TResult>
{
public TResult Do() => OnDo(input); // 执行查询,传入输入参数
protected abstract TResult OnDo(TInput input); // 子类实现查询逻辑
public TResult Do() => OnDo(); // 执行查询
protected abstract TResult OnDo(); // 子类实现查询逻辑
}
```
### [`EmptyQueryInput`](./query.md)
### EmptyQueryInput
空查询输入类,用于表示不需要任何输入参数的查询操作。
@ -47,10 +55,16 @@ public sealed class EmptyQueryInput : IQueryInput
}
```
### [`QueryBus`](./query.md)
### QueryBus
查询总线实现,负责执行查询并返回结果。
**核心方法:**
```csharp
TResult Send<TResult>(IQuery<TResult> query); // 发送并执行查询
```
**使用方式:**
```csharp
@ -69,56 +83,41 @@ public sealed class QueryBus : IQueryBus
### 1. 定义查询
```csharp
// 定义查询输入参数
public record GetPlayerGoldQueryInput : IQueryInput;
// 查询玩家金币数量
public class GetPlayerGoldQuery : AbstractQuery<GetPlayerGoldQueryInput, int>
public class GetPlayerGoldQuery : AbstractQuery<int>
{
public GetPlayerGoldQuery() : base(new EmptyQueryInput())
{
}
protected override int OnDo(GetPlayerGoldQueryInput input)
protected override int OnDo()
{
return this.GetModel<PlayerModel>().Gold.Value;
}
}
// 查询玩家是否死亡
public record IsPlayerDeadQueryInput : IQueryInput;
public class IsPlayerDeadQuery : AbstractQuery<IsPlayerDeadQueryInput, bool>
public class IsPlayerDeadQuery : AbstractQuery<bool>
{
public IsPlayerDeadQuery() : base(new EmptyQueryInput())
{
}
protected override bool OnDo(IsPlayerDeadQueryInput input)
protected override bool OnDo()
{
return this.GetModel<PlayerModel>().Health.Value <= 0;
}
}
// 查询背包中指定物品的数量
public record GetItemCountQueryInput(string ItemId) : IQueryInput;
public class GetItemCountQuery : AbstractQuery<GetItemCountQueryInput, int>
public class GetItemCountQuery : AbstractQuery<int>
{
public GetItemCountQuery(string itemId) : base(new GetItemCountQueryInput(itemId))
{
}
protected override int OnDo(GetItemCountQueryInput input)
private readonly string _itemId;
public GetItemCountQuery(string itemId)
{
var inventory = this.GetModel<InventoryModel>();
return inventory.GetItemCount(input.ItemId);
_itemId = itemId;
}
protected override int OnDo()
{
var inventory = this.GetModel<InventoryModel>();
return inventory.GetItemCount(_itemId);
}
}
```
```
### 2. 发送查询
@ -144,11 +143,11 @@ public partial class ShopUI : Control, IController
if (playerGold >= _itemPrice)
{
// 发送购买命令
this.SendCommand(new BuyItemCommand { ItemId = "sword_01" });
this.SendCommand(new BuyItemCommand("sword_01"));
}
else
{
GD.Print("金币不足!");
Console.WriteLine("金币不足!");
}
}
}
@ -159,11 +158,11 @@ public partial class ShopUI : Control, IController
```csharp
public class CombatSystem : AbstractSystem
{
protected override void OnInit()
{
// 注册事件监听
this.RegisterEvent<EnemyAttackEvent>(OnEnemyAttack);
}
protected override void OnInit()
{
// 注册事件监听
this.RegisterEvent<EnemyAttackEvent>(OnEnemyAttack);
}
private void OnEnemyAttack(EnemyAttackEvent e)
{
@ -173,12 +172,11 @@ this.RegisterEvent<EnemyAttackEvent>(OnEnemyAttack);
if (!isDead)
{
// 执行伤害逻辑
this.SendCommand(new TakeDamageCommand { Damage = e.Damage });
this.SendCommand(new TakeDamageCommand(e.Damage));
}
}
}
```
```
## 高级用法
@ -189,22 +187,24 @@ this.RegisterEvent<EnemyAttackEvent>(OnEnemyAttack);
// 查询指定范围内的敌人列表
public class GetEnemiesInRangeQuery : AbstractQuery<List<Enemy>>
{
public Vector3 Center { get; set; }
public float Radius { get; set; }
private readonly Vector3 _center;
private readonly float _radius;
public GetEnemiesInRangeQuery(Vector3 center, float radius)
{
_center = center;
_radius = radius;
}
protected override List<Enemy> OnDo()
{
var enemySystem = this.GetSystem<EnemySpawnSystem>();
return enemySystem.GetEnemiesInRange(Center, Radius);
return enemySystem.GetEnemiesInRange(_center, _radius);
}
}
// 使用
var enemies = this.SendQuery(new GetEnemiesInRangeQuery
{
Center = playerPosition,
Radius = 10.0f
});
var enemies = this.SendQuery(new GetEnemiesInRangeQuery(playerPosition, 10.0f));
```
### 2. 组合查询
@ -213,7 +213,12 @@ var enemies = this.SendQuery(new GetEnemiesInRangeQuery
// 查询玩家是否可以使用技能
public class CanUseSkillQuery : AbstractQuery<bool>
{
public string SkillId { get; set; }
private readonly string _skillId;
public CanUseSkillQuery(string skillId)
{
_skillId = skillId;
}
protected override bool OnDo()
{
@ -221,37 +226,44 @@ public string SkillId { get; set; }
var skillModel = this.GetModel<SkillModel>();
// 查询技能消耗
var skillCost = this.SendQuery(new GetSkillCostQuery { SkillId = SkillId });
var skillCost = this.SendQuery(new GetSkillCostQuery(_skillId));
// 检查是否满足条件
return playerModel.Mana.Value >= skillCost.ManaCost
&& !this.SendQuery(new IsSkillOnCooldownQuery { SkillId = SkillId });
&& !this.SendQuery(new IsSkillOnCooldownQuery(_skillId));
}
}
public class GetSkillCostQuery : AbstractQuery<SkillCost>
{
public string SkillId { get; set; }
private readonly string _skillId;
public GetSkillCostQuery(string skillId)
{
_skillId = skillId;
}
protected override SkillCost OnDo()
{
return this.GetModel<SkillModel>().GetSkillCost(SkillId);
return this.GetModel<SkillModel>().GetSkillCost(_skillId);
}
}
public class IsSkillOnCooldownQuery : AbstractQuery<bool>
{
public string SkillId { get; set; }
private readonly string _skillId;
public IsSkillOnCooldownQuery(string skillId)
{
_skillId = skillId;
}
protected override bool OnDo()
{
return this.GetModel<SkillModel>().IsOnCooldown(SkillId);
return this.GetModel<SkillModel>().IsOnCooldown(_skillId);
}
}
```
```
### 3. 聚合数据查询
@ -332,18 +344,38 @@ protected override void OnInit() { }
```
## Query 的执行机制
所有发送给查询总线的查询最终都会通过 `QueryExecutor` 来执行:
```csharp
public class QueryExecutor
{
public static TResult Execute<TResult>(IQuery<TResult> query)
{
return query.Do();
}
}
```
**特点:**
- 提供统一的查询执行机制
- 支持同步查询执行
- 与架构上下文集成
## Command vs Query
### Command命令
- **用途**:修改系统状态
- **返回值**无返回值void
- **返回值**无返回值void或有返回值
- **示例**:购买物品、造成伤害、升级角色
### Query查询
- **用途**:读取数据,不修改状态
- **返回值**:有返回值
- **返回值**必须有返回值
- **示例**:获取金币数量、检查技能冷却、查询玩家位置
```csharp
@ -370,12 +402,17 @@ public class GoodQuery : AbstractQuery<int>
// ✅ 修改数据应该使用 Command
public class AddGoldCommand : AbstractCommand
{
public int Amount { get; set; }
private readonly int _amount;
public AddGoldCommand(int amount)
{
_amount = amount;
}
protected override void OnExecute()
{
var model = this.GetModel<PlayerModel>();
model.Gold.Value += Amount;
model.Gold.Value += _amount;
}
}
```
@ -387,6 +424,9 @@ public class AddGoldCommand : AbstractCommand
3. **可组合** - 复杂查询可以通过组合简单查询实现
4. **避免过度查询** - 如果需要频繁查询,考虑使用 BindableProperty
5. **命名清晰** - Query 名称应该清楚表达查询意图Get、Is、Can、Has等前缀
6. **参数通过构造函数传递** - 查询需要的参数应在创建时传入
7. **查询无状态** - 查询不应该保存长期状态,执行完即可丢弃
8. **合理使用缓存** - 对于复杂计算,可以在 Model 中缓存结果
## 性能优化
@ -396,7 +436,7 @@ public class AddGoldCommand : AbstractCommand
// 在 Model 中缓存复杂计算
public class PlayerModel : AbstractModel
{
private int? _cachedPower;
private int? _cachedPower;
public int GetPower()
{
@ -417,9 +457,8 @@ private int? _cachedPower;
{
_cachedPower = null;
}
}
```
```
### 2. 批量查询
@ -428,20 +467,46 @@ private int? _cachedPower;
// 一次查询多个数据,而不是多次单独查询
public class GetMultipleItemCountsQuery : AbstractQuery<Dictionary<string, int>>
{
public List<string> ItemIds { get; set; }
private readonly List<string> _itemIds;
public GetMultipleItemCountsQuery(List<string> itemIds)
{
_itemIds = itemIds;
}
protected override Dictionary<string, int> OnDo()
{
var inventory = this.GetModel<InventoryModel>();
return ItemIds.ToDictionary(id => id, id => inventory.GetItemCount(id));
return _itemIds.ToDictionary(id => id, id => inventory.GetItemCount(id));
}
}
```
## 查询模式优势
### 1. 职责分离
- 读写操作明确分离
- 便于优化读写性能
- 降低系统复杂度
### 2. 可扩展性
- 读写可以独立扩展
- 支持不同的数据存储策略
- 便于实现读写分离
### 3. 可维护性
- 查询逻辑集中管理
- 便于重构和优化
- 降低组件间耦合
## 相关包
- [`command`](./command.md) - CQRS 的命令部分
- [`model`](./model.md) - Query 主要从 Model 获取数据
- [`system`](./system.md) - System 中可以发送 Query
- [`controller`](./controller.md) - Controller 中可以发送 Query
- [`extensions`](./extensions.md) - 提供 SendQuery 扩展方法
- [`extensions`](./extensions.md) - 提供 SendQuery 扩展方法
- [`architecture`](./architecture.md) - 架构核心,负责查询的分发和执行

View File

@ -4,9 +4,11 @@
System 包定义了业务逻辑层Business Logic Layer。System 负责处理游戏的核心业务逻辑,协调 Model 之间的交互,响应事件并执行复杂的业务流程。
System 是 GFramework 架构中业务逻辑的核心组件,通过事件系统与 Model 和 Controller 进行通信。
## 核心接口
### [`ICanGetSystem`](./system.md)
### ICanGetSystem
标记接口,表示该类型可以获取其他 System。
@ -16,7 +18,7 @@ System 包定义了业务逻辑层Business Logic Layer。System 负责处
public interface ICanGetSystem : IBelongToArchitecture
```
### [`ISystem`](./system.md)
### ISystem
System 接口,定义了系统的基本行为。
@ -30,7 +32,10 @@ void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件
**继承的能力:**
- `ICanSetArchitecture` - 可设置架构
- `IContextAware` - 上下文感知
- `IInitializable` - 可初始化
- `IDisposable` - 可销毁
- `IArchitecturePhaseAware` - 架构阶段感知
- `ICanGetModel` - 可获取 Model
- `ICanGetUtility` - 可获取 Utility
- `ICanGetSystem` - 可获取其他 System
@ -39,33 +44,57 @@ void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件
## 核心类
### [`AbstractSystem`](./system.md)
### AbstractSystem
抽象 System 基类,提供了 System 的基础实现。继承自 ContextAwareBase具有上下文感知能力。
**核心方法:**
```csharp
void IInitializable.Init(); // 实现初始化接口
void IDisposable.Destroy(); // 实现销毁接口
protected abstract void OnInit(); // 抽象初始化方法,由子类实现
protected virtual void OnDestroy(); // 虚拟销毁方法,子类可重写
public virtual void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件
```
**使用方式:**
```csharp
public abstract class AbstractSystem : ContextAwareBase, ISystem
{
void ISystem.Init() => OnInit(); // 系统初始化,内部调用抽象方法 OnInit()
void ISystem.Destroy() => OnDestroy(); // 系统销毁,内部调用 OnDestroy()
protected abstract void OnInit(); // 子类实现初始化逻辑
protected virtual void OnDestroy(); // 子类可选择重写销毁逻辑
void IInitializable.Init() => OnInit(); // 系统初始化,内部调用抽象方法 OnInit()
void IDisposable.Destroy() => OnDestroy(); // 系统销毁,内部调用 OnDestroy()
protected abstract void OnInit(); // 子类实现初始化逻辑
protected virtual void OnDestroy(); // 子类可选择重写销毁逻辑
public virtual void OnArchitecturePhase(ArchitecturePhase phase); // 处理架构阶段事件
}
```
## System 生命周期
System 的生命周期由架构管理:
1. **注册阶段**:通过 `architecture.RegisterSystem()` 注册到架构
2. **初始化阶段**:架构调用 `Init()` 方法,内部执行 `OnInit()`
3. **运行阶段**:处理事件和执行业务逻辑
4. **销毁阶段**:架构调用 `Destroy()` 方法,内部执行 `OnDestroy()`
## 基本使用
### 1. 定义 System
```
```csharp
// 战斗系统
public class CombatSystem : AbstractSystem
{
private ILogger _logger;
protected override void OnInit()
{
_logger = this.GetUtility<ILogger>();
_logger.Info("CombatSystem initialized");
// 注册事件监听
this.RegisterEvent<EnemyAttackEvent>(OnEnemyAttack);
this.RegisterEvent<PlayerAttackEvent>(OnPlayerAttack);
@ -83,6 +112,8 @@ public class CombatSystem : AbstractSystem
// 发送伤害事件
this.SendEvent(new PlayerTookDamageEvent { Damage = damage });
_logger.Debug($"Player took {damage} damage from enemy attack");
}
private void OnPlayerAttack(PlayerAttackEvent e)
@ -98,6 +129,8 @@ public class CombatSystem : AbstractSystem
EnemyId = e.Enemy.Id,
Damage = damage
});
_logger.Debug($"Enemy {e.Enemy.Id} took {damage} damage from player attack");
}
private int CalculateDamage(int attackPower, int defense)
@ -107,34 +140,50 @@ public class CombatSystem : AbstractSystem
protected override void OnDestroy()
{
_logger.Info("CombatSystem destroyed");
// 清理资源
base.OnDestroy();
}
public override void OnArchitecturePhase(ArchitecturePhase phase)
{
switch (phase)
{
case ArchitecturePhase.AfterSystemInit:
_logger.Info("CombatSystem is ready");
break;
case ArchitecturePhase.Destroying:
_logger.Info("CombatSystem is shutting down");
break;
}
}
}
```
### 2. 注册 System
```
```csharp
public class GameArchitecture : Architecture
{
protected override void Init()
{
// 注册 Model
this.RegisterModel<PlayerModel>(new PlayerModel());
this.RegisterModel<EnemyModel>(new EnemyModel());
this.RegisterModel(new PlayerModel());
this.RegisterModel(new EnemyModel());
this.RegisterModel(new InventoryModel());
// 注册 System系统注册后会自动调用 Init
this.RegisterSystem<CombatSystem>(new CombatSystem());
this.RegisterSystem<InventorySystem>(new InventorySystem());
this.RegisterSystem<QuestSystem>(new QuestSystem());
this.RegisterSystem(new CombatSystem());
this.RegisterSystem(new InventorySystem());
this.RegisterSystem(new QuestSystem());
this.RegisterSystem(new UISystem());
}
}
```
### 3. 在其他组件中获取 System
```
```csharp
// 在 Controller 中
public partial class GameController : Node, IController
{
@ -145,6 +194,9 @@ public partial class GameController : Node, IController
// 获取 System
var combatSystem = this.GetSystem<CombatSystem>();
var questSystem = this.GetSystem<QuestSystem>();
// 使用 System
combatSystem.StartBattle();
}
}
@ -154,7 +206,17 @@ public class StartBattleCommand : AbstractCommand
protected override void OnExecute()
{
var combatSystem = this.GetSystem<CombatSystem>();
// 使用 System...
combatSystem.StartBattle();
}
}
// 在其他 System 中
public class AISystem : AbstractSystem
{
protected override void OnInit()
{
var combatSystem = this.GetSystem<CombatSystem>();
// 与其他 System 协作
}
}
```