# 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(); ``` 等价概念: ```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().AsSingleton(); var boss = game.CreateFeature(); boss.Register().PerScope(); using var revive = boss.CreateScope("RevivePanel"); revive.Register().PerScope(); var reward = revive.Resolve(); ``` 解析顺序: ```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().AsSingleton()` 的声明式生命周期 API。 - `Register().AsSingleton()` 的接口到实现注册。 - `Register(name).PerScope()` 的命名注册。 - `Register(Type serviceType, string name, Type implementationType).PerScope()` 的运行时类型注册。 - `RegisterInstance(instance)` 的现有实例注册。 - `Resolve()` / `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()`。 因此下一版不采用“薄包装 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 CreateScope() CreateFeature() 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().AsSingleton(); var boss = game.CreateFeature(); boss.Register().PerScope(); using var revive = boss.CreateScope("RevivePanel"); revive.Register().PerScope(); var reward = revive.Resolve(); ``` 解析顺序: ```text RevivePanel Scope -> BossFightFeature Scope -> Root Scope ``` `revive.Register` 不污染 `boss`,`boss.Register` 不污染 `root`。 ## CreateFeature 规则 `CreateFeature()` 是 Scope 创建 + Feature 安装的语义封装。 调用者不传 name 时,默认使用 `typeof(TFeature).Name`: ```csharp var boss = game.CreateFeature(); // 等价于 game.CreateFeature(typeof(BossFightFeature).Name) ``` 推荐 API: ```csharp IArchitectureScope CreateFeature() where TFeature : IFeature, new(); IArchitectureScope CreateFeature(string name) where TFeature : IFeature, new(); ``` 第一版可以先使用 `where TFeature : IFeature, new()`,保持简单: ```csharp public IArchitectureScope CreateFeature(string name) where TFeature : IFeature, new() { var child = CreateScope(name); var feature = new TFeature(); feature.Install(child); return child; } ``` 后续如果 `TFeature` 需要依赖注入构造,可以改为从当前 Scope 创建: ```csharp child.Register().PerScope(); var feature = child.Resolve(); feature.Install(child); ``` 调用侧不应该手写: ```csharp boss.InstallFeature(new BossFightFeature()); boss.UseArchitecture(new BossFightArchitecture()); ``` Feature 的安装和 Scope 的创建应该被 `CreateFeature()` 收住。 ## 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().PerScope(); scope.Register().PerScope(); scope.UseMvvm(); scope.UseMvc(); } } ``` `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() where TFeature : IFeature, new(); IArchitectureScope CreateFeature(string name) where TFeature : IFeature, new(); IRegistrationBuilder Register(); IRegistrationBuilder Register() where TImplementation : TService; T Resolve(); T Resolve(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() where TFeature : IFeature, new(); IArchitectureScope CreateFeature(string name) where TFeature : IFeature, new(); IRegistrationBuilder Register(); IRegistrationBuilder Register(string name); IRegistrationBuilder Register() where TImplementation : TService; IRegistrationBuilder Register(string name) where TImplementation : TService; void RegisterInstance(T instance); void RegisterInstance(T instance, string name); T Resolve(); T Resolve(string name); bool TryResolve(out T value); bool TryResolve(out T value, string name); } ``` 工厂方法: ```csharp var game = FlowScope.Create(); var boss = game.CreateFeature(); ``` ## 暂不进入的内容 以下能力暂时不作为下一版第一阶段重点: - Command / Query / Event 通信模型。 - 完整 MVC / MVVM 框架。 - 自动扫描所有 Feature。 - UI Panel 生命周期统一接管。 - 复杂诊断面板。 - 热更新资源接入细节。 原因是当前最核心的问题还在: ```text Scope / Feature / Container / Architecture 的第一层抽象需要先稳定。 ``` 通信和表现层规则应该建立在稳定 Scope 模型之上。 ## 验收标准 下一版实现完成后,应满足: 1. Root 和普通 Scope 使用同一种 `ArchitectureScope` 模型。 2. `CreateFeature()` 默认使用 `typeof(TFeature).Name` 作为 Scope 名称。 3. `CreateFeature(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 能力,但默认心智模型必须保持简单。