refactor(scope): 收束架构作用域容器边界
This commit is contained in:
578
docs/requirements/scope-feature-architecture-requirements.md
Normal file
578
docs/requirements/scope-feature-architecture-requirements.md
Normal file
@@ -0,0 +1,578 @@
|
||||
# FlowScope Scope / Feature 架构需求草案
|
||||
|
||||
## 目标
|
||||
|
||||
这份文档用于约束下一版 FlowScope 核心架构实现。目标不是复刻 QFramework,也不是照搬 ft_3 当前容器用法,而是形成一套能服务真实 Unity 项目的可落地框架:
|
||||
|
||||
- 能力向 ft_3 靠拢:支持动态注册、父子 Scope、局部覆盖、运行期灵活组合。
|
||||
- 规则向 QFramework 靠拢:业务代码默认在当前架构上下文中工作,不鼓励直接拿根容器乱 Resolve。
|
||||
- 默认使用简单:大多数业务只需要 `Root -> Feature` 两级。
|
||||
- 保留扩展能力:复杂业务可以继续创建子 Scope,但不把每个 UI、流程、函数调用都强制变成 Scope。
|
||||
|
||||
## 核心结论
|
||||
|
||||
下一版的第一主角应该是统一的 `ArchitectureScope`,不是固定的 `RootContext + FeatureScope` 两层结构。
|
||||
|
||||
`RootScope`、`FeatureScope`、`ChildScope` 本质都是同一种 Scope,只是所在层级和安装内容不同:
|
||||
|
||||
```text
|
||||
Root Scope
|
||||
BossFightFeature Scope
|
||||
ScratchTicketFeature Scope
|
||||
```
|
||||
|
||||
必要时可以继续细分:
|
||||
|
||||
```text
|
||||
Root Scope
|
||||
BossFightFeature Scope
|
||||
BattleRound Scope
|
||||
RevivePanel Scope
|
||||
```
|
||||
|
||||
但这不是默认推荐路径。默认推荐是每个活动、玩法、业务模块对应一个 Feature Scope,Feature 内部再按 MVC、MVVM、MVP、Controller、ViewModel、Service、State Machine 等方式组织。
|
||||
|
||||
## 概念定义
|
||||
|
||||
### ArchitectureScope
|
||||
|
||||
`ArchitectureScope` 是框架的核心上下文,也是受规则约束的容器节点。
|
||||
|
||||
它负责:
|
||||
|
||||
- 保存当前 Scope 的注册项。
|
||||
- 从当前 Scope 开始解析依赖。
|
||||
- 找不到时向父 Scope 查找。
|
||||
- 创建子 Scope。
|
||||
- 创建 Feature Scope。
|
||||
- 管理当前 Scope 生命周期。
|
||||
- 允许当前 Scope 覆盖父 Scope 的能力。
|
||||
|
||||
它不应该只是一个裸 DI Container,而是一个带层级、生命周期和使用规则的运行时上下文。
|
||||
|
||||
### RootScope
|
||||
|
||||
`RootScope` 是没有父节点的 `ArchitectureScope`。
|
||||
|
||||
它不应该拥有一套和普通 Scope 不同的能力。它只是树的根:
|
||||
|
||||
```text
|
||||
RootScope = Parent == null 的 ArchitectureScope
|
||||
```
|
||||
|
||||
Root 适合注册跨 Feature 的基础能力,例如:
|
||||
|
||||
- 配置服务
|
||||
- 资源服务
|
||||
- 存档服务
|
||||
- 音频服务
|
||||
- 网络服务
|
||||
- 全局账号/环境信息
|
||||
|
||||
Root 不应该成为业务代码到处直接访问的全局服务定位器。
|
||||
|
||||
### Feature
|
||||
|
||||
`Feature` 是一个业务模块的安装定义,不是另一种容器。
|
||||
|
||||
一个 Feature 代表一个可独立理解的业务边界,例如:
|
||||
|
||||
- BossFightFeature
|
||||
- ScratchTicketFeature
|
||||
- Event1v1BattleFeature
|
||||
- MainMenuFeature
|
||||
|
||||
创建 Feature 时,框架会自动创建一个新的 `ArchitectureScope`,然后把 Feature 安装到这个 Scope 上。
|
||||
|
||||
推荐 API:
|
||||
|
||||
```csharp
|
||||
var boss = game.CreateFeature<BossFightFeature>();
|
||||
```
|
||||
|
||||
等价概念:
|
||||
|
||||
```csharp
|
||||
var boss = game.CreateScope("BossFight");
|
||||
new BossFightFeature().Install(boss);
|
||||
```
|
||||
|
||||
但调用侧不应该手写安装流程。
|
||||
|
||||
### Child Scope
|
||||
|
||||
Child Scope 是 Feature 内部或任意 Scope 下的局部生命周期工具。
|
||||
|
||||
它适合处理:
|
||||
|
||||
- 生命周期明显短于 Feature 的对象。
|
||||
- 需要局部覆盖某个服务的场景。
|
||||
- 一组临时对象需要一起释放的场景。
|
||||
- 同一个 Feature 内存在多个并行实例的场景。
|
||||
- 特殊流程需要隔离依赖解析的场景。
|
||||
|
||||
它不适合处理:
|
||||
|
||||
- 普通 UI Panel。
|
||||
- 普通 ViewModel。
|
||||
- 普通 Controller。
|
||||
- 普通业务函数调用。
|
||||
- 只是为了“看起来更分层”的拆分。
|
||||
|
||||
默认原则:能放在 Feature 内部 MVC/MVVM 结构里的,不要先拆成 Scope。
|
||||
|
||||
## Register / Resolve 规则
|
||||
|
||||
这版设计不应该限制只能在安装期 Register。
|
||||
|
||||
更准确的规则是:
|
||||
|
||||
```text
|
||||
Register 本身不是问题。
|
||||
问题是越级注册、越级修改、污染父级、绕开当前 Scope 解析。
|
||||
```
|
||||
|
||||
因此下一版需要遵守:
|
||||
|
||||
1. 任意 Scope 都可以随时 Register。
|
||||
2. Register 永远只写当前 Scope。
|
||||
3. Resolve 永远从当前 Scope 开始。
|
||||
4. 当前 Scope 找不到时,才向 Parent 查找。
|
||||
6. 子 Scope 可以覆盖父 Scope 的注册。
|
||||
6. 子 Scope 不能修改父 Scope 的注册。
|
||||
7. Root 只是普通 Scope 的根节点,不是特殊全局服务定位器。
|
||||
8. Feature 是创建出来并自动安装的 Scope。
|
||||
|
||||
示例:
|
||||
|
||||
```csharp
|
||||
var game = FlowScope.Create();
|
||||
|
||||
game.Register<IConfigService, ConfigService>().AsSingleton();
|
||||
|
||||
var boss = game.CreateFeature<BossFightFeature>();
|
||||
boss.Register<IRewardService, BossRewardService>().PerScope();
|
||||
|
||||
using var revive = boss.CreateScope("RevivePanel");
|
||||
revive.Register<IRewardService, ReviveRewardService>().PerScope();
|
||||
|
||||
var reward = revive.Resolve<IRewardService>();
|
||||
```
|
||||
|
||||
解析顺序:
|
||||
|
||||
```text
|
||||
RevivePanel Scope -> BossFight Scope -> Root Scope
|
||||
```
|
||||
|
||||
此时 `revive.Register` 不会污染 `boss`,`boss.Register` 不会污染 `root`。
|
||||
|
||||
|
||||
## 容器策略
|
||||
|
||||
下一版不应该只包装 ft_3 的 `asap.core.Container`,而应该吸收它的核心设计,形成 FlowScope 自己的 Container。
|
||||
|
||||
已通过 `asap.core.dll` 元数据确认,ft_3 当前底层容器具备这些值得保留的设计:
|
||||
|
||||
- `(Type, string)` 作为注册 key。
|
||||
- `Register<T>().AsSingleton()` 的声明式生命周期 API。
|
||||
- `Register<TService, TImplementation>().AsSingleton()` 的接口到实现注册。
|
||||
- `Register<T>(name).PerScope()` 的命名注册。
|
||||
- `Register(Type serviceType, string name, Type implementationType).PerScope()` 的运行时类型注册。
|
||||
- `RegisterInstance(instance)` 的现有实例注册。
|
||||
- `Resolve<T>()` / `Container.IScope.GetInstance(...)` 的解析入口。
|
||||
- `CreateScope()` 的 Scope 生命周期入口。
|
||||
- `PerScope()` 通过 Scope lifetime 在具体 Scope 解析时生成/缓存实例。
|
||||
|
||||
同时也确认了 ft_3 当前公开接口的限制:
|
||||
|
||||
- `Container.IScope` 公开接口只有 `GetInstance((Type, string))`。
|
||||
- `Container.IScope` 没有公开 `Parent` 属性。
|
||||
- `Container.IScope` 没有公开 `Register` 方法。
|
||||
- 底层注册入口主要在根 `Container` 上,不是每个 Scope 都能注册到自己。
|
||||
- 底层只确认有 `AsSingleton()` 和 `PerScope()`,没有确认 `AsTransient()`。
|
||||
- 底层没有确认存在 `TryResolve<T>()`。
|
||||
|
||||
因此下一版不采用“薄包装 asap.core.Container”的方案。
|
||||
|
||||
原因是 FlowScope 当前目标和 ft_3 底层公开模型不完全一致:
|
||||
|
||||
```text
|
||||
ft_3 当前模型:Root Container 注册,Scope 负责按 Scope 生命周期解析/缓存。
|
||||
FlowScope 目标模型:每个 ArchitectureScope 都可以注册到自己,并从自己开始 Resolve,再向父级查找。
|
||||
```
|
||||
|
||||
如果只包装,会出现两套语义:外层说 Scope-local Register,内层却只有 Root Register。长期看会让 `ArchitectureScope` 变成补丁层,代码容易拧巴。
|
||||
|
||||
下一版应该选择:
|
||||
|
||||
```text
|
||||
借鉴 ft_3 Container 的算法和 API 手感
|
||||
但实现 FlowScope 自己的 Container / Scope Tree
|
||||
```
|
||||
|
||||
## FlowScope Container 目标模型
|
||||
|
||||
FlowScope 的容器节点应该同时承担三件事:
|
||||
|
||||
```text
|
||||
Container 负责能力
|
||||
ArchitectureScope 负责规则
|
||||
Feature 工厂负责业务模块创建
|
||||
```
|
||||
|
||||
但实现上不再拆成“外层 Scope 包内层 Container.IScope”,而是统一为 FlowScope 自己的 Scope 节点:
|
||||
|
||||
```text
|
||||
ContainerScope / ArchitectureScope
|
||||
Name
|
||||
Parent
|
||||
Children
|
||||
Dictionary<ServiceKey, Registration>
|
||||
CreateScope()
|
||||
CreateFeature<TFeature>()
|
||||
Register()
|
||||
Resolve()
|
||||
TryResolve()
|
||||
Dispose()
|
||||
```
|
||||
|
||||
其中 `ServiceKey` 继续沿用 ft_3 的核心思路:
|
||||
|
||||
```csharp
|
||||
public readonly struct ServiceKey
|
||||
{
|
||||
public Type Type { get; }
|
||||
public string Name { get; }
|
||||
}
|
||||
```
|
||||
|
||||
## 生命周期规则
|
||||
|
||||
下一版容器至少支持:
|
||||
|
||||
```text
|
||||
Singleton
|
||||
注册所在 Scope 持有一个实例。
|
||||
|
||||
PerScope
|
||||
每个 Resolve 发起 Scope 持有一个实例。
|
||||
|
||||
Transient
|
||||
每次 Resolve 都创建新实例。
|
||||
|
||||
Instance
|
||||
注册现有实例。是否 Dispose 需要显式规则,默认不销毁外部传入实例更安全。
|
||||
```
|
||||
|
||||
`PerScope` 是从 ft_3 里最值得保留的能力之一。它适合活动、Feature、局部流程这种“同一个注册,不同 Scope 下得到不同实例”的场景。
|
||||
|
||||
## Register / Resolve 目标规则
|
||||
|
||||
FlowScope 自己的 Container 需要直接支持当前讨论确定的规则:
|
||||
|
||||
1. 任意 Scope 都可以随时 Register。
|
||||
2. Register 永远只写当前 Scope。
|
||||
3. Resolve 永远从当前 Scope 开始。
|
||||
4. 当前 Scope 找不到时,才向 Parent 查找。
|
||||
5. 子 Scope 可以覆盖父 Scope 的注册。
|
||||
6. 子 Scope 不能修改父 Scope 的注册。
|
||||
7. Root 只是没有 Parent 的普通 Scope。
|
||||
8. Parent / Children 是 FlowScope 自己容器模型的一部分,不依赖底层外部容器。
|
||||
|
||||
示例:
|
||||
|
||||
```csharp
|
||||
var game = FlowScope.Create();
|
||||
|
||||
game.Register<IConfigService, ConfigService>().AsSingleton();
|
||||
|
||||
var boss = game.CreateFeature<BossFightFeature>();
|
||||
boss.Register<IRewardService, BossRewardService>().PerScope();
|
||||
|
||||
using var revive = boss.CreateScope("RevivePanel");
|
||||
revive.Register<IRewardService, ReviveRewardService>().PerScope();
|
||||
|
||||
var reward = revive.Resolve<IRewardService>();
|
||||
```
|
||||
|
||||
解析顺序:
|
||||
|
||||
```text
|
||||
RevivePanel Scope -> BossFightFeature Scope -> Root Scope
|
||||
```
|
||||
|
||||
`revive.Register` 不污染 `boss`,`boss.Register` 不污染 `root`。
|
||||
|
||||
## CreateFeature 规则
|
||||
|
||||
`CreateFeature<TFeature>()` 是 Scope 创建 + Feature 安装的语义封装。
|
||||
|
||||
调用者不传 name 时,默认使用 `typeof(TFeature).Name`:
|
||||
|
||||
```csharp
|
||||
var boss = game.CreateFeature<BossFightFeature>();
|
||||
// 等价于 game.CreateFeature<BossFightFeature>(typeof(BossFightFeature).Name)
|
||||
```
|
||||
|
||||
推荐 API:
|
||||
|
||||
```csharp
|
||||
IArchitectureScope CreateFeature<TFeature>()
|
||||
where TFeature : IFeature, new();
|
||||
|
||||
IArchitectureScope CreateFeature<TFeature>(string name)
|
||||
where TFeature : IFeature, new();
|
||||
```
|
||||
|
||||
第一版可以先使用 `where TFeature : IFeature, new()`,保持简单:
|
||||
|
||||
```csharp
|
||||
public IArchitectureScope CreateFeature<TFeature>(string name)
|
||||
where TFeature : IFeature, new()
|
||||
{
|
||||
var child = CreateScope(name);
|
||||
var feature = new TFeature();
|
||||
feature.Install(child);
|
||||
return child;
|
||||
}
|
||||
```
|
||||
|
||||
后续如果 `TFeature` 需要依赖注入构造,可以改为从当前 Scope 创建:
|
||||
|
||||
```csharp
|
||||
child.Register<TFeature>().PerScope();
|
||||
var feature = child.Resolve<TFeature>();
|
||||
feature.Install(child);
|
||||
```
|
||||
|
||||
调用侧不应该手写:
|
||||
|
||||
```csharp
|
||||
boss.InstallFeature(new BossFightFeature());
|
||||
boss.UseArchitecture(new BossFightArchitecture());
|
||||
```
|
||||
|
||||
Feature 的安装和 Scope 的创建应该被 `CreateFeature<TFeature>()` 收住。
|
||||
## Feature 默认边界
|
||||
|
||||
大多数业务默认只需要:
|
||||
|
||||
```text
|
||||
Root Scope
|
||||
Feature Scope
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```text
|
||||
Root Scope
|
||||
BossFightFeature Scope
|
||||
ScratchTicketFeature Scope
|
||||
```
|
||||
|
||||
BossFight 内部的战斗、复活、奖励、UI,不应该默认都拆成 Scope。它们可以优先放在 Feature 内部架构里:
|
||||
|
||||
```text
|
||||
BossFightFeature
|
||||
Model
|
||||
Service
|
||||
Controller
|
||||
ViewModel
|
||||
ViewBinder
|
||||
StateMachine
|
||||
```
|
||||
|
||||
只有当局部生命周期、依赖覆盖、并行实例、独立释放这些需求明确存在时,才创建 Child Scope。
|
||||
|
||||
## Feature 与 MVC / MVVM
|
||||
|
||||
`Feature` 只定义业务模块边界,不强制决定内部表现层架构。
|
||||
|
||||
Feature 内部可以选择:
|
||||
|
||||
- MVC
|
||||
- MVVM
|
||||
- MVP
|
||||
- Presenter
|
||||
- Flow Controller
|
||||
- State Machine
|
||||
- 简单 Service + View 组合
|
||||
|
||||
框架不应该强制所有 Feature 使用同一种 UI 或业务组织模式。
|
||||
|
||||
示例:
|
||||
|
||||
```csharp
|
||||
public sealed class BossFightFeature : IFeature
|
||||
{
|
||||
public void Install(IArchitectureScope scope)
|
||||
{
|
||||
scope.Register<BossFightModel>().PerScope();
|
||||
scope.Register<IBossFightService, BossFightService>().PerScope();
|
||||
|
||||
scope.UseMvvm<BossFightViewModel>();
|
||||
scope.UseMvc<BossFightController>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`UseMvvm`、`UseMvc` 这类 API 可以作为后续扩展模块,不一定属于第一版核心。
|
||||
|
||||
## 与 ft_3 Activity 的关系
|
||||
|
||||
Activity 不应该等同于大 Feature 内部的所有业务实现。
|
||||
|
||||
更合理的职责划分:
|
||||
|
||||
```text
|
||||
Activity
|
||||
负责活动开关、入口、可见性、预加载、跳转、生命周期接入。
|
||||
|
||||
Feature
|
||||
负责活动作为业务模块的运行边界。
|
||||
|
||||
ArchitectureScope
|
||||
负责该模块的依赖注册、解析、父子继承、局部覆盖和生命周期。
|
||||
|
||||
MVC / MVVM / State
|
||||
负责 Feature 内部具体业务和 UI 组织。
|
||||
```
|
||||
|
||||
也就是说,Activity 更像外部接入器,Feature 才是业务模块边界。
|
||||
|
||||
## 约束方式
|
||||
|
||||
约束重点不应该是禁止 Register,而是让代码默认只能操作当前 Scope。
|
||||
|
||||
建议通过接口和可见性控制:
|
||||
|
||||
```csharp
|
||||
public interface IArchitectureScope : IDisposable
|
||||
{
|
||||
string Name { get; }
|
||||
IScopeInfo Parent { get; }
|
||||
|
||||
IArchitectureScope CreateScope(string name);
|
||||
|
||||
IArchitectureScope CreateFeature<TFeature>()
|
||||
where TFeature : IFeature, new();
|
||||
IArchitectureScope CreateFeature<TFeature>(string name)
|
||||
where TFeature : IFeature, new();
|
||||
|
||||
IRegistrationBuilder Register<TImplementation>();
|
||||
IRegistrationBuilder Register<TService, TImplementation>()
|
||||
where TImplementation : TService;
|
||||
|
||||
T Resolve<T>();
|
||||
T Resolve<T>(string name);
|
||||
}
|
||||
```
|
||||
|
||||
`Parent` 可以只暴露只读元信息,避免业务代码通过 Parent 直接写父 Scope。
|
||||
|
||||
```csharp
|
||||
public interface IScopeInfo
|
||||
{
|
||||
string Name { get; }
|
||||
IScopeInfo Parent { get; }
|
||||
}
|
||||
```
|
||||
|
||||
如果确实需要高级能力,可以后续通过内部接口或显式高级 API 暴露,而不是默认给所有业务代码。
|
||||
|
||||
## 推荐最小核心 API
|
||||
|
||||
第一版可以先只实现:
|
||||
|
||||
```csharp
|
||||
public interface IFeature
|
||||
{
|
||||
void Install(IArchitectureScope scope);
|
||||
}
|
||||
|
||||
public interface IArchitectureScope : IDisposable
|
||||
{
|
||||
string Name { get; }
|
||||
IScopeInfo Parent { get; }
|
||||
|
||||
IArchitectureScope CreateScope(string name);
|
||||
IArchitectureScope CreateFeature<TFeature>()
|
||||
where TFeature : IFeature, new();
|
||||
IArchitectureScope CreateFeature<TFeature>(string name)
|
||||
where TFeature : IFeature, new();
|
||||
|
||||
IRegistrationBuilder Register<TImplementation>();
|
||||
IRegistrationBuilder Register<TImplementation>(string name);
|
||||
IRegistrationBuilder Register<TService, TImplementation>()
|
||||
where TImplementation : TService;
|
||||
IRegistrationBuilder Register<TService, TImplementation>(string name)
|
||||
where TImplementation : TService;
|
||||
|
||||
void RegisterInstance<T>(T instance);
|
||||
void RegisterInstance<T>(T instance, string name);
|
||||
|
||||
T Resolve<T>();
|
||||
T Resolve<T>(string name);
|
||||
bool TryResolve<T>(out T value);
|
||||
bool TryResolve<T>(out T value, string name);
|
||||
}
|
||||
```
|
||||
|
||||
工厂方法:
|
||||
|
||||
```csharp
|
||||
var game = FlowScope.Create();
|
||||
var boss = game.CreateFeature<BossFightFeature>();
|
||||
```
|
||||
|
||||
## 暂不进入的内容
|
||||
|
||||
以下能力暂时不作为下一版第一阶段重点:
|
||||
|
||||
- Command / Query / Event 通信模型。
|
||||
- 完整 MVC / MVVM 框架。
|
||||
- 自动扫描所有 Feature。
|
||||
- UI Panel 生命周期统一接管。
|
||||
- 复杂诊断面板。
|
||||
- 热更新资源接入细节。
|
||||
|
||||
原因是当前最核心的问题还在:
|
||||
|
||||
```text
|
||||
Scope / Feature / Container / Architecture 的第一层抽象需要先稳定。
|
||||
```
|
||||
|
||||
通信和表现层规则应该建立在稳定 Scope 模型之上。
|
||||
|
||||
## 验收标准
|
||||
|
||||
下一版实现完成后,应满足:
|
||||
|
||||
1. Root 和普通 Scope 使用同一种 `ArchitectureScope` 模型。
|
||||
2. `CreateFeature<TFeature>()` 默认使用 `typeof(TFeature).Name` 作为 Scope 名称。
|
||||
3. `CreateFeature<TFeature>(name)` 会自动创建子 Scope 并调用 `TFeature.Install(scope)`。
|
||||
4. 任意 Scope 可以随时 Register,且只能注册到自己。
|
||||
5. Resolve 从当前 Scope 开始,支持向父 Scope 查找。
|
||||
6. 子 Scope 可以覆盖父 Scope 注册。
|
||||
7. 子 Scope Dispose 不会释放父 Scope 对象。
|
||||
8. 父 Scope Dispose 会释放子 Scope。
|
||||
9. Feature Scope 默认就是主要业务边界。
|
||||
10. Child Scope 能用,但文档和测试不鼓励把普通 UI/VM/Controller 都拆成 Scope。
|
||||
11. 第一阶段不要求实现 Command / Query / Event。
|
||||
|
||||
## 当前设计取舍
|
||||
|
||||
这套设计选择了:
|
||||
|
||||
```text
|
||||
默认 Root -> Feature,复杂时再 Child Scope。
|
||||
```
|
||||
|
||||
而不是:
|
||||
|
||||
```text
|
||||
强制 Root -> Feature -> Flow -> UI -> Task。
|
||||
```
|
||||
|
||||
原因是 FlowScope 应该帮助真实项目收住边界,而不是替业务制造额外层级。框架应提供可递归的 Scope 能力,但默认心智模型必须保持简单。
|
||||
778
docs/specs/architecture-scope-container-implementation-spec.md
Normal file
778
docs/specs/architecture-scope-container-implementation-spec.md
Normal file
@@ -0,0 +1,778 @@
|
||||
# FlowScope ArchitectureScope / Container 实现规格
|
||||
|
||||
## 背景
|
||||
|
||||
本文基于 `docs/requirements/scope-feature-architecture-requirements.md`,用于约束下一版 FlowScope 核心架构实现。
|
||||
|
||||
本轮设计不再沿用当前 `RootContext + FeatureScope` 的试验模型,也不继续在 `IFeatureScope`、`ICommandContext`、`IQueryContext` 上叠加规则。新方向是吸收 ft_3 Container 的核心能力和 API 手感,自研 FlowScope Container / Scope Tree。
|
||||
|
||||
第一阶段只解决一件事:
|
||||
|
||||
```text
|
||||
用统一的 ArchitectureScope 建立 Root -> Feature -> Child Scope 的容器树、注册规则、解析规则和生命周期语义。
|
||||
```
|
||||
|
||||
暂时不进入:
|
||||
|
||||
- Command / Query / Event。
|
||||
- 完整 MVC / MVVM。
|
||||
- UI Panel 生命周期统一接管。
|
||||
- 自动扫描所有 Feature。
|
||||
- 复杂诊断面板。
|
||||
|
||||
## 设计目标
|
||||
|
||||
1. Root、Feature、Child Scope 都是同一种 `ArchitectureScope`。
|
||||
2. Root 只是 `Parent == null` 的 Scope,不拥有另一套接口模型。
|
||||
3. Feature 是业务模块安装定义,不是另一种容器。
|
||||
4. `CreateFeature<TFeature>()` 默认使用 `typeof(TFeature).Name` 创建 Scope,并自动调用 `Feature.Install(scope)`。
|
||||
5. 任意 Scope 可以随时 `Register`,但只能写入自己。
|
||||
6. `Resolve` 从当前 Scope 开始,找不到再向父 Scope 查找。
|
||||
7. 子 Scope 可以覆盖父 Scope 的注册。
|
||||
8. 默认业务边界是 `Root -> Feature`,Child Scope 只作为复杂生命周期能力保留。
|
||||
9. 实现必须避免“外层说 Scope-local Register,内层只有 Root Register”的双重语义。
|
||||
|
||||
## 核心类型清单
|
||||
|
||||
### FlowScope
|
||||
|
||||
静态入口类型,负责创建根 Scope。
|
||||
|
||||
建议职责:
|
||||
|
||||
- `Create()` 创建一个没有 Parent 的 `ArchitectureScope`。
|
||||
- 不持有全局单例。
|
||||
- 不提供静态 `Resolve<T>()`。
|
||||
|
||||
建议 API:
|
||||
|
||||
```csharp
|
||||
public static class FlowScope
|
||||
{
|
||||
public static IArchitectureScope Create();
|
||||
public static IArchitectureScope Create(string rootName);
|
||||
}
|
||||
```
|
||||
|
||||
### ArchitectureScope
|
||||
|
||||
统一 Scope 节点的内部实现类型。
|
||||
|
||||
建议职责:
|
||||
|
||||
- 保存 Scope 名称、父节点、子节点。
|
||||
- 保存当前 Scope 的注册表。
|
||||
- 保存当前 Scope 持有的实例和释放栈。
|
||||
- 执行 Register / Resolve / CreateScope / CreateFeature / Dispose。
|
||||
- 对外通过 `IArchitectureScope` 暴露。
|
||||
|
||||
Root、Feature、Child Scope 不应分别实现不同运行时类型。它们的差异来自创建位置和安装内容,而不是来自继承层级。
|
||||
|
||||
### IArchitectureScope
|
||||
|
||||
业务侧默认接触的 Scope 接口。
|
||||
|
||||
它既是容器访问入口,也是规则约束入口。
|
||||
|
||||
```csharp
|
||||
public interface IArchitectureScope : IScopeInfo, IDisposable
|
||||
{
|
||||
IArchitectureScope CreateScope(string name);
|
||||
|
||||
IArchitectureScope CreateFeature<TFeature>()
|
||||
where TFeature : IFeature, new();
|
||||
|
||||
IArchitectureScope CreateFeature<TFeature>(string name)
|
||||
where TFeature : IFeature, new();
|
||||
|
||||
IRegistrationBuilder Register<TImplementation>();
|
||||
IRegistrationBuilder Register<TImplementation>(string name);
|
||||
|
||||
IRegistrationBuilder Register<TService, TImplementation>()
|
||||
where TImplementation : TService;
|
||||
|
||||
IRegistrationBuilder Register<TService, TImplementation>(string name)
|
||||
where TImplementation : TService;
|
||||
|
||||
void RegisterInstance<TService>(TService instance);
|
||||
void RegisterInstance<TService>(TService instance, string name);
|
||||
|
||||
TService Resolve<TService>();
|
||||
TService Resolve<TService>(string name);
|
||||
|
||||
bool TryResolve<TService>(out TService value);
|
||||
bool TryResolve<TService>(out TService value, string name);
|
||||
}
|
||||
```
|
||||
|
||||
### IScopeInfo
|
||||
|
||||
只读 Scope 元信息,避免业务代码通过 Parent 写父级。
|
||||
|
||||
```csharp
|
||||
public interface IScopeInfo
|
||||
{
|
||||
string Name { get; }
|
||||
IScopeInfo Parent { get; }
|
||||
string Path { get; }
|
||||
bool IsDisposed { get; }
|
||||
}
|
||||
```
|
||||
|
||||
`Parent` 第一阶段建议只暴露为 `IScopeInfo`,而不是 `IArchitectureScope`。如果后续需要高级管理能力,应通过单独的内部接口或显式管理 API 暴露。
|
||||
|
||||
### IFeature
|
||||
|
||||
Feature 是业务模块安装定义。
|
||||
|
||||
```csharp
|
||||
public interface IFeature
|
||||
{
|
||||
void Install(IArchitectureScope scope);
|
||||
}
|
||||
```
|
||||
|
||||
第一阶段不要求 `IFeature : IDisposable`。Feature 自身不是生命周期容器,Feature 相关对象的生命周期应由安装后的 Scope 管理。
|
||||
|
||||
如果某个 Feature 安装对象确实需要释放,应把它注册进 Scope,或由它在 `Install` 中注册需要释放的服务。
|
||||
|
||||
### ServiceKey
|
||||
|
||||
注册和解析使用的键。
|
||||
|
||||
```csharp
|
||||
public readonly struct ServiceKey : IEquatable<ServiceKey>
|
||||
{
|
||||
public Type Type { get; }
|
||||
public string Name { get; }
|
||||
}
|
||||
```
|
||||
|
||||
语义:
|
||||
|
||||
- `Type` 必须非空。
|
||||
- `Name` 可以为空或空字符串,但内部应归一化为同一种无名状态。
|
||||
- `(Type, Name)` 完全相同才视为同一个注册。
|
||||
|
||||
### Registration
|
||||
|
||||
注册记录。
|
||||
|
||||
建议字段:
|
||||
|
||||
```csharp
|
||||
internal sealed class Registration
|
||||
{
|
||||
public ServiceKey Key { get; }
|
||||
public Type ImplementationType { get; }
|
||||
public object Instance { get; }
|
||||
public ServiceLifetime Lifetime { get; }
|
||||
public bool OwnsInstance { get; }
|
||||
}
|
||||
```
|
||||
|
||||
`Registration` 只描述“如何创建”,不应把所有 PerScope 缓存都放在注册所在 Scope 上。
|
||||
|
||||
### ServiceLifetime
|
||||
|
||||
第一阶段支持四种生命周期。
|
||||
|
||||
```csharp
|
||||
public enum ServiceLifetime
|
||||
{
|
||||
Singleton,
|
||||
PerScope,
|
||||
Transient,
|
||||
Instance
|
||||
}
|
||||
```
|
||||
|
||||
### IRegistrationBuilder
|
||||
|
||||
声明生命周期的构建器。
|
||||
|
||||
```csharp
|
||||
public interface IRegistrationBuilder
|
||||
{
|
||||
void AsSingleton();
|
||||
void PerScope();
|
||||
void AsTransient();
|
||||
}
|
||||
```
|
||||
|
||||
第一阶段建议每次 `Register` 返回一个待完成 builder,只有调用生命周期方法后才写入注册表;或者默认 `PerScope`,但必须在 spec 与测试中固定。为了避免隐式行为,推荐第一阶段要求显式调用生命周期方法。
|
||||
|
||||
## 接口设计
|
||||
|
||||
第一阶段接口应围绕“统一 Scope 节点”组织,而不是围绕 Root / Feature 两种上下文组织。
|
||||
|
||||
公开接口分三层:
|
||||
|
||||
```text
|
||||
FlowScope
|
||||
创建 Root Scope。
|
||||
|
||||
IArchitectureScope
|
||||
默认业务接口,负责创建子 Scope、创建 Feature、注册、解析和释放。
|
||||
|
||||
IScopeInfo
|
||||
只读诊断接口,暴露 Name / Parent / Path / IsDisposed。
|
||||
```
|
||||
|
||||
Feature 接口只负责安装:
|
||||
|
||||
```text
|
||||
IFeature.Install(IArchitectureScope scope)
|
||||
```
|
||||
|
||||
不在第一阶段公开:
|
||||
|
||||
- `IRootContext`
|
||||
- `IFeatureScope`
|
||||
- `ICommandContext`
|
||||
- `IQueryContext`
|
||||
- `IEventContext`
|
||||
|
||||
原因是这些接口会把核心重新拉回“Root 特殊、Feature 特殊、通信模型提前进入核心”的旧方向。第一阶段所有业务和测试都应以 `IArchitectureScope` 为主入口。
|
||||
|
||||
## 容器生命周期语义
|
||||
|
||||
### Singleton
|
||||
|
||||
`Singleton` 表示注册所在 Scope 持有唯一实例。
|
||||
|
||||
解析规则:
|
||||
|
||||
- 在某个 Scope 找到 Singleton 注册后,实例缓存到该注册所在 Scope。
|
||||
- 子 Scope Resolve 到父 Scope 的 Singleton 注册时,拿到父 Scope 的同一个实例。
|
||||
- 子 Scope Dispose 不释放父 Scope Singleton。
|
||||
- 注册所在 Scope Dispose 时释放该 Singleton 实例。
|
||||
|
||||
适用:
|
||||
|
||||
- Root 注册全局服务。
|
||||
- Feature 注册该 Feature 内唯一共享服务。
|
||||
|
||||
### PerScope
|
||||
|
||||
`PerScope` 表示每个发起 Resolve 的 Scope 持有一个实例。
|
||||
|
||||
解析规则:
|
||||
|
||||
- 从当前 Scope 开始查找注册。
|
||||
- 找到注册后,实例缓存到“发起 Resolve 的 Scope”,不是注册所在 Scope。
|
||||
- 同一个发起 Scope 多次 Resolve 返回同一个实例。
|
||||
- 不同子 Scope Resolve 同一个父级 PerScope 注册时,各自得到不同实例。
|
||||
- 发起 Scope Dispose 时释放自己缓存的 PerScope 实例。
|
||||
|
||||
这是吸收 ft_3 Container 后最重要的生命周期能力,用于“同一个注册规则,在不同 Scope 下得到不同运行实例”。
|
||||
|
||||
### Transient
|
||||
|
||||
`Transient` 表示每次 Resolve 都创建新实例。
|
||||
|
||||
解析规则:
|
||||
|
||||
- 不缓存。
|
||||
- 如果创建出的实例实现 `IDisposable`,第一阶段建议由发起 Scope 跟踪并在 Dispose 时释放。
|
||||
- 如果不跟踪 Transient Disposable,必须在接口文档里明确调用方负责释放。为了减少 Unity 项目泄漏风险,推荐跟踪释放。
|
||||
|
||||
### Instance
|
||||
|
||||
`Instance` 表示注册外部传入实例。
|
||||
|
||||
解析规则:
|
||||
|
||||
- 每次 Resolve 返回同一个传入对象。
|
||||
- 默认 `OwnsInstance = false`,Scope Dispose 不释放外部传入实例。
|
||||
- 如后续需要 Scope 接管实例释放,可以增加显式 API,例如 `RegisterOwnedInstance<T>()`,第一阶段不做。
|
||||
|
||||
### Dispose 顺序
|
||||
|
||||
Scope Dispose 必须满足:
|
||||
|
||||
1. 幂等,多次 Dispose 不重复释放。
|
||||
2. 先 Dispose 子 Scope,再 Dispose 当前 Scope 自己持有的实例。
|
||||
3. 子 Scope Dispose 不影响父 Scope。
|
||||
4. 父 Scope Dispose 会递归 Dispose 所有子 Scope。
|
||||
5. 当前 Scope Dispose 后不能继续 Register / Resolve / CreateScope / CreateFeature。
|
||||
6. Dispose 过程中出现多个异常时,第一阶段可以选择:
|
||||
- 记录第一个异常并继续释放剩余对象,最后抛出聚合异常。
|
||||
- 或使用 `AggregateException`。
|
||||
|
||||
建议使用 LIFO 顺序释放当前 Scope 持有的实例,使后创建对象先释放。
|
||||
|
||||
## Register 算法
|
||||
|
||||
### 规则
|
||||
|
||||
1. 任意 Scope 可以随时 Register。
|
||||
2. Register 永远只写当前 Scope。
|
||||
3. Register 不会向父 Scope 写入。
|
||||
4. Register 不会向子 Scope 广播。
|
||||
5. 当前 Scope 已 Dispose 时 Register 失败。
|
||||
6. 同一 Scope 内相同 `ServiceKey` 重复注册,第一阶段建议直接失败,避免隐式覆盖。
|
||||
|
||||
子 Scope 覆盖父 Scope 不是通过修改父注册实现,而是子 Scope 自己注册相同 `ServiceKey`。
|
||||
|
||||
### 算法
|
||||
|
||||
```text
|
||||
Register(scope, key, registration):
|
||||
1. 如果 scope 已 Dispose,抛 ObjectDisposedException。
|
||||
2. 校验 key.Type 非空。
|
||||
3. 归一化 key.Name。
|
||||
4. 如果 scope._registrations 已存在 key,抛 DuplicateRegistrationException。
|
||||
5. 把 registration 写入 scope._registrations。
|
||||
6. 返回或完成 builder。
|
||||
```
|
||||
|
||||
### 命名注册
|
||||
|
||||
命名注册必须和无名注册共存。
|
||||
|
||||
```text
|
||||
Register<IRewardService, DefaultRewardService>()
|
||||
Register<IRewardService, BossRewardService>("boss")
|
||||
```
|
||||
|
||||
以上是两个不同 `ServiceKey`。
|
||||
|
||||
## Resolve 算法
|
||||
|
||||
### 规则
|
||||
|
||||
1. Resolve 从当前 Scope 开始。
|
||||
2. 当前 Scope 找不到时,再向 Parent 查找。
|
||||
3. 查找路径直到 Root。
|
||||
4. 子 Scope 的相同 `ServiceKey` 会覆盖父 Scope。
|
||||
5. Resolve 不允许绕过当前 Scope 直接查 Root。
|
||||
6. 当前 Scope 已 Dispose 时 Resolve 失败。
|
||||
|
||||
### 查找算法
|
||||
|
||||
```text
|
||||
FindRegistration(scope, key):
|
||||
current = scope
|
||||
while current != null:
|
||||
if current._registrations.TryGetValue(key, out registration):
|
||||
return (registrationOwner: current, registration)
|
||||
current = current.Parent
|
||||
return null
|
||||
```
|
||||
|
||||
### 创建算法
|
||||
|
||||
```text
|
||||
Resolve(scope, key):
|
||||
1. 如果 scope 已 Dispose,抛 ObjectDisposedException。
|
||||
2. result = FindRegistration(scope, key)。
|
||||
3. 如果找不到,抛 ServiceNotFoundException。
|
||||
4. 根据 registration.Lifetime 创建或读取实例。
|
||||
5. 返回实例。
|
||||
```
|
||||
|
||||
生命周期分支:
|
||||
|
||||
```text
|
||||
Singleton:
|
||||
缓存位置 = registrationOwner
|
||||
若缓存不存在,在 registrationOwner 中创建并记录释放
|
||||
返回 registrationOwner 缓存
|
||||
|
||||
PerScope:
|
||||
缓存位置 = resolveStartScope
|
||||
若缓存不存在,在 resolveStartScope 中创建并记录释放
|
||||
返回 resolveStartScope 缓存
|
||||
|
||||
Transient:
|
||||
每次创建新实例
|
||||
若实例需要 Scope 接管释放,记录到 resolveStartScope 释放栈
|
||||
返回新实例
|
||||
|
||||
Instance:
|
||||
返回 registration.Instance
|
||||
```
|
||||
|
||||
### TryResolve 算法
|
||||
|
||||
```text
|
||||
TryResolve(scope, key, out value):
|
||||
1. 如果 scope 已 Dispose,抛 ObjectDisposedException。
|
||||
2. 如果找不到注册,value = default,返回 false。
|
||||
3. 如果找到注册,走 Resolve 创建流程。
|
||||
4. 创建成功返回 true。
|
||||
5. 创建过程中发生构造异常,不吞异常。
|
||||
```
|
||||
|
||||
`TryResolve` 只表示“是否存在注册”,不表示“构造失败也返回 false”。
|
||||
|
||||
## CreateScope 算法
|
||||
|
||||
### 规则
|
||||
|
||||
1. 任意未 Dispose Scope 都可以创建子 Scope。
|
||||
2. 子 Scope 的 Parent 指向当前 Scope。
|
||||
3. 子 Scope 加入当前 Scope 的 Children 集合。
|
||||
4. 子 Scope 名称由调用方传入。
|
||||
5. 子 Scope 名称用于诊断和 Path,不作为服务注册 key。
|
||||
6. 同一父级下是否允许重名第一阶段建议禁止,便于诊断。
|
||||
|
||||
### 算法
|
||||
|
||||
```text
|
||||
CreateScope(parent, name):
|
||||
1. 如果 parent 已 Dispose,抛 ObjectDisposedException。
|
||||
2. 校验 name 非空。
|
||||
3. 如果 parent.Children 已存在同名未释放 Scope,抛 DuplicateScopeNameException。
|
||||
4. 创建 child = new ArchitectureScope(name, parent)。
|
||||
5. parent.Children.Add(child)。
|
||||
6. 返回 child。
|
||||
```
|
||||
|
||||
`Path` 示例:
|
||||
|
||||
```text
|
||||
Root
|
||||
Root/BossFightFeature
|
||||
Root/BossFightFeature/RevivePanel
|
||||
```
|
||||
|
||||
## CreateFeature 算法
|
||||
|
||||
### 规则
|
||||
|
||||
1. `CreateFeature<TFeature>()` 默认使用 `typeof(TFeature).Name`。
|
||||
2. `CreateFeature<TFeature>(name)` 创建当前 Scope 的子 Scope。
|
||||
3. 创建成功后实例化 `TFeature`。
|
||||
4. 自动调用 `feature.Install(featureScope)`。
|
||||
5. 如果 Install 失败,必须 Dispose 已创建的 featureScope,并从父级 Children 中移除。
|
||||
6. 调用者不应手写 Scope 创建和 Feature 安装流程。
|
||||
|
||||
### 第一阶段接口
|
||||
|
||||
```csharp
|
||||
IArchitectureScope CreateFeature<TFeature>()
|
||||
where TFeature : IFeature, new();
|
||||
|
||||
IArchitectureScope CreateFeature<TFeature>(string name)
|
||||
where TFeature : IFeature, new();
|
||||
```
|
||||
|
||||
### 算法
|
||||
|
||||
```text
|
||||
CreateFeature<TFeature>(parent):
|
||||
return CreateFeature<TFeature>(parent, typeof(TFeature).Name)
|
||||
|
||||
CreateFeature<TFeature>(parent, name):
|
||||
1. featureScope = parent.CreateScope(name)。
|
||||
2. try:
|
||||
feature = new TFeature()
|
||||
feature.Install(featureScope)
|
||||
return featureScope
|
||||
catch:
|
||||
featureScope.Dispose()
|
||||
从 parent.Children 移除 featureScope
|
||||
rethrow
|
||||
```
|
||||
|
||||
### 后续扩展点
|
||||
|
||||
第一阶段先使用 `new()` 约束,避免把构造注入、工厂注册和 Feature 安装绑在一起。
|
||||
|
||||
后续如需 Feature 构造依赖注入,可增加:
|
||||
|
||||
```csharp
|
||||
IArchitectureScope CreateFeature<TFeature>(string name, Func<IArchitectureScope, TFeature> factory)
|
||||
where TFeature : IFeature;
|
||||
```
|
||||
|
||||
不要在第一阶段默认扫描或反射构造 Feature。
|
||||
|
||||
## Dispose 算法
|
||||
|
||||
### Scope 状态
|
||||
|
||||
建议内部状态:
|
||||
|
||||
```text
|
||||
Active
|
||||
Disposing
|
||||
Disposed
|
||||
```
|
||||
|
||||
### 算法
|
||||
|
||||
```text
|
||||
Dispose(scope):
|
||||
1. 如果 scope.State == Disposed,直接返回。
|
||||
2. 如果 scope.State == Disposing,直接返回或抛 InvalidOperationException;推荐直接返回保证幂等。
|
||||
3. scope.State = Disposing。
|
||||
4. 复制 Children 列表。
|
||||
5. 按创建顺序反向 Dispose 子 Scope。
|
||||
6. 清空 Children。
|
||||
7. 按 LIFO Dispose 当前 Scope 持有的实例。
|
||||
8. 清空当前 Scope 的 PerScope / Singleton / Transient 跟踪缓存。
|
||||
9. 从 Parent.Children 中移除自己。
|
||||
10. scope.State = Disposed。
|
||||
11. 如有释放异常,抛出聚合异常。
|
||||
```
|
||||
|
||||
### 持有关系
|
||||
|
||||
当前 Scope 只释放自己持有的对象:
|
||||
|
||||
- 当前 Scope 创建的 Singleton。
|
||||
- 当前 Scope 发起 Resolve 后缓存的 PerScope。
|
||||
- 当前 Scope 接管的 Transient Disposable。
|
||||
- 当前 Scope 创建的子 Scope。
|
||||
|
||||
当前 Scope 不释放:
|
||||
|
||||
- 父 Scope 持有的 Singleton。
|
||||
- 父 Scope 持有的 PerScope 实例。
|
||||
- 默认 RegisterInstance 传入的外部对象。
|
||||
|
||||
## 文件组织建议
|
||||
|
||||
第一阶段建议把核心集中在 Runtime Core,不引入 Command / Query / Event 目录。
|
||||
|
||||
```text
|
||||
My project/Assets/FlowScope/Runtime/
|
||||
Core/
|
||||
FlowScope.cs
|
||||
IArchitectureScope.cs
|
||||
IScopeInfo.cs
|
||||
IFeature.cs
|
||||
Core/Container/
|
||||
ArchitectureScope.cs
|
||||
ServiceKey.cs
|
||||
Registration.cs
|
||||
RegistrationBuilder.cs
|
||||
ServiceLifetime.cs
|
||||
ScopeExceptions.cs
|
||||
```
|
||||
|
||||
测试建议:
|
||||
|
||||
```text
|
||||
My project/Assets/FlowScope/Tests/EditMode/
|
||||
Core/
|
||||
ArchitectureScopeRegistrationTests.cs
|
||||
ArchitectureScopeResolveTests.cs
|
||||
ArchitectureScopeFeatureTests.cs
|
||||
ArchitectureScopeDisposeTests.cs
|
||||
```
|
||||
|
||||
如果后续迁移到 package 结构,应保持相同命名空间和目录职责:
|
||||
|
||||
```text
|
||||
Packages/com.flowscope.kernel/Runtime/Core/
|
||||
Packages/com.flowscope.kernel/Runtime/Core/Container/
|
||||
Packages/com.flowscope.kernel/Tests/EditMode/Core/
|
||||
```
|
||||
|
||||
命名空间建议:
|
||||
|
||||
```text
|
||||
FlowScope
|
||||
FlowScope.Container
|
||||
```
|
||||
|
||||
不要继续把新核心放到 `FlowScope.Composition`,避免和旧 `RootContext + FeatureScope + Command/Query` 试验模型混在一起。
|
||||
|
||||
## 测试用例清单
|
||||
|
||||
### Root / Scope 统一模型
|
||||
|
||||
- `Create_ReturnsRootArchitectureScope`
|
||||
- `RootScope_HasNoParent`
|
||||
- `ChildScope_UsesSameArchitectureScopeContractAsRoot`
|
||||
- `ScopePath_BuildsFromRootToChild`
|
||||
|
||||
### Register 规则
|
||||
|
||||
- `Register_WritesOnlyCurrentScope`
|
||||
- `Register_OnDisposedScopeThrows`
|
||||
- `Register_DuplicateKeyInSameScopeThrows`
|
||||
- `Register_SameKeyInChildOverridesParentWithoutChangingParent`
|
||||
- `Register_NamedAndUnnamedKeysAreIndependent`
|
||||
- `RegisterInstance_DefaultDoesNotDisposeExternalInstance`
|
||||
|
||||
### Resolve 规则
|
||||
|
||||
- `Resolve_FindsCurrentScopeRegistrationFirst`
|
||||
- `Resolve_FallsBackToParentWhenCurrentMissing`
|
||||
- `Resolve_ChildRegistrationOverridesParent`
|
||||
- `Resolve_MissingRegistrationThrows`
|
||||
- `TryResolve_MissingRegistrationReturnsFalse`
|
||||
- `TryResolve_ConstructorFailureThrows`
|
||||
- `Resolve_OnDisposedScopeThrows`
|
||||
|
||||
### 生命周期
|
||||
|
||||
- `Singleton_IsCachedInRegistrationOwnerScope`
|
||||
- `Singleton_FromParentIsSharedByChildren`
|
||||
- `PerScope_IsCachedInResolveStartScope`
|
||||
- `PerScope_FromParentCreatesDifferentInstancesPerChildScope`
|
||||
- `Transient_CreatesNewInstanceEveryResolve`
|
||||
- `Transient_DisposableIsReleasedWhenOwningScopeDisposes`
|
||||
- `ChildDispose_DoesNotDisposeParentOwnedInstances`
|
||||
- `ParentDispose_DisposesChildrenAndOwnedInstances`
|
||||
- `Dispose_IsIdempotent`
|
||||
- `Dispose_UsesLifoForOwnedInstances`
|
||||
|
||||
### CreateScope
|
||||
|
||||
- `CreateScope_CreatesChildWithParent`
|
||||
- `CreateScope_OnDisposedParentThrows`
|
||||
- `CreateScope_EmptyNameThrows`
|
||||
- `CreateScope_DuplicateSiblingNameThrows`
|
||||
- `ChildDispose_RemovesChildFromParent`
|
||||
|
||||
### CreateFeature
|
||||
|
||||
- `CreateFeature_UsesFeatureTypeNameAsDefaultScopeName`
|
||||
- `CreateFeature_WithNameUsesExplicitName`
|
||||
- `CreateFeature_CallsInstallWithCreatedScope`
|
||||
- `CreateFeature_ReturnsInstalledScope`
|
||||
- `CreateFeature_InstallCanRegisterServicesToFeatureScope`
|
||||
- `CreateFeature_InstallFailureDisposesCreatedScope`
|
||||
- `CreateFeature_InstallFailureRemovesScopeFromParent`
|
||||
|
||||
### 默认业务边界
|
||||
|
||||
- `FeatureScope_CanResolveRootServices`
|
||||
- `FeatureScope_CanOwnFeatureServices`
|
||||
- `TwoFeatureScopes_DoNotSharePerScopeFeatureServices`
|
||||
- `ChildScope_CanOverrideFeatureServiceForLocalLifetime`
|
||||
- `FeatureWithoutChildScope_IsEnoughForNormalBusinessServices`
|
||||
|
||||
## 当前旧实现迁移 / 替换策略
|
||||
|
||||
### 要替换的旧模型
|
||||
|
||||
当前 `My project/Assets/FlowScope/Runtime/Composition` 中的试验模型应作为替换对象,而不是继续扩展对象。
|
||||
|
||||
旧模型特征:
|
||||
|
||||
- `IRootContext` 与 `IFeatureScope` 是不同接口。
|
||||
- `RootContext` 和 `FeatureScope` 是不同实现。
|
||||
- `IFeatureScope` 同时混入 `IArchitectureScope`、`ICommandContext`、`IQueryContext`。
|
||||
- `CreateFeatureScope(name)` 只创建 Feature Scope,没有统一 Child Scope 模型。
|
||||
- Command / Query / Architecture roles 与 Scope 基础能力耦合过早。
|
||||
|
||||
新模型应改为:
|
||||
|
||||
```text
|
||||
FlowScope.Create()
|
||||
-> IArchitectureScope Root
|
||||
Root.CreateFeature<TFeature>()
|
||||
-> IArchitectureScope Feature
|
||||
Feature.CreateScope("LocalLifetime")
|
||||
-> IArchitectureScope Child
|
||||
```
|
||||
|
||||
### 迁移步骤建议
|
||||
|
||||
第一阶段迁移只做核心替换,不迁移 Command / Query / Event。
|
||||
|
||||
1. 新增 `Core` / `Core.Container` 下的统一 Scope 类型和接口。
|
||||
2. 新增测试覆盖 Register / Resolve / CreateFeature / Dispose。
|
||||
3. 将旧 Composition 测试中仍有价值的用例迁移到新 Core 测试:
|
||||
- Root 服务可被 Feature Resolve。
|
||||
- Feature 级服务彼此隔离。
|
||||
- 命名注册可解析。
|
||||
4. 删除或降级旧 `RootContext` / `FeatureScope` 作为历史试验实现。
|
||||
5. 暂时不把旧 `ICommandContext` / `IQueryContext` 接回新 Scope。
|
||||
6. 后续如果要恢复 Command / Query / Event,应作为 Scope 上层扩展模块,而不是第一阶段核心职责。
|
||||
|
||||
### 兼容策略
|
||||
|
||||
第一阶段不建议保留旧 API 兼容层。
|
||||
|
||||
原因:
|
||||
|
||||
- `CreateFeatureScope(name)` 会把心智继续拉回“Root 特殊、Feature 特殊”的两层模型。
|
||||
- `IFeatureScope` 会让调用侧继续依赖旧接口名。
|
||||
- Command / Query 混入旧 FeatureScope 会污染新核心边界。
|
||||
|
||||
如果必须短期兼容样例,可用单独 adapter 文件临时桥接,但不得作为新核心主入口:
|
||||
|
||||
```text
|
||||
LegacyCompositionAdapter
|
||||
CreateFeatureScope(name) -> root.CreateScope(name)
|
||||
```
|
||||
|
||||
该 adapter 应标记为迁移期代码,并在第一阶段验收后移除。
|
||||
|
||||
## 第一阶段验收标准
|
||||
|
||||
### 文档与边界
|
||||
|
||||
1. 新核心 spec 明确以 `ArchitectureScope` 为唯一 Scope 模型。
|
||||
2. 文档明确旧 `RootContext + FeatureScope` 是替换对象。
|
||||
3. 文档明确第一阶段不进入 Command / Query / Event、完整 MVC/MVVM、UI Panel 生命周期统一接管。
|
||||
|
||||
### API
|
||||
|
||||
1. 存在 `FlowScope.Create()` 根入口。
|
||||
2. 存在统一 `IArchitectureScope`。
|
||||
3. `Root`、`Feature`、`Child` 都通过 `IArchitectureScope` 表达。
|
||||
4. 存在 `IFeature.Install(IArchitectureScope scope)`。
|
||||
5. 存在 `CreateFeature<TFeature>()` 和 `CreateFeature<TFeature>(string name)`。
|
||||
6. `CreateFeature<TFeature>()` 默认 Scope 名称为 `typeof(TFeature).Name`。
|
||||
|
||||
### Register / Resolve
|
||||
|
||||
1. 任意 Scope 可以 Register。
|
||||
2. Register 只写当前 Scope。
|
||||
3. Resolve 从当前 Scope 开始。
|
||||
4. Resolve 找不到时向 Parent 查找。
|
||||
5. 子 Scope 相同 key 覆盖父 Scope。
|
||||
6. 子 Scope 不能修改父 Scope 注册。
|
||||
|
||||
### 生命周期
|
||||
|
||||
1. 支持 `Singleton`、`PerScope`、`Transient`、`Instance`。
|
||||
2. 父级 Singleton 被子级共享。
|
||||
3. 父级 PerScope 注册在不同子 Scope 下生成不同实例。
|
||||
4. 子 Scope Dispose 不释放父 Scope 对象。
|
||||
5. 父 Scope Dispose 会释放全部子 Scope。
|
||||
6. Dispose 幂等。
|
||||
|
||||
### 测试
|
||||
|
||||
1. EditMode 测试覆盖 Root / Feature / Child 统一模型。
|
||||
2. EditMode 测试覆盖 Register 只写当前 Scope。
|
||||
3. EditMode 测试覆盖 Resolve 向父级查找与子级覆盖。
|
||||
4. EditMode 测试覆盖 CreateFeature 默认名称、自动 Install、失败清理。
|
||||
5. EditMode 测试覆盖 Dispose 递归释放、子不释放父、幂等。
|
||||
|
||||
### 负向验收
|
||||
|
||||
第一阶段完成时,不应出现:
|
||||
|
||||
- 新核心依赖旧 `RootContext`。
|
||||
- 新核心依赖旧 `FeatureScope`。
|
||||
- 新核心把 Command / Query / Event 作为必需接口。
|
||||
- 新核心要求每个 UI Panel 都创建 Scope。
|
||||
- 新核心要求 Feature 内部必须使用 MVC 或 MVVM。
|
||||
|
||||
## 后续阶段预留
|
||||
|
||||
后续可以在 Scope Tree 稳定后再讨论:
|
||||
|
||||
- Command / Query / Event 作为扩展模块如何挂到 Scope。
|
||||
- Feature 内部 MVC / MVVM 推荐层。
|
||||
- UI Panel 生命周期是否需要独立模块接管。
|
||||
- Scope 诊断树、注册来源、Resolve Trace。
|
||||
- Feature 工厂的依赖注入构造。
|
||||
- 自动扫描 Feature 的 marker-based 方案。
|
||||
|
||||
这些能力不应反向改变第一阶段核心事实:
|
||||
|
||||
```text
|
||||
FlowScope 的第一主角是统一 ArchitectureScope。
|
||||
```
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user