Files
FlowScope/docs/superpowers/plans/2026-06-17-architecture-scope-container-implementation-plan.md
2026-06-18 12:20:20 +08:00

1171 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ArchitectureScope Container Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:**`docs/specs/architecture-scope-container-implementation-spec.md` 实现统一的 `ArchitectureScope` / FlowScope Container / Scope Tree并替换当前 `RootContext + FeatureScope` 试验入口。
**Architecture:** 第一阶段只做核心 Scope 与容器边界:`FlowScope.Create()` 创建 RootRoot / Feature / Child 都通过同一个 `IArchitectureScope` 表达。保留现有 `FlowScope.Container.Container` 中已经成立的父子查找、局部覆盖、`PerScope()`、Dispose 子树等能力,在其上新增 Core 接口与 `ArchitectureScope` 包装/约束层;旧 `FlowScope.Composition` 不继续扩展,最后删除或降级。
**Tech Stack:** Unity C#NUnit EditMode tests现有 `FlowScope.Runtime.asmdef` / `FlowScope.Tests.EditMode.asmdef`,验证优先使用 `dotnet build --no-restore`
---
## 执行边界
本计划只实现以下内容:
- `FlowScope.Create()` 根入口。
- `IArchitectureScope` / `IScopeInfo` / `IFeature` / `IRegistrationBuilder`
- `ArchitectureScope` 统一 Scope 节点。
- Register / Resolve / CreateScope / CreateFeature / Dispose 规则。
- 旧 Composition 测试中仍有价值的 Root 服务、Feature 隔离、命名注册用例迁移到 Core 测试。
- 移除或降级旧 `RootContext + FeatureScope + Command/Query` 试验入口。
本计划不实现:
- Command / Query / Event。
- 完整 MVC / MVVM。
- UI Panel 生命周期统一接管。
- 自动扫描 Feature。
- 资源、存档、音频、UI 模块改造。
执行前必须先确认当前工作树已有改动,不能覆盖用户已有修改:
```powershell
git status --short
git diff -- "My project/Assets/FlowScope/Runtime/Container/Container.cs"
git diff -- "My project/Assets/FlowScope/Runtime/Container/ContainerRegistration.cs"
git diff -- "My project/Assets/FlowScope/Tests/EditMode/Container/ContainerTests.cs"
```
如果这些文件已有未合并改动,执行者必须在当前改动基础上续写,不得 `git restore` 或重写文件。
## 文件结构
### 新增 Runtime Core 文件
- Create: `My project/Assets/FlowScope/Runtime/Core/FlowScope.cs`
- 静态入口,创建 Root Scope。
- Create: `My project/Assets/FlowScope/Runtime/Core/IArchitectureScope.cs`
- 统一 Scope 主接口。
- Create: `My project/Assets/FlowScope/Runtime/Core/IScopeInfo.cs`
- 只读 Scope 元信息接口。
- Create: `My project/Assets/FlowScope/Runtime/Core/IFeature.cs`
- Feature 安装接口。
- Create: `My project/Assets/FlowScope/Runtime/Core/IRegistrationBuilder.cs`
- Core 层注册生命周期 builder 接口。
- Create: `My project/Assets/FlowScope/Runtime/Core/Container/ArchitectureScope.cs`
- 统一 Scope 节点实现。
- Create: `My project/Assets/FlowScope/Runtime/Core/Container/ScopeExceptions.cs`
- 核心 Scope 异常类型。
### 修改现有 Container 文件
- Modify: `My project/Assets/FlowScope/Runtime/Container/Container.cs`
- 暴露 `ParentInfo` / `Path` / `IsDisposed` 所需的最小只读能力。
- 增加同级重名 Scope 检查。
- 确保 `CreateScope(string name)` 空名失败。
- 保持注册只写当前 Container不改变父级。
- Modify: `My project/Assets/FlowScope/Runtime/Container/ContainerRegistration.cs`
-`ServiceLifetime.Scoped` 命名调整为 `PerScope`,或在 Core 层适配为 `PerScope`
- 保持 `PerScope` 缓存到 Resolve 发起 Scope 的语义。
### 新增 Core EditMode 测试
- Create: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeRegistrationTests.cs`
- Create: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeResolveTests.cs`
- Create: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeFeatureTests.cs`
- Create: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeDisposeTests.cs`
### 删除或降级旧 Composition
- Delete or quarantine: `My project/Assets/FlowScope/Runtime/Composition/CompositionContracts.cs`
- Delete or quarantine: `My project/Assets/FlowScope/Runtime/Composition/FlowScopeApp.cs`
- Delete or migrate: `My project/Assets/FlowScope/Tests/EditMode/Composition/CompositionTests.cs`
如果执行时选择“降级”而不是删除,必须放到 `FlowScope.LegacyComposition` 命名空间,并且不得被新 Core 测试依赖。
## Task 1: 新增 Core 接口与根入口
**Files:**
- Create: `My project/Assets/FlowScope/Runtime/Core/IScopeInfo.cs`
- Create: `My project/Assets/FlowScope/Runtime/Core/IRegistrationBuilder.cs`
- Create: `My project/Assets/FlowScope/Runtime/Core/IFeature.cs`
- Create: `My project/Assets/FlowScope/Runtime/Core/IArchitectureScope.cs`
- Create: `My project/Assets/FlowScope/Runtime/Core/FlowScope.cs`
- Test: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeRegistrationTests.cs`
- [ ] **Step 1: 创建目录和 Unity meta**
执行者在 Unity 项目中创建目录:
```text
My project/Assets/FlowScope/Runtime/Core/
My project/Assets/FlowScope/Runtime/Core/Container/
My project/Assets/FlowScope/Tests/EditMode/Core/
```
如果通过文件系统创建,执行后打开 Unity 或保持后续生成 `.meta` 被纳入同一提交。不要手写 `.meta`
- [ ] **Step 2: 先写 Root 入口测试**
`ArchitectureScopeRegistrationTests.cs` 写入:
```csharp
using NUnit.Framework;
namespace FlowScope.Tests.EditMode.Core
{
public sealed class ArchitectureScopeRegistrationTests
{
[Test]
public void Create_ReturnsRootArchitectureScope()
{
using var root = FlowScope.FlowScope.Create();
Assert.That(root.Name, Is.EqualTo("Root"));
Assert.That(root.Parent, Is.Null);
Assert.That(root.Path, Is.EqualTo("Root"));
Assert.That(root.IsDisposed, Is.False);
}
}
}
```
- [ ] **Step 3: 运行测试确认失败**
```powershell
dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore
```
Expected: FAIL缺少 `FlowScope.FlowScope` / `IArchitectureScope`
- [ ] **Step 4: 新增接口代码**
`IScopeInfo.cs`
```csharp
namespace FlowScope
{
public interface IScopeInfo
{
string Name { get; }
IScopeInfo Parent { get; }
string Path { get; }
bool IsDisposed { get; }
}
}
```
`IRegistrationBuilder.cs`
```csharp
namespace FlowScope
{
public interface IRegistrationBuilder
{
void AsSingleton();
void PerScope();
void AsTransient();
}
}
```
`IFeature.cs`
```csharp
namespace FlowScope
{
public interface IFeature
{
void Install(IArchitectureScope scope);
}
}
```
`IArchitectureScope.cs`
```csharp
using System;
namespace FlowScope
{
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);
}
}
```
- [ ] **Step 5: 新增临时可编译入口**
`FlowScope.cs`
```csharp
using FlowScope.Core.Container;
namespace FlowScope
{
public static class FlowScope
{
public static IArchitectureScope Create()
{
return Create("Root");
}
public static IArchitectureScope Create(string rootName)
{
return new ArchitectureScope(rootName);
}
}
}
```
- [ ] **Step 6: 暂存后续实现需要的空壳**
`ArchitectureScope.cs` 先写可编译空壳,但未实现行为:
```csharp
using System;
namespace FlowScope.Core.Container
{
internal sealed class ArchitectureScope : IArchitectureScope
{
public ArchitectureScope(string name)
{
Name = name;
Path = name;
}
public string Name { get; }
public IScopeInfo Parent => null;
public string Path { get; }
public bool IsDisposed { get; private set; }
public IArchitectureScope CreateScope(string name) => throw new NotImplementedException();
public IArchitectureScope CreateFeature<TFeature>() where TFeature : IFeature, new() => throw new NotImplementedException();
public IArchitectureScope CreateFeature<TFeature>(string name) where TFeature : IFeature, new() => throw new NotImplementedException();
public IRegistrationBuilder Register<TImplementation>() => throw new NotImplementedException();
public IRegistrationBuilder Register<TImplementation>(string name) => throw new NotImplementedException();
public IRegistrationBuilder Register<TService, TImplementation>() where TImplementation : TService => throw new NotImplementedException();
public IRegistrationBuilder Register<TService, TImplementation>(string name) where TImplementation : TService => throw new NotImplementedException();
public void RegisterInstance<TService>(TService instance) => throw new NotImplementedException();
public void RegisterInstance<TService>(TService instance, string name) => throw new NotImplementedException();
public TService Resolve<TService>() => throw new NotImplementedException();
public TService Resolve<TService>(string name) => throw new NotImplementedException();
public bool TryResolve<TService>(out TService value, string name) => throw new NotImplementedException();
public bool TryResolve<TService>(out TService value) => throw new NotImplementedException();
public void Dispose() => IsDisposed = true;
}
}
```
- [ ] **Step 7: 运行测试确认入口测试通过**
```powershell
dotnet build "My project/FlowScope.Runtime.csproj" --no-restore
dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore
```
Expected: PASS build。此时只有 Root 入口行为成立。
**建议提交点:** `feat: 新增 ArchitectureScope 核心接口入口`
## Task 2: 实现 Register 只写当前 Scope
**Files:**
- Modify: `My project/Assets/FlowScope/Runtime/Core/Container/ArchitectureScope.cs`
- Create: `My project/Assets/FlowScope/Runtime/Core/Container/RegistrationBuilderAdapter.cs`
- Test: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeRegistrationTests.cs`
- [ ] **Step 1: 增加 Register 测试**
`ArchitectureScopeRegistrationTests.cs` 增加:
```csharp
[Test]
public void Register_WritesOnlyCurrentScope()
{
using var root = FlowScope.FlowScope.Create();
using var child = root.CreateScope("Child");
root.Register<IService, RootService>().AsSingleton();
child.Register<IService, ChildService>().AsSingleton();
Assert.That(root.Resolve<IService>(), Is.TypeOf<RootService>());
Assert.That(child.Resolve<IService>(), Is.TypeOf<ChildService>());
}
[Test]
public void Register_DuplicateKeyInSameScopeThrows()
{
using var root = FlowScope.FlowScope.Create();
root.Register<IService, RootService>().AsSingleton();
Assert.Throws<InvalidOperationException>(() =>
root.Register<IService, ChildService>().AsSingleton());
}
private interface IService { }
private sealed class RootService : IService { }
private sealed class ChildService : IService { }
```
- [ ] **Step 2: 运行测试确认失败**
```powershell
dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore
```
Expected: FAIL`CreateScope` / `Register` / `Resolve` 未实现。
- [ ] **Step 3: 用现有 Container 实现 Register 代理**
`RegistrationBuilderAdapter.cs`
```csharp
using FlowScope.Container;
namespace FlowScope.Core.Container
{
internal sealed class RegistrationBuilderAdapter : IRegistrationBuilder
{
private readonly ContainerRegistrationBuilder _inner;
public RegistrationBuilderAdapter(ContainerRegistrationBuilder inner)
{
_inner = inner;
}
public void AsSingleton() => _inner.AsSingleton();
public void PerScope() => _inner.PerScope();
public void AsTransient() => _inner.AsTransient();
}
}
```
`ArchitectureScope` 中持有现有 Container
```csharp
private readonly FlowScope.Container.Container _container;
public IRegistrationBuilder Register<TImplementation>()
{
ThrowIfDisposed();
return new RegistrationBuilderAdapter(_container.Register<TImplementation>());
}
public IRegistrationBuilder Register<TImplementation>(string name)
{
ThrowIfDisposed();
return new RegistrationBuilderAdapter(_container.Register<TImplementation>(name));
}
public IRegistrationBuilder Register<TService, TImplementation>()
where TImplementation : TService
{
ThrowIfDisposed();
return new RegistrationBuilderAdapter(_container.Register<TService, TImplementation>());
}
public IRegistrationBuilder Register<TService, TImplementation>(string name)
where TImplementation : TService
{
ThrowIfDisposed();
return new RegistrationBuilderAdapter(_container.Register<TService, TImplementation>(name));
}
public void RegisterInstance<TService>(TService instance)
{
RegisterInstance(instance, null);
}
public void RegisterInstance<TService>(TService instance, string name)
{
ThrowIfDisposed();
_container.RegisterInstance(instance, name);
}
```
- [ ] **Step 4: 实现 Resolve 代理**
```csharp
public TService Resolve<TService>()
{
ThrowIfDisposed();
return _container.Resolve<TService>();
}
public TService Resolve<TService>(string name)
{
ThrowIfDisposed();
return _container.Resolve<TService>(name);
}
public bool TryResolve<TService>(out TService value)
{
ThrowIfDisposed();
return _container.TryResolve(out value);
}
public bool TryResolve<TService>(out TService value, string name)
{
ThrowIfDisposed();
return _container.TryResolve(out value, name);
}
```
- [ ] **Step 5: 运行测试确认通过**
```powershell
dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore
```
Expected: PASS build新增 Register 测试通过。
**建议提交点:** `feat: 实现 ArchitectureScope 注册解析入口`
## Task 3: 实现 Scope Tree 与 Path
**Files:**
- Modify: `My project/Assets/FlowScope/Runtime/Core/Container/ArchitectureScope.cs`
- Modify: `My project/Assets/FlowScope/Runtime/Container/Container.cs`
- Test: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeResolveTests.cs`
- [ ] **Step 1: 写 Scope Tree 测试**
`ArchitectureScopeResolveTests.cs`
```csharp
using NUnit.Framework;
namespace FlowScope.Tests.EditMode.Core
{
public sealed class ArchitectureScopeResolveTests
{
[Test]
public void CreateScope_CreatesChildWithParentAndPath()
{
using var root = FlowScope.FlowScope.Create();
using var feature = root.CreateScope("BossFightFeature");
using var child = feature.CreateScope("RevivePanel");
Assert.That(feature.Parent, Is.SameAs(root));
Assert.That(child.Parent, Is.SameAs(feature));
Assert.That(feature.Path, Is.EqualTo("Root/BossFightFeature"));
Assert.That(child.Path, Is.EqualTo("Root/BossFightFeature/RevivePanel"));
}
[Test]
public void CreateScope_DuplicateSiblingNameThrows()
{
using var root = FlowScope.FlowScope.Create();
using var first = root.CreateScope("BossFightFeature");
Assert.Throws<InvalidOperationException>(() => root.CreateScope("BossFightFeature"));
}
}
}
```
- [ ] **Step 2: 运行测试确认失败**
```powershell
dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore
```
Expected: FAIL`CreateScope` 未实现或未检查重名。
- [ ] **Step 3: 在 ArchitectureScope 内维护子 Scope**
实现要点:
```csharp
private readonly ArchitectureScope _parent;
private readonly List<ArchitectureScope> _children = new List<ArchitectureScope>();
public IScopeInfo Parent => _parent;
public IArchitectureScope CreateScope(string name)
{
ThrowIfDisposed();
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("Scope name cannot be empty.", nameof(name));
}
if (_children.Exists(child => !child.IsDisposed && child.Name == name))
{
throw new InvalidOperationException($"Scope '{name}' already exists under '{Path}'.");
}
var childContainer = _container.CreateScope(name);
var child = new ArchitectureScope(name, this, childContainer);
_children.Add(child);
return child;
}
```
构造函数需要变成:
```csharp
public ArchitectureScope(string name)
: this(name, null, new FlowScope.Container.Container())
{
}
private ArchitectureScope(string name, ArchitectureScope parent, FlowScope.Container.Container container)
{
if (string.IsNullOrWhiteSpace(name))
{
throw new ArgumentException("Scope name cannot be empty.", nameof(name));
}
Name = name;
_parent = parent;
_container = container;
Path = parent == null ? name : $"{parent.Path}/{name}";
}
```
- [ ] **Step 4: 确保 Dispose 从父节点移除**
`Dispose` 中释放子 Scope 后:
```csharp
_parent?._children.Remove(this);
```
- [ ] **Step 5: 运行测试确认通过**
```powershell
dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore
```
Expected: PASS buildScope Tree / Path 测试通过。
**建议提交点:** `feat: 实现 ArchitectureScope 树结构`
## Task 4: 固化 Resolve 父级查找与子级覆盖
**Files:**
- Modify: `My project/Assets/FlowScope/Runtime/Core/Container/ArchitectureScope.cs`
- Modify: `My project/Assets/FlowScope/Runtime/Container/Container.cs`
- Test: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeResolveTests.cs`
- [ ] **Step 1: 增加父级查找与覆盖测试**
```csharp
[Test]
public void Resolve_FallsBackToParentWhenCurrentMissing()
{
using var root = FlowScope.FlowScope.Create();
using var feature = root.CreateScope("BossFightFeature");
var clock = new RootClock(42);
root.RegisterInstance<IRootClock>(clock);
Assert.That(feature.Resolve<IRootClock>(), Is.SameAs(clock));
}
[Test]
public void Resolve_ChildRegistrationOverridesParent()
{
using var root = FlowScope.FlowScope.Create();
using var feature = root.CreateScope("BossFightFeature");
root.Register<IRewardService, GlobalRewardService>().AsSingleton();
feature.Register<IRewardService, BossRewardService>().AsSingleton();
Assert.That(root.Resolve<IRewardService>(), Is.TypeOf<GlobalRewardService>());
Assert.That(feature.Resolve<IRewardService>(), Is.TypeOf<BossRewardService>());
}
private interface IRootClock { int Value { get; } }
private sealed class RootClock : IRootClock
{
public RootClock(int value) => Value = value;
public int Value { get; }
}
private interface IRewardService { }
private sealed class GlobalRewardService : IRewardService { }
private sealed class BossRewardService : IRewardService { }
```
- [ ] **Step 2: 运行测试**
```powershell
dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore
```
Expected: PASS。现有 `Container.TryFindRegistration` 已支持向父查找;如果失败,修复 Container 父子链传递。
- [ ] **Step 3: 增加 TryResolve 语义测试**
```csharp
[Test]
public void TryResolve_MissingRegistrationReturnsFalse()
{
using var root = FlowScope.FlowScope.Create();
Assert.That(root.TryResolve<IRewardService>(out var value), Is.False);
Assert.That(value, Is.Null);
}
```
- [ ] **Step 4: 确保构造异常不被 TryResolve 吞掉**
```csharp
[Test]
public void TryResolve_ConstructorFailureThrows()
{
using var root = FlowScope.FlowScope.Create();
root.Register<BrokenService>().AsTransient();
Assert.Throws<InvalidOperationException>(() =>
root.TryResolve<BrokenService>(out _));
}
private sealed class BrokenService
{
public BrokenService()
{
throw new InvalidOperationException("broken");
}
}
```
Expected: PASS。若现有反射调用把异常包成 `TargetInvocationException`,按当前实际行为断言外层异常,但不要返回 false。
**建议提交点:** `test: 固化 Scope 解析查找语义`
## Task 5: 固化生命周期语义
**Files:**
- Modify: `My project/Assets/FlowScope/Runtime/Container/ContainerRegistration.cs`
- Modify: `My project/Assets/FlowScope/Runtime/Container/Container.cs`
- Test: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeDisposeTests.cs`
- [ ] **Step 1: 写生命周期测试**
`ArchitectureScopeDisposeTests.cs`
```csharp
using NUnit.Framework;
namespace FlowScope.Tests.EditMode.Core
{
public sealed class ArchitectureScopeDisposeTests
{
[Test]
public void Singleton_FromParentIsSharedByChildrenAndDisposedByParent()
{
using var root = FlowScope.FlowScope.Create();
var first = root.CreateScope("First");
var second = root.CreateScope("Second");
root.Register<IDisposableService, DisposableService>().AsSingleton();
var rootInstance = root.Resolve<IDisposableService>();
Assert.That(first.Resolve<IDisposableService>(), Is.SameAs(rootInstance));
Assert.That(second.Resolve<IDisposableService>(), Is.SameAs(rootInstance));
first.Dispose();
Assert.That(rootInstance.IsDisposed, Is.False);
root.Dispose();
Assert.That(rootInstance.IsDisposed, Is.True);
}
[Test]
public void PerScope_FromParentCreatesDifferentInstancesPerChildScope()
{
using var root = FlowScope.FlowScope.Create();
var first = root.CreateScope("First");
var second = root.CreateScope("Second");
root.Register<IDisposableService, DisposableService>().PerScope();
var firstInstance = first.Resolve<IDisposableService>();
var secondInstance = second.Resolve<IDisposableService>();
Assert.That(first.Resolve<IDisposableService>(), Is.SameAs(firstInstance));
Assert.That(second.Resolve<IDisposableService>(), Is.SameAs(secondInstance));
Assert.That(firstInstance, Is.Not.SameAs(secondInstance));
first.Dispose();
Assert.That(firstInstance.IsDisposed, Is.True);
Assert.That(secondInstance.IsDisposed, Is.False);
}
private interface IDisposableService
{
bool IsDisposed { get; }
}
private sealed class DisposableService : IDisposableService, System.IDisposable
{
public bool IsDisposed { get; private set; }
public void Dispose() => IsDisposed = true;
}
}
}
```
- [ ] **Step 2: 运行测试**
```powershell
dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore
```
Expected: PASS。现有 Container 大概率已满足;如果失败,修复 `ContainerRegistration.ResolveScoped(requester)` 的缓存归属。
- [ ] **Step 3: 写 Dispose 顺序与幂等测试**
```csharp
[Test]
public void Dispose_ParentDisposesChildrenBeforeOwnInstancesAndIsIdempotent()
{
DisposeRecorder.Order.Clear();
var root = FlowScope.FlowScope.Create();
var child = root.CreateScope("Child");
root.Register<ITracked, RootTracked>().AsSingleton();
child.Register<IChildTracked, ChildTracked>().AsSingleton();
root.Resolve<ITracked>();
child.Resolve<IChildTracked>();
root.Dispose();
root.Dispose();
CollectionAssert.AreEqual(new[] { "child", "root" }, DisposeRecorder.Order);
}
private interface ITracked { }
private interface IChildTracked { }
private static class DisposeRecorder
{
public static readonly System.Collections.Generic.List<string> Order =
new System.Collections.Generic.List<string>();
}
private sealed class RootTracked : ITracked, System.IDisposable
{
public void Dispose() => DisposeRecorder.Order.Add("root");
}
private sealed class ChildTracked : IChildTracked, System.IDisposable
{
public void Dispose() => DisposeRecorder.Order.Add("child");
}
```
- [ ] **Step 4: 明确 Instance 不释放外部实例**
保留或迁移现有 `ContainerTests.RegisterInstance_Resolve_ReturnsSameInstanceAndDoesNotDisposeExternalInstance`,在 Core 测试中也覆盖:
```csharp
[Test]
public void RegisterInstance_DefaultDoesNotDisposeExternalInstance()
{
var root = FlowScope.FlowScope.Create();
var service = new DisposableService();
root.RegisterInstance<IDisposableService>(service);
root.Dispose();
Assert.That(service.IsDisposed, Is.False);
}
```
**建议提交点:** `test: 固化 ArchitectureScope 生命周期语义`
## Task 6: 实现 CreateFeature 自动创建与安装
**Files:**
- Modify: `My project/Assets/FlowScope/Runtime/Core/Container/ArchitectureScope.cs`
- Test: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeFeatureTests.cs`
- [ ] **Step 1: 写 CreateFeature 测试**
`ArchitectureScopeFeatureTests.cs`
```csharp
using System;
using NUnit.Framework;
namespace FlowScope.Tests.EditMode.Core
{
public sealed class ArchitectureScopeFeatureTests
{
[Test]
public void CreateFeature_UsesFeatureTypeNameAndCallsInstall()
{
using var root = FlowScope.FlowScope.Create();
using var feature = root.CreateFeature<BossFightFeature>();
Assert.That(feature.Name, Is.EqualTo(nameof(BossFightFeature)));
Assert.That(feature.Path, Is.EqualTo("Root/BossFightFeature"));
Assert.That(feature.Resolve<BossFightModel>(), Is.Not.Null);
}
[Test]
public void CreateFeature_WithNameUsesExplicitName()
{
using var root = FlowScope.FlowScope.Create();
using var feature = root.CreateFeature<BossFightFeature>("BossFight");
Assert.That(feature.Name, Is.EqualTo("BossFight"));
Assert.That(feature.Resolve<BossFightModel>(), Is.Not.Null);
}
private sealed class BossFightFeature : IFeature
{
public void Install(IArchitectureScope scope)
{
scope.Register<BossFightModel>().PerScope();
}
}
private sealed class BossFightModel { }
}
}
```
- [ ] **Step 2: 实现 CreateFeature**
`ArchitectureScope` 中:
```csharp
public IArchitectureScope CreateFeature<TFeature>()
where TFeature : IFeature, new()
{
return CreateFeature<TFeature>(typeof(TFeature).Name);
}
public IArchitectureScope CreateFeature<TFeature>(string name)
where TFeature : IFeature, new()
{
ThrowIfDisposed();
var featureScope = (ArchitectureScope)CreateScope(name);
try
{
var feature = new TFeature();
feature.Install(featureScope);
return featureScope;
}
catch
{
featureScope.Dispose();
throw;
}
}
```
- [ ] **Step 3: 写 Install 失败清理测试**
```csharp
[Test]
public void CreateFeature_WhenInstallFailsDisposesCreatedScope()
{
using var root = FlowScope.FlowScope.Create();
Assert.Throws<InvalidOperationException>(() => root.CreateFeature<BrokenFeature>());
using var feature = root.CreateFeature<BossFightFeature>(nameof(BrokenFeature));
Assert.That(feature.Name, Is.EqualTo(nameof(BrokenFeature)));
}
private sealed class BrokenFeature : IFeature
{
public void Install(IArchitectureScope scope)
{
throw new InvalidOperationException("install failed");
}
}
```
这个测试证明失败 Scope 已从父级移除,否则同名创建会失败。
- [ ] **Step 4: 运行测试确认通过**
```powershell
dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore
```
Expected: PASS buildCreateFeature 默认名、显式名、Install、失败清理全部通过。
**建议提交点:** `feat: 实现 CreateFeature 自动安装`
## Task 7: 迁移有价值的旧 Composition 用例
**Files:**
- Modify: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeFeatureTests.cs`
- Modify: `My project/Assets/FlowScope/Tests/EditMode/Core/ArchitectureScopeResolveTests.cs`
- Delete or quarantine: `My project/Assets/FlowScope/Tests/EditMode/Composition/CompositionTests.cs`
- [ ] **Step 1: 迁移 Root 服务可被 Feature Resolve**
新增测试:
```csharp
[Test]
public void FeatureScope_CanResolveRootServices()
{
using var root = FlowScope.FlowScope.Create();
var clock = new RootClock(10);
root.RegisterInstance<IRootClock>(clock);
using var feature = root.CreateFeature<ClockFeature>();
Assert.That(feature.Resolve<IRootClock>(), Is.SameAs(clock));
Assert.That(feature.Resolve<ClockModel>().Value, Is.EqualTo(10));
}
private interface IRootClock { int Value { get; } }
private sealed class RootClock : IRootClock
{
public RootClock(int value) => Value = value;
public int Value { get; }
}
private sealed class ClockFeature : IFeature
{
public void Install(IArchitectureScope scope)
{
scope.Register<ClockModel>().PerScope();
}
}
private sealed class ClockModel
{
public ClockModel(IRootClock clock) => Value = clock.Value;
public int Value { get; }
}
```
- [ ] **Step 2: 迁移 Feature 级服务隔离**
新增测试:
```csharp
[Test]
public void TwoFeatureScopes_DoNotSharePerScopeFeatureServices()
{
using var root = FlowScope.FlowScope.Create();
root.Register<IFeatureService, FeatureService>().PerScope();
using var first = root.CreateFeature<EmptyFeature>("FirstActivity");
using var second = root.CreateFeature<EmptyFeature>("SecondActivity");
Assert.That(first.Resolve<IFeatureService>(), Is.SameAs(first.Resolve<IFeatureService>()));
Assert.That(first.Resolve<IFeatureService>(), Is.Not.SameAs(second.Resolve<IFeatureService>()));
}
private interface IFeatureService { }
private sealed class FeatureService : IFeatureService { }
private sealed class EmptyFeature : IFeature
{
public void Install(IArchitectureScope scope) { }
}
```
- [ ] **Step 3: 迁移命名注册**
新增测试:
```csharp
[Test]
public void NamedRegistration_ResolvesByTypeAndNameFromFeature()
{
using var root = FlowScope.FlowScope.Create();
root.Register<IRewardService, BossRewardService>("boss").PerScope();
root.Register<IRewardService, ScratchRewardService>("scratch").PerScope();
using var boss = root.CreateFeature<EmptyFeature>("BossFight");
using var scratch = root.CreateFeature<EmptyFeature>("ScratchTicket");
Assert.That(boss.Resolve<IRewardService>("boss"), Is.TypeOf<BossRewardService>());
Assert.That(scratch.Resolve<IRewardService>("scratch"), Is.TypeOf<ScratchRewardService>());
Assert.That(boss.Resolve<IRewardService>("boss"), Is.SameAs(boss.Resolve<IRewardService>("boss")));
Assert.That(boss.Resolve<IRewardService>("boss"), Is.Not.SameAs(scratch.Resolve<IRewardService>("boss")));
}
private interface IRewardService { }
private sealed class BossRewardService : IRewardService { }
private sealed class ScratchRewardService : IRewardService { }
```
- [ ] **Step 4: 不迁移 Command / Query 测试**
旧测试 `FeatureScope_CommandAndQueryOperateOnScopedArchitecture` 不迁移。原因spec 明确第一阶段不进入 Command / Query / Event。
- [ ] **Step 5: 运行测试**
```powershell
dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore
```
Expected: PASS build。
**建议提交点:** `test: 迁移 Composition 核心用例到 ArchitectureScope`
## Task 8: 删除或隔离旧 Composition 试验线
**Files:**
- Delete or quarantine: `My project/Assets/FlowScope/Runtime/Composition/CompositionContracts.cs`
- Delete or quarantine: `My project/Assets/FlowScope/Runtime/Composition/FlowScopeApp.cs`
- Delete or quarantine: `My project/Assets/FlowScope/Tests/EditMode/Composition/CompositionTests.cs`
- [ ] **Step 1: 查找旧 Composition 引用**
```powershell
rg -n "FlowScope\.Composition|IRootContext|IFeatureScope|ICommandContext|IQueryContext|CreateFeatureScope|FlowScopeApp" "My project/Assets/FlowScope"
```
Expected: 只剩 Runtime/Composition 与 Tests/EditMode/Composition 自身引用。
- [ ] **Step 2: 删除旧 Runtime/Composition 文件**
如果没有外部引用,执行:
```powershell
git rm "My project/Assets/FlowScope/Runtime/Composition/CompositionContracts.cs" "My project/Assets/FlowScope/Runtime/Composition/CompositionContracts.cs.meta" "My project/Assets/FlowScope/Runtime/Composition/FlowScopeApp.cs" "My project/Assets/FlowScope/Runtime/Composition/FlowScopeApp.cs.meta"
```
如果 Unity `.meta` 尚未生成或未跟踪,按实际文件列表只删除存在的路径。
- [ ] **Step 3: 删除旧 Composition 测试**
```powershell
git rm "My project/Assets/FlowScope/Tests/EditMode/Composition/CompositionTests.cs" "My project/Assets/FlowScope/Tests/EditMode/Composition/CompositionTests.cs.meta"
```
- [ ] **Step 4: 删除空目录 meta**
如果目录为空,删除对应目录和 `.meta`
```powershell
git rm "My project/Assets/FlowScope/Runtime/Composition.meta" "My project/Assets/FlowScope/Tests/EditMode/Composition.meta"
```
如果目录里仍有迁移期 adapter不删除目录。
- [ ] **Step 5: 再次确认旧入口不存在**
```powershell
rg -n "FlowScope\.Composition|IRootContext|IFeatureScope|ICommandContext|IQueryContext|CreateFeatureScope|FlowScopeApp" "My project/Assets/FlowScope"
```
Expected: exit code 1表示没有匹配这里 exit code 1 是通过条件。
**建议提交点:** `refactor: 移除旧 Composition 试验入口`
## Task 9: 总体验证与验收
**Files:**
- Verify: `My project/FlowScope.Runtime.csproj`
- Verify: `My project/FlowScope.Tests.EditMode.csproj`
- Verify: `docs/specs/architecture-scope-container-implementation-spec.md`
- [ ] **Step 1: 编译 Runtime**
```powershell
dotnet build "My project/FlowScope.Runtime.csproj" --no-restore
```
Expected: Build succeeded。
- [ ] **Step 2: 编译 EditMode Tests**
```powershell
dotnet build "My project/FlowScope.Tests.EditMode.csproj" --no-restore
```
Expected: Build succeeded。
- [ ] **Step 3: 验证负向边界**
```powershell
rg -n "RootContext|FeatureScope|IRootContext|IFeatureScope|ICommandContext|IQueryContext|CreateFeatureScope|FlowScopeApp" "My project/Assets/FlowScope/Runtime"
```
Expected: exit code 1表示 Runtime 中不再存在旧试验入口。
- [ ] **Step 4: 验证第一阶段没有引入后续模块**
```powershell
rg -n "CommandContext|QueryContext|EventContext|UseMvvm|UseMvc|UIPanelLifecycle" "My project/Assets/FlowScope/Runtime/Core"
```
Expected: exit code 1表示 Core 没有提前引入 Command / Query / Event / MVC / MVVM / UI Panel 生命周期接管。
- [ ] **Step 5: 格式与空白检查**
```powershell
git diff --check
```
Expected: 无输出。
- [ ] **Step 6: 检查 staged 范围**
如果用户要求提交,执行者只应 stage 本计划相关文件:
```powershell
git add "My project/Assets/FlowScope/Runtime/Core" "My project/Assets/FlowScope/Tests/EditMode/Core" "My project/Assets/FlowScope/Runtime/Container/Container.cs" "My project/Assets/FlowScope/Runtime/Container/ContainerRegistration.cs"
```
如果旧 Composition 文件被删除,再额外 stage 精确删除路径。不要 broad add 整个项目。
**建议最终提交信息:** `feat: 实现统一 ArchitectureScope 容器树`
## 自检清单
- [ ] `FlowScope.Create()` 返回 Root `IArchitectureScope`
- [ ] Root / Feature / Child 都是同一种 `IArchitectureScope`
- [ ] `CreateFeature<TFeature>()` 默认名为 `typeof(TFeature).Name`
- [ ] `CreateFeature<TFeature>(name)` 自动创建 Scope 并调用 `Install`
- [ ] 任意 Scope 可 Register但只写自己。
- [ ] Resolve 从当前 Scope 开始,找不到向 Parent 查找。
- [ ] 子 Scope 覆盖父 Scope 时不修改父 Scope。
- [ ] Singleton 由注册所在 Scope 持有。
- [ ] PerScope 由 Resolve 发起 Scope 持有。
- [ ] Transient 每次创建新实例,并按最终规则释放。
- [ ] RegisterInstance 默认不释放外部实例。
- [ ] 子 Scope Dispose 不释放父 Scope 对象。
- [ ] 父 Scope Dispose 释放所有子 Scope。
- [ ] Dispose 幂等。
- [ ]`RootContext + FeatureScope + Command/Query` 不再是新核心入口。
- [ ] 第一阶段没有实现 Command / Query / Event、完整 MVC/MVVM、UI Panel 生命周期统一接管。