From 041fb63813b5ea815a821329ae5b408890c4aca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=90=BD=E7=AC=94?= <46271592+Skymly@users.noreply.github.com> Date: Tue, 7 Jul 2026 23:13:34 +0800 Subject: [PATCH] Migrate all pattern docs to spec/ + design/ split Split all 7 pattern documentation files from docs/.md into docs/spec/.md (stable contracts: API surface, diagnostic IDs, invariants, compatibility baseline) and docs/design/.md (implementation details: design goals, implementation overview, design tradeoffs, ecosystem boundaries, known limitations). - Strategy: spec/Strategy.md + design/Strategy.md - ChainOfResponsibility: spec/ChainOfResponsibility.md + design/ChainOfResponsibility.md - Composite: spec/Composite.md + design/Composite.md (links ADR-006, ADR-007) - FactoryRegistry: spec/FactoryRegistry.md + design/FactoryRegistry.md (fixed IReadOnlyRegistry inheritance discrepancy) - Decorator: spec/Decorator.md + design/Decorator.md - EventAggregator: spec/EventAggregator.md + design/EventAggregator.md - StateTransitionTable: spec/StateTransitionTable.md + design/StateTransitionTable.md (links ADR-005, archived RFCs) - Delete 7 old docs/.md files - Update docs/spec/README.md and docs/design/README.md with completed migration status - Update docs/README.md pattern index to point to spec/ + design/ - Update docs/DOCUMENTATION.md migration section to mark complete - Update AGENTS.md pattern doc references - Update README.md documentation table - Update docs/ROADMAP.md pattern doc references - Update ADR-005/006/007 references (remove 'pending' markers) - Update archived RFC cross-references --- AGENTS.md | 6 +- README.md | 9 +- docs/ChainOfResponsibility.md | 155 ------- docs/Composite.md | 154 ------- docs/DOCUMENTATION.md | 24 +- docs/Decorator.md | 129 ------ docs/EventAggregator.md | 195 --------- docs/FactoryRegistry.md | 177 -------- docs/README.md | 26 +- docs/ROADMAP.md | 24 +- docs/StateTransitionTable.md | 385 ----------------- docs/adr/ADR-005-state-transition-table.md | 5 +- .../ADR-006-composite-parallel-traversal.md | 5 +- ...DR-007-composite-tree-schema-validation.md | 5 +- docs/design/ChainOfResponsibility.md | 111 +++++ docs/design/Composite.md | 193 +++++++++ docs/design/Decorator.md | 128 ++++++ docs/design/EventAggregator.md | 108 +++++ docs/design/FactoryRegistry.md | 169 ++++++++ docs/design/README.md | 14 +- docs/design/StateTransitionTable.md | 197 +++++++++ docs/design/Strategy.md | 137 ++++++ .../archive/CompositeTreeSchemaValidation.md | 2 +- docs/rfc/archive/StateTransitionTable.md | 8 +- docs/spec/ChainOfResponsibility.md | 96 +++++ docs/spec/Composite.md | 144 +++++++ docs/spec/Decorator.md | 109 +++++ docs/spec/EventAggregator.md | 134 ++++++ docs/spec/FactoryRegistry.md | 232 +++++++++++ docs/spec/README.md | 24 +- docs/spec/StateTransitionTable.md | 393 ++++++++++++++++++ docs/{ => spec}/Strategy.md | 160 ++----- 32 files changed, 2270 insertions(+), 1388 deletions(-) delete mode 100644 docs/ChainOfResponsibility.md delete mode 100644 docs/Composite.md delete mode 100644 docs/Decorator.md delete mode 100644 docs/EventAggregator.md delete mode 100644 docs/FactoryRegistry.md delete mode 100644 docs/StateTransitionTable.md create mode 100644 docs/design/ChainOfResponsibility.md create mode 100644 docs/design/Composite.md create mode 100644 docs/design/Decorator.md create mode 100644 docs/design/EventAggregator.md create mode 100644 docs/design/FactoryRegistry.md create mode 100644 docs/design/StateTransitionTable.md create mode 100644 docs/design/Strategy.md create mode 100644 docs/spec/ChainOfResponsibility.md create mode 100644 docs/spec/Composite.md create mode 100644 docs/spec/Decorator.md create mode 100644 docs/spec/EventAggregator.md create mode 100644 docs/spec/FactoryRegistry.md create mode 100644 docs/spec/StateTransitionTable.md rename docs/{ => spec}/Strategy.md (63%) diff --git a/AGENTS.md b/AGENTS.md index a34a070..968f989 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -115,7 +115,7 @@ dotnet test DesignPatterns.slnx -c Release | State(M1–M2) | `ITransitionTable`、`[StateMachine]`、`[Transition]` | `StateTransitionGenerator` | | DI Health Checks | `AddDesignPatternsHealthChecks`、`IHealthCheck` | —(运行时扩展) | -模式文档:[docs/Strategy.md](docs/Strategy.md)、[docs/ChainOfResponsibility.md](docs/ChainOfResponsibility.md)、[docs/Composite.md](docs/Composite.md)、[docs/FactoryRegistry.md](docs/FactoryRegistry.md)、[docs/Decorator.md](docs/Decorator.md)、[docs/EventAggregator.md](docs/EventAggregator.md)、[docs/FactoryKeyConventions.md](docs/FactoryKeyConventions.md)、[docs/StateTransitionTable.md](docs/StateTransitionTable.md)。设计提案见 [docs/rfc/](docs/rfc/README.md),架构决策见 [docs/adr/](docs/adr/README.md)。 +模式文档:Spec(稳定契约)见 [docs/spec/](docs/spec/README.md),Design Doc(实现细节)见 [docs/design/](docs/design/README.md)。横切约定见 [docs/FactoryKeyConventions.md](docs/FactoryKeyConventions.md)。设计提案见 [docs/rfc/](docs/rfc/README.md),架构决策见 [docs/adr/](docs/adr/README.md)。 --- @@ -339,9 +339,9 @@ git push origin v0.1.0-preview3 | CHANGELOG | 在 `[Unreleased]` 下添加条目 | | 文档目录 | 不在 `docs/` 之外创建文档文件(`.Local/` 除外) | -### 现有模式文档迁移 +### 模式文档结构 -现有 `docs/.md`(Strategy.md、Composite.md 等)将逐步拆分迁移至 `docs/spec/` + `docs/design/`。迁移计划见 [docs/spec/README.md](docs/spec/README.md)。迁移可逐模式进行,不要求一次性完成。 +所有模式文档已拆分为 Spec(`docs/spec/`)+ Design Doc(`docs/design/`)。Spec 描述稳定契约(API 面、诊断 ID、不变量),Design Doc 描述实现细节(设计权衡、已知局限)。索引见 [docs/spec/README.md](docs/spec/README.md) 和 [docs/design/README.md](docs/design/README.md)。 --- diff --git a/README.md b/README.md index 0a69df3..307f1ce 100644 --- a/README.md +++ b/README.md @@ -88,13 +88,8 @@ DesignPatterns.slnx | [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) | 环境、构建、测试、架构约定 | | [docs/PUBLISHING.md](docs/PUBLISHING.md) | NuGet 发版流程 | | [docs/FactoryKeyConventions.md](docs/FactoryKeyConventions.md) | Strategy / Factory key 命名约定 | -| [docs/Strategy.md](docs/Strategy.md) | Strategy 模式设计与 API | -| [docs/ChainOfResponsibility.md](docs/ChainOfResponsibility.md) | 责任链模式设计与 API | -| [docs/Composite.md](docs/Composite.md) | Composite 模式设计与 API | -| [docs/FactoryRegistry.md](docs/FactoryRegistry.md) | Factory Registry 模式设计与 API | -| [docs/Decorator.md](docs/Decorator.md) | Decorator 模式设计与 API | -| [docs/EventAggregator.md](docs/EventAggregator.md) | Event Aggregator 模式设计与 API | -| [docs/StateTransitionTable.md](docs/StateTransitionTable.md) | State 转换表设计与 API | +| [docs/spec/](docs/spec/README.md) | 模式规范(Spec)— 稳定契约:API 面、诊断 ID、不变量 | +| [docs/design/](docs/design/README.md) | 模式设计文档(Design Doc)— 实现细节、设计权衡、已知局限 | | [docs/ROADMAP.md](docs/ROADMAP.md) | 功能与技术 backlog | | [CONTRIBUTING.md](CONTRIBUTING.md) | 贡献与测试说明 | | [AGENTS.md](AGENTS.md) | AI 编码助手项目上下文 | diff --git a/docs/ChainOfResponsibility.md b/docs/ChainOfResponsibility.md deleted file mode 100644 index 901a580..0000000 --- a/docs/ChainOfResponsibility.md +++ /dev/null @@ -1,155 +0,0 @@ -# Chain of Responsibility — 设计与实现文档 - -## 概述 - -责任链模式将请求沿 handler 链传递,每个 handler 决定是处理请求还是转发给下一个。本库提供 **中间件式管道 primitives**;与 MediatR 或 ASP.NET Core 管道在能力上可能重叠,但本库聚焦「源生成排序 + 编译期诊断」的探索价值(见 [AGENTS.md](../AGENTS.md)「项目是什么」)。 - -## 设计目标 - -1. 与 ASP.NET Core 中间件一致的 **onion 调用模型**(先注册先执行 inbound) -2. 通过 **是否调用 `next`** 实现短路,不强制业务 context 实现额外接口 -3. **异步一等**:`ValueTask` + `CancellationToken` -4. 不依赖 DI;handler 由调用方手动组装 - -## 运行时 API(`DesignPatterns/Behavioral/`) - -| 类型 | 说明 | -|------|------| -| `HandlerDelegate` | 调用链中下一阶段的委托 | -| `IHandler` | 单个 handler:`InvokeAsync(context, next, ct)` | -| `HandlerPipelineBuilder` | `Use(handler)` / `Use(delegate)`,`Build()` | -| `HandlerPipeline` | 不可变管道,`InvokeAsync(context, ct)`、`InvokeTracedAsync`(见下文) | -| `HandlerPipelineTrace` | 一次 traced 调用的逐步结果 | -| `HandlerPipelineStep` | 单步:索引、handler 显示名、状态 | -| `HandlerPipelineStepStatus` | `Completed` / `ShortCircuited` / `NotReached` | - -## 用法 - -```csharp -var pipeline = new HandlerPipelineBuilder() - .Use(new LoggingHandler()) - .Use(new AuthorizationHandler()) // 未授权时不调用 next → 短路 - .Use(new ResourceHandler()) - .Build(); - -await pipeline.InvokeAsync(new RequestContext("/api/orders", isAuthenticated: true)); -``` - -### 短路语义 - -- Handler **调用** `await next(context, ct)` → 继续后续 handler -- Handler **不调用** `next` → 后续 handler 不再执行(inbound 与 outbound 均跳过) - -### 边界行为 - -| 场景 | 行为 | -|------|------| -| 空管道 | `InvokeAsync` / `InvokeTracedAsync` 正常完成,trace 为空 | -| `Use(null)` | `ArgumentNullException` | - -### 短路可观测性(`InvokeTracedAsync`) - -F2 增强:在不引入完整中间件框架的前提下,记录**每个已注册 handler** 在一次调用中的结局,便于调试鉴权拦截、早退逻辑等。 - -```csharp -var trace = await pipeline.InvokeTracedAsync(context, cancellationToken); - -foreach (var step in trace.Steps) -{ - Console.WriteLine($"{step.Index}: {step.Name} → {step.Status}"); -} - -if (trace.Steps.Any(s => s.Status == HandlerPipelineStepStatus.NotReached)) -{ - // 有 handler 因更早的短路而从未执行 -} -``` - -| `HandlerPipelineStepStatus` | 含义 | -|----------------------------|------| -| **Completed** | Handler 已执行且调用了 `next` | -| **ShortCircuited** | Handler 已执行但**未**调用 `next`(管道在此 handler 之后不再向下) | -| **NotReached** | Handler **未执行**(因更早的 handler 短路) | - -**与 `InvokeAsync` 的关系**:`InvokeTracedAsync` 不改变执行顺序与短路语义,仅额外返回 `HandlerPipelineTrace`;委托 handler 在 trace 中显示名为 `""`。 - -**解读提示**: - -- 区分「提前拦截」与「正常终端」:看**后续** handler 是否为 `NotReached`。例如鉴权 handler 不写 `401` 且不调用 `next` 时,其后 handler 应为 `NotReached`;而链末 handler 不调用 `next` 属于常见终端写法,其自身为 `ShortCircuited`,但**不代表**前面 handler 失败。 -- `HandlerPipelineTrace.WasShortCircuited` 为「是否存在任一步为 `ShortCircuited`」;终端 handler 不调用 `next` 时也会为 `true`,不宜单独作为「业务短路」判据。 - -集成测试见 `tests/DesignPatterns.Tests/Integration/HandlerPipelineIntegrationTests.cs`(生成管道 + 鉴权通过 / 失败两条 trace 路径)。 - -## 与 Strategy / Factory 的边界 - -| 模式 | 关注点 | -|------|--------| -| **Chain** | 请求在 **有序 pipeline** 中流动,可短路 | -| **Strategy** | 按 key **选一个** 实现 | -| **Factory** | 按 key **创建一个** 新实例 | - -## 示例 - -见 [DesignPatterns.Samples.Chain](https://github.com/Skymly/DesignPatterns.Samples/tree/main/DesignPatterns.Samples.Chain):日志 → 鉴权 → 资源 handler,演示授权与未授权两种路径。 - -## 编译期:`[HandlerOrder]` 源生成器 - -### 特性 - -```csharp -// 泛型(推荐,C# 11+ / net8.0) -[HandlerOrder(10)] -public sealed class LoggingHandler : IHandler { } - -// 非泛型(netstandard2.0) -[HandlerOrder(10, typeof(RequestContext))] -public sealed class LoggingHandler : IHandler { } -``` - -数值越小越先执行(与手动 `Use` 注册顺序一致)。 - -同一 handler 类可标注多个 `[HandlerOrder<...>]`(`AllowMultiple = true`),分别加入不同 `TContext` 的生成管道,例如共享日志 handler 同时实现 `IHandler` 与 `IHandler`。同一 context 下重复的 Order 仍报 **DP005**。 - -### 生成输出 - -对每种 `TContext` 生成 `{Context}HandlerPipeline`: - -```csharp -public static partial class RequestContextHandlerPipeline -{ - public static HandlerPipeline Instance { get; } -} -``` - -### 诊断 - -| ID | 说明 | -|----|------| -| DP005 | 同一 context 下重复的 Order | -| DP008 | 未实现 `IHandler` | -| DP009 | 缺少 public 无参构造 | -| DP024 | Info | Analyzer | 实现了 `IHandler` 但未加 `[HandlerOrder]`(该 context 已有其它 handler 注册) | -| DP024 | Info | CodeFix | 一键添加 `[HandlerOrder(order, typeof(TContext))]` | - -## DI 集成 - -引用 `DesignPatterns.Extensions.DependencyInjection` 后,`{Context}HandlerPipeline` 生成: - -```csharp -RequestContextHandlerPipeline.RegisterDi(services); -var pipeline = provider.GetRequiredService>(); -``` - -`Create(IServiceProvider sp)` 按 `[HandlerOrder]` 顺序 `GetRequiredService` 各 handler。Singleton 管道在首次构建时固定 handler 实例;需要每次解析新管道时使用 `registryLifetime: ServiceLifetime.Transient`。 - -手动注册:`services.AddHandlerPipeline(builder => ...)`(扩展包)。 - -## 后续 - -- Autofac / DryIoc 扩展(可选) - -## 参考 - -- GoF: Chain of Responsibility (Behavioral) -- [docs/DEVELOPMENT.md](DEVELOPMENT.md) -- [AGENTS.md](../AGENTS.md) diff --git a/docs/Composite.md b/docs/Composite.md deleted file mode 100644 index df7b10c..0000000 --- a/docs/Composite.md +++ /dev/null @@ -1,154 +0,0 @@ -# Composite — 设计与实现文档 - -## 概述 - -Composite 模式将对象组织成树形结构,以统一方式对待单个对象与组合对象。本库提供 **遍历 primitives** 与 **编译期 catalog 校验**,不替代完整 UI 组件框架或持久化树存储。 - -## 设计目标 - -1. **统一节点接口**:`ICompositeNode` 通过 `Children` 区分叶子与分支 -2. **多种遍历策略**:深度优先(前/后序)、广度优先 -3. **Catalog 装配**:`[CompositePart]` 生成 flat catalog,`BuildRoot()` 组装单根树,`BuildForest()` 组装多根森林 -4. 不依赖 DI;树由编译期定义、运行时一次性装配 - -## 运行时 API(`DesignPatterns/Structural/`) - -| 类型 | 说明 | -|------|------| -| `ICompositeNode` | 节点契约:`Children` | -| `ICompositeBuildable` | 装配时接收子节点:`SetChildren(IReadOnlyList)` | -| `CompositeTraverser` | 同步/异步遍历单根树或森林 | -| `CompositeTraversalOptions` | 顺序、MaxDepth、仅叶子、跳过子树 | -| `CompositeTreeBuilder` | 手动 `Leaf` / `Branch` / `Build()` | -| `CompositeCatalogEntry` | Catalog 条目(key、parent、order、类型) | -| `CompositeCatalogAssembler` | 从 catalog 装配单根(`Assemble`)或多根森林(`AssembleForest`) | - -## 用法 - -### 手动构建 - -```csharp -var root = new MenuBranch("Home"); -var child = new MenuLeaf("Settings"); - -var tree = new CompositeTreeBuilder() - .Branch(root, b => b.Leaf(child)) - .Build(); - -CompositeTraverser.Traverse(tree, (node, depth, _) => - Console.WriteLine($"{new string(' ', depth * 2)}{node.Title}")); -``` - -### 编译期 Catalog - -```csharp -[CompositePart("root")] -public sealed class HomeMenu : IMenuNode, ICompositeBuildable { ... } - -[CompositePart("settings", ParentKey = "root", Order = 10)] -public sealed class SettingsMenu : IMenuNode, ICompositeBuildable { ... } - -var root = MenuNodeCompositeCatalog.BuildRoot(); -``` - -### 森林 / 多根 Catalog - -当 catalog 含多个 `ParentKey == null` 的根时,使用 `BuildForest()` 与 `TraverseForest`: - -```csharp -[CompositePart("home")] -public sealed class HomeMenu : IMenuNode, ICompositeBuildable { ... } - -[CompositePart("admin", Order = 10)] -public sealed class AdminMenu : IMenuNode, ICompositeBuildable { ... } - -[CompositePart("settings", ParentKey = "home", Order = 0)] -public sealed class SettingsMenu : IMenuNode, ICompositeBuildable { ... } - -var forest = MenuNodeCompositeCatalog.BuildForest(); - -CompositeTraverser.TraverseForest(forest, (node, depth, rootIndex) => - Console.WriteLine($"root={rootIndex} depth={depth} {node.Title}")); -``` - -也可手动装配森林: - -```csharp -var entries = new[] -{ - new CompositeCatalogEntry("home", null, 0, typeof(HomeMenu)), - new CompositeCatalogEntry("admin", null, 10, typeof(AdminMenu)), - new CompositeCatalogEntry("settings", "home", 0, typeof(SettingsMenu)), -}; - -var forest = CompositeCatalogAssembler.AssembleForest(entries); -``` - -`BuildRoot()` 仍要求 catalog 中**恰好一个**根;多根 catalog 调用 `BuildRoot()` 会在运行时抛 `CompositeAssemblyException`。 - -### 装配约定 - -| 要求 | 说明 | -|------|------| -| `ICompositeBuildable` | `TContract` 为 composite 接口(如 `IMenuNode`) | -| public 无参构造 | 与 Strategy/Handler 生成器一致 | -| 单根(`BuildRoot`) | catalog 中恰好一个 `ParentKey == null` | -| 多根(`BuildForest`) | 一个或多个 `ParentKey == null`,按 `Order` 再 key 排序 | -| `SetChildren` 一次性 | 装配后实现类应冻结 children | - -## 与 Chain / Strategy 的边界 - -| 模式 | 结构 | 关注点 | -|------|------|--------| -| **Composite** | 树 | 统一访问部分与整体、遍历 | -| **Chain** | 线性 pipeline | 有序执行、可短路 | -| **Strategy** | 扁平 key → 实现 | 选一个算法 | - -## 示例 - -见 [DesignPatterns.Samples.Composite](https://github.com/Skymly/DesignPatterns.Samples/tree/main/DesignPatterns.Samples.Composite): - -1. **Catalog 森林**:`MenuNodeCompositeCatalog.BuildForest()` + `TraverseForest`(多根 `[CompositePart]`;多根时 `BuildRoot()` 抛错) -2. **手动**:`CompositeTreeBuilder()` 的 `Leaf` / `Branch` / `Build()`(`ManualMenuNodes`,无 catalog 特性) - -## 编译期:`[CompositePart]` 源生成器 - -### 特性 - -```csharp -// 泛型(推荐,C# 11+ / net8.0) -[CompositePart("settings", ParentKey = "root", Order = 10)] -public sealed class SettingsMenu : IMenuNode, ICompositeBuildable { } - -// 非泛型(netstandard2.0) -[CompositePart("settings", typeof(IMenuNode), ParentKey = "root", Order = 10)] -public sealed class SettingsMenu : IMenuNode, ICompositeBuildable { } -``` - -### 生成输出 - -对每个 contract 生成(`IPaymentStrategy` → `PaymentStrategyCompositeKeys`;`IMenuNode` → `MenuNodeCompositeKeys`): - -- `{Contract}CompositeKeys` — key 常量 -- `{Contract}CompositeCatalog` — entry 列表 + `BuildRoot()` + `BuildForest()` - -### 诊断 - -| ID | 说明 | -|----|------| -| DP010 | 同一 contract 下 key 重复 | -| DP011 | ParentKey 不存在 | -| DP012 | parent 链形成环 | -| DP013 | 未实现 composite contract | -| DP014 | 缺少 public 无参构造 | -| DP015 | 未实现 `ICompositeBuildable` | - -## 仍不支持 - -- 运行时动态增删节点 -- DI 自动注册 `BuildRoot()` / `BuildForest()` 结果 - -## 参考 - -- GoF: Composite (Structural) -- [docs/DEVELOPMENT.md](DEVELOPMENT.md) diff --git a/docs/DOCUMENTATION.md b/docs/DOCUMENTATION.md index 5d11cbe..3c003f6 100644 --- a/docs/DOCUMENTATION.md +++ b/docs/DOCUMENTATION.md @@ -403,25 +403,25 @@ docs/ └── PluginAssemblies.md ``` -### 8.1 现有模式文档的迁移 +### 8.1 现有模式文档的迁移(已完成) -现有 `docs/.md`(Strategy.md、Composite.md 等)将逐步拆分迁移至 `docs/spec/` + `docs/design/`: +所有 7 个模式文档已从 `docs/.md` 拆分迁移至 `docs/spec/` + `docs/design/`: -| 现有文件 | 迁移目标 | -|----------|----------| -| `docs/Strategy.md` | `docs/spec/Strategy.md` + `docs/design/Strategy.md` | -| `docs/ChainOfResponsibility.md` | `docs/spec/ChainOfResponsibility.md` + `docs/design/ChainOfResponsibility.md` | -| `docs/Composite.md` | `docs/spec/Composite.md` + `docs/design/Composite.md` | -| `docs/FactoryRegistry.md` | `docs/spec/FactoryRegistry.md` + `docs/design/FactoryRegistry.md` | -| `docs/Decorator.md` | `docs/spec/Decorator.md` + `docs/design/Decorator.md` | -| `docs/EventAggregator.md` | `docs/spec/EventAggregator.md` + `docs/design/EventAggregator.md` | -| `docs/StateTransitionTable.md` | `docs/spec/StateTransitionTable.md` + `docs/design/StateTransitionTable.md` | +| 原文件 | Spec | Design Doc | +|--------|------|------------| +| `docs/Strategy.md` | `docs/spec/Strategy.md` | `docs/design/Strategy.md` | +| `docs/ChainOfResponsibility.md` | `docs/spec/ChainOfResponsibility.md` | `docs/design/ChainOfResponsibility.md` | +| `docs/Composite.md` | `docs/spec/Composite.md` | `docs/design/Composite.md` | +| `docs/FactoryRegistry.md` | `docs/spec/FactoryRegistry.md` | `docs/design/FactoryRegistry.md` | +| `docs/Decorator.md` | `docs/spec/Decorator.md` | `docs/design/Decorator.md` | +| `docs/EventAggregator.md` | `docs/spec/EventAggregator.md` | `docs/design/EventAggregator.md` | +| `docs/StateTransitionTable.md` | `docs/spec/StateTransitionTable.md` | `docs/design/StateTransitionTable.md` | 迁移遵循以下规则: - **API 面、诊断 ID 表、不变量、兼容基线** → Spec - **实现概览、设计权衡、与生态边界、已知局限、示例** → Design Doc - 横切约定文档(`FactoryKeyConventions.md`、`AppSettings.md` 等)保留在 `docs/` 根目录,不迁移。 -- 迁移可逐模式进行,不要求一次性完成。迁移时创建一个独立 PR。 +- 旧文件已删除。 --- diff --git a/docs/Decorator.md b/docs/Decorator.md deleted file mode 100644 index e2d65d0..0000000 --- a/docs/Decorator.md +++ /dev/null @@ -1,129 +0,0 @@ -# Decorator — 设计与实现文档 - -## 概述 - -Decorator(装饰器)在**不修改核心类型**的前提下,为同一契约接口叠加横切或增强行为(日志、缓存、指标等)。本库提供 **可组合的包装栈 primitives** 与 **`[Decorator]` 编译期排序**,不替代 ASP.NET Core 中间件、动态代理或完整 AOP 框架。 - -## 设计目标 - -1. **同一契约、显式内核**:`DecoratorStackBuilder.Build(core)` 必须传入真实核心实现 -2. **顺序可预测**:先注册 / `Order` 越小越靠外(先接到调用,再委托向内) -3. **零 DI 依赖**:手动组装;DI 扩展留 P3 -4. **编译期胶水**:生成 `{Contract}DecoratorStack.Build(core)` 与 `{Contract}DecoratorOrder` 常量,不生成装饰方法体 - -## 与 Chain of Responsibility 的边界 - -| 维度 | Chain | Decorator | -|------|-------|-----------| -| 抽象 | `IHandler` | 装饰器与核心均实现 **`TService`** | -| 调用 | `next` 可短路 | 通常委托 `inner` | -| 核心 | 无单一内核服务 | 显式 **`core`** 实例 | -| 特性 | `[HandlerOrder]` | `[Decorator]` | - -## 运行时 API(`DesignPatterns/Structural/`) - -| 类型 | 说明 | -|------|------| -| `IDecorator` | `TService Decorate(TService inner)` | -| `IDecoratorOf` | 可选标记接口(生成器不强制) | -| `DecoratorStackBuilder` | `Add()` / `Add(instance)` / `Add(..., Func)` / `Build(core)` | - -### 手动组装 - -```csharp -var service = new DecoratorStackBuilder() - .Add() - .Add() - .Build(new PaymentService()); -``` - -### 条件装饰 - -`Add` 的重载可传入 `Func`;谓词在 **`Build` 时**求值,为 `false` 时跳过该装饰器(返回 inner 不变): - -```csharp -var enableMetrics = configuration.GetValue("Metrics:Enabled"); - -var service = new DecoratorStackBuilder() - .Add() - .Add(() => enableMetrics) - .Build(new PaymentService()); -``` - -生成器产出的 `{Contract}DecoratorStack.Build(core)` 仍注册全部 `[Decorator]` 类型;条件开关仅适用于手动 `DecoratorStackBuilder` 组装。 - -### 边界行为 - -| 场景 | 行为 | -|------|------| -| `Build(null)` / `Add(null)` | `ArgumentNullException` | -| `Add(..., null)` 谓词 | `ArgumentNullException` | -| 无装饰器 | 返回同一 `core` 引用 | - -## 编译期:`[Decorator]` 源生成器 - -### 特性 - -```csharp -// 泛型(推荐,C# 11+ / net8.0) -[Decorator(10)] -public sealed class LoggingPaymentDecorator : IPaymentService, IDecorator { ... } - -// 非泛型(netstandard2.0) -[Decorator(10, typeof(IPaymentService))] -public sealed class LoggingPaymentDecorator : IPaymentService, IDecorator { ... } -``` - -### 生成输出 - -对 `IPaymentService` → `PaymentServiceDecoratorStack` 与 `PaymentServiceDecoratorOrder`: - -```csharp -public static partial class PaymentServiceDecoratorStack -{ - public static IPaymentService Build(IPaymentService core) { ... } -} - -public static partial class PaymentServiceDecoratorOrder -{ - public const int LoggingPaymentDecorator = 10; - public const int MetricsPaymentDecorator = 20; -} -``` - -`DecoratorOrder` 常量名取装饰器**简单类型名**;同一 contract 下类型名须唯一(跨命名空间同名会导致重复常量编译错误)。重复 `Order` 仍由 **DP016** 在编译期报告。 - -特性中可引用常量替代魔法数: - -```csharp -[Decorator(PaymentServiceDecoratorOrder.LoggingPaymentDecorator)] -public sealed class LoggingPaymentDecorator : IPaymentService, IDecorator { ... } -``` - -### 诊断 - -| ID | 说明 | -|----|------| -| DP016 | 同一 contract 下重复 Order | -| DP017 | 未实现 service contract | -| DP018 | 未实现 `IDecorator` | -| DP019 | 缺少 public 无参构造 | - -CodeFix:DP017(加契约接口)、DP019(加无参构造)。DP018 需手写 `Decorate` 方法。 - -## 示例 - -见 [DesignPatterns.Samples.Decorator](https://github.com/Skymly/DesignPatterns.Samples/tree/main/DesignPatterns.Samples.Decorator):日志 + 耗时装饰、`DecoratorOrder` 常量引用、条件 `Add` 与 `PaymentServiceDecoratorStack.Build(core)` 对比。 - -## v1 不支持 - -- `DispatchProxy` / 透明代理 -- 装饰器 DAG 或按 key 选子集 -- 生成器为 `[Decorator]` 自动接线运行时谓词(条件装饰仅手动 builder) -- DI 自动注册 - -## 参考 - -- GoF: Decorator (Structural) -- [ChainOfResponsibility.md](ChainOfResponsibility.md) -- [docs/DEVELOPMENT.md](DEVELOPMENT.md) diff --git a/docs/EventAggregator.md b/docs/EventAggregator.md deleted file mode 100644 index 9267b60..0000000 --- a/docs/EventAggregator.md +++ /dev/null @@ -1,195 +0,0 @@ -# Event Aggregator — 设计与实现文档 - -## 概述 - -Event Aggregator 提供进程内、轻量的 **发布/订阅(pub/sub)** 机制:按事件类型路由到已订阅的处理器,无需引入完整消息总线框架。 - -## 设计目标 - -1. 最小 API:`Subscribe` / `Unsubscribe` / `PublishAsync` -2. 异步一等:`IEventHandler.HandleAsync` 与 `CancellationToken` -3. 线程安全:订阅表在锁下修改,发布时对处理器列表做快照 -4. 不侵入 Core 的 DI 依赖;可选 `AddEventAggregator` 扩展 - ---- - -## 运行时 API(`DesignPatterns/Behavioral/`) - -| 类型 | 职责 | -|------|------| -| `IEventHandler` | 处理指定事件类型 | -| `IEventAggregator` | 订阅、取消订阅、发布 | -| `EventAggregator` | 默认实现 | - -### 事件处理器 - -```csharp -public interface IEventHandler -{ - ValueTask HandleAsync(TEvent evt, CancellationToken cancellationToken = default); -} -``` - -任意类实现该接口即可作为处理器;**不要求**继承基类或注册特性。 - -### 聚合器 - -```csharp -public interface IEventAggregator -{ - void Subscribe(IEventHandler handler); - void Unsubscribe(IEventHandler handler); - ValueTask PublishAsync(TEvent evt, CancellationToken cancellationToken = default); -} -``` - -### 基本用法 - -```csharp -var aggregator = new EventAggregator(); - -aggregator.Subscribe(new EmailNotificationHandler()); -aggregator.Subscribe(new AuditLogHandler()); - -await aggregator.PublishAsync(new OrderPlacedEvent("ORD-001", 99.99m)); - -aggregator.Unsubscribe(auditHandler); -await aggregator.PublishAsync(new OrderPlacedEvent("ORD-002", 49.99m)); -``` - -事件类型 `TEvent` 可以是 `class`、`struct` 或 `record`;同一聚合器上可并存多种 `TEvent`。 - ---- - -## 发布语义 - -- **顺序**:同一事件类型的处理器按 **订阅顺序** 依次调用。 -- **并发**:`PublishAsync` 在快照列表上顺序 `await` 各 `HandleAsync`;不同事件类型互不影响。 -- **空订阅**:无处理器时 `PublishAsync` 立即完成。 -- **取消**:在调用每个处理器前检查 `cancellationToken`;处理器内也应协作取消。 - ---- - -## 线程安全 - -- `Subscribe` / `Unsubscribe` 在内部锁上修改 `Dictionary>`。 -- `PublishAsync` 在锁内复制当前处理器列表为 **快照**,随后在锁外调用,避免在 `HandleAsync` 执行期间持有锁。 -- 同一 `handler` 实例可重复 `Subscribe`(会多次出现在列表中);`Unsubscribe` 移除**第一次**匹配项。 - ---- - -## DI 集成 - -独立包 `DesignPatterns.Extensions.DependencyInjection`: - -```csharp -services.AddEventAggregator(); // 默认 Singleton -``` - -注册 `IEventAggregator` → `EventAggregator` 实例。处理器本身仍由应用注册到 DI 并手动 `Subscribe`,或由应用在启动时注入 `IEventAggregator` 后订阅。 - ---- - -## 源生成器(`[RegisterEventHandler]`) - -`RegisterEventHandlerGenerator` 在编译期扫描 `[RegisterEventHandler]` 特性,按 **event type 分组** 生成 `{Event}EventHandlerRegistry` 静态类,消除手动 `Subscribe` 样板代码。 - -### 特性 - -| 特性 | 形态 | 适用 TFM | -|------|------|----------| -| `[RegisterEventHandler(typeof(FooEvent))]` | 非泛型 | 全部(netstandard2.0+) | -| `[RegisterEventHandler]` | 泛型 | `#if NET7_0_OR_GREATER`(C# 11+ generic attributes) | - -与 `[RegisterStrategy]` / `[RegisterFactory]` 的双形态模式一致。 - -### 生成产物 - -对每个被标注的 event type,生成 `{Event}EventHandlerRegistry`(`Event` 后缀自动剥离,如 `OrderPlacedEvent` → `OrderPlacedEventHandlerRegistry`): - -| 成员 | 路径 | 条件 | -|------|------|------| -| `SubscribeAll(IEventAggregator)` | 静态:`new Handler()` + `Subscribe` | handler 有公共无参构造 | -| `RegisterDi(IServiceCollection, ServiceLifetime)` | DI:注册 handler 实现到容器 | `DesignPatterns_EnableDiIntegration=true` | -| `SubscribeAll(IEventAggregator, IServiceProvider)` | DI:从容器解析 handler + `Subscribe` | `DesignPatterns_EnableDiIntegration=true` | - -**静态路径**仅包含有公共无参构造的 handler;**DI 路径**包含全部有效 handler。这与其他生成器的无参构造约束一致(DP007/DP009/DP014/DP019/DP022 同源)。 - -### 用法 - -#### 静态路径(无 DI 依赖) - -```csharp -public record OrderPlacedEvent(string OrderId); - -[RegisterEventHandler] -public sealed class LogOrderHandler : IEventHandler -{ - public ValueTask HandleAsync(OrderPlacedEvent evt, CancellationToken ct = default) => default; -} - -// 启动时 -var aggregator = new EventAggregator(); -OrderPlacedEventHandlerRegistry.SubscribeAll(aggregator); -await aggregator.PublishAsync(new OrderPlacedEvent("ORD-001")); -``` - -#### DI 路径(两步) - -```csharp -// 1. 注册聚合器 + handler 实现 -services.AddEventAggregator(); -OrderPlacedEventHandlerRegistry.RegisterDi(services); - -// 2. 启动时从容器解析并订阅 -var provider = services.BuildServiceProvider(); -var aggregator = provider.GetRequiredService(); -OrderPlacedEventHandlerRegistry.SubscribeAll(aggregator, provider); -``` - -`RegisterDi` 默认 `implementationLifetime: ServiceLifetime.Transient`(每次解析新实例,因 handler 通常无状态)。 - -### 诊断 - -| ID | 归属 | 语义 | -|----|------|------| -| DP044 | Analyzer | 实现 `IEventHandler` 但未标注 `[RegisterEventHandler]`(Info) | -| DP045 | Generator | 同一 handler 类对同一 event type 重复标注 `[RegisterEventHandler]`(Error) | -| DP046 | Generator | 标注 `[RegisterEventHandler]` 但类未实现 `IEventHandler`(Error) | - ---- - -## 与 MediatR / 消息总线的差异 - -> 本库以技术探索为目的,**允许与 MediatR 能力重叠**。下表用于说明当前实现的设计取向差异,供选型参考,**非**「不实现」的理由(见 [AGENTS.md](../AGENTS.md)「项目是什么」)。 - -| | Event Aggregator | 典型 MediatR | -|---|---|---| -| 范围 | 进程内、同 AppDomain | 可扩展管道、行为、请求/通知模型 | -| 路由 | 按 `TEvent` 类型 | 按 Request/Notification 类型 + 管道 | -| 编译期 | `[RegisterEventHandler]` 源生成器 + DP044/045/046 诊断 | 通常基于约定或显式注册 | - -当前实现**不**做跨进程、持久化、重试策略或请求/响应关联 ID;这些是后续探索候选,准入标准见 [ROADMAP.md](ROADMAP.md) F3。 - ---- - -## 示例 - -见 [DesignPatterns.Samples.EventAggregator](https://github.com/Skymly/DesignPatterns.Samples/tree/main/DesignPatterns.Samples.EventAggregator)。 - ---- - -## 不做 - -- 不做弱事件 / 委托语法糖包装 -- 不做按名称或 topic 字符串路由(仅按 CLR 类型) -- 不在 Core 中引用 DI 容器 -- 不用反射扫描程序集自动订阅(改由 `[RegisterEventHandler]` 源生成器在编译期收集 handler) - ---- - -## 参考 - -- 进程内 Observer / Mediator 变体(Behavioral) -- [AGENTS.md](../AGENTS.md) — 项目规则与里程碑 -- [docs/DEVELOPMENT.md](DEVELOPMENT.md) — 通用开发约定 diff --git a/docs/FactoryRegistry.md b/docs/FactoryRegistry.md deleted file mode 100644 index 2174833..0000000 --- a/docs/FactoryRegistry.md +++ /dev/null @@ -1,177 +0,0 @@ -# Factory Registry — 设计与实现文档 - -## 概述 - -Factory Registry 按 key 解析**工厂实现**,每次 `Create` 调用对应 factory 委托得到新产品实例。本库提供: - -- **运行时**:`IFactoryRegistry`、`FactoryRegistryBuilder` 手动注册 -- **编译期**:`[RegisterFactory]` / `[RegisterFactory]` 源生成器 → 强类型 Keys + 静态 Registry -- **Analyzer**:DP023 提示未标记的工厂实现(与 Strategy DP006 对称) - -## 设计目标 - -1. 最小 API:`TryCreate` / `Create` / `Keys` -2. 显式失败:`FactoryNotFoundException` 带 key 信息 -3. 不侵入 Core 的 DI 依赖;生命周期由 factory 委托或 DI 扩展包负责 - ---- - -## 运行时 API(`DesignPatterns/Creational/`) - -| 类型 | 职责 | -|------|------| -| `IFactoryRegistry` | 只读注册表 + 按 key 创建 | -| `FactoryRegistry` | 不可变实现 | -| `FactoryRegistryBuilder` | 手动注册 `Func` 或 `Func` | -| `FactoryNotFoundException` | key 未注册时抛出 | - -> `IFactoryRegistry` **不**继承 `IReadOnlyRegistry`;该共享抽象仅由 `IStrategyRegistry` 使用。 - -### 手动 Builder(无生成器时) - -```csharp -using DesignPatterns.Creational; - -public interface IProduct -{ - string Name { get; } -} - -var registry = new FactoryRegistryBuilder() - .Register("standard", () => new StandardProduct()) - .Register("premium", () => new PremiumProduct()) - .Build(); - -var product = registry.Create("standard"); -if (!registry.TryCreate("unknown", out _)) -{ - // key 不存在 -} -``` - -- `Register(key, Func)`:每次 `Create(key)` 调用该 factory -- `Register(key, Func)`:factory 接收 key -- 重复 key 在 `Register` 时抛 `ArgumentException`(**运行时**检测) - ---- - -## 编译期:`[RegisterFactory]` 源生成器 - -### 特性 - -```csharp -// 泛型 Attribute(C# 11+ / net7+ 消费者) -[RegisterFactory("standard")] -public class StandardFactory : IProductFactory { ... } - -// 非泛型(netstandard2.0) -[RegisterFactory("standard", typeof(IProductFactory))] -public class StandardFactory : IProductFactory { ... } -``` - -工厂契约一般为 **接口**(或基类);实现类需 **public 无参构造**,以便生成器注册 `() => new Implementation()`。 - -### 生成器输出 - -对每个契约 `TContract`(命名规则与 Strategy 相同:去掉前缀 `I` 等): - -#### 1. 强类型 Key 常量 - -```csharp -// ProductFactoryKeys.g.cs -public static partial class ProductFactoryKeys -{ - public const string Standard = "standard"; - public const string Premium = "premium"; -} -``` - -#### 2. 静态注册表 - -```csharp -// ProductFactoryRegistry.g.cs -public static partial class ProductFactoryRegistry -{ - public static IFactoryRegistry Create() { ... } -} -``` - -`Create()` 返回的注册表在每次 `Create(key)` 时 **新建** 产品实例(与 Strategy 注册表返回单例实例不同)。 - ---- - -## 诊断规则 - -| ID | 严重性 | 来源 | 触发条件 | -|----|--------|------|----------| -| DP020 | Error | 源生成器 | 同一 `TContract` 下 key 重复 | -| DP021 | Error | 源生成器 | 标记的类未实现指定的 `TContract` | -| DP022 | Error | 源生成器 | 标记的类缺少 public 无参构造 | -| DP023 | Info | Analyzer | 实现了某工厂契约但未加 `[RegisterFactory]` | - -CodeFix:DP023 可一键添加 `[RegisterFactory("suggested-key", typeof(TContract))]`(与 DP006 对称)。 - ---- - -## 与 Strategy Registry 的对比 - -| | Strategy Registry | Factory Registry | -|---|---|---| -| 注册内容 | key → **实例** | key → **每次 Create 新建** | -| 编译期 | `[RegisterStrategy]` | `[RegisterFactory]` | -| 共享抽象 | `IStrategyRegistry` → `IReadOnlyRegistry` | 无 | -| 未找到 | `StrategyNotFoundException` | `FactoryNotFoundException` | -| 未注册实现 | DP006 (Info) | DP023 (Info) | - -详见 [Strategy.md](Strategy.md)。 - ---- - -## DI 集成 - -引用 `DesignPatterns.Extensions.DependencyInjection` 后,生成器为 `{Contract}FactoryRegistry` 增加: - -```csharp -ProductFactoryRegistry.RegisterDi(services); // TryAdd 各实现 + IFactoryRegistry<,> -// 或 -services.AddFactoryRegistry(builder => { ... }); // 手动 Builder -``` - -`Create(IServiceProvider sp)` 使用 `() => sp.GetRequiredService()` 注册工厂委托;**每次** `registry.Create(key)` 调用委托。 - -**默认 `implementationLifetime = Transient`**:工厂模式的语义是每次 `Create`/`CreateAsync` 返回新产品实例,因此实现类型默认注册为 `Transient`。如需共享同一实现实例(如内部缓存状态),可显式传 `Singleton`: - -```csharp -// 默认:Transient(每次 Create 返回新产品) -ProductFactoryRegistry.RegisterDi(services); - -// 显式 Singleton(多次 Create 返回同一实现实例) -ProductFactoryRegistry.RegisterDi(services, implementationLifetime: ServiceLifetime.Singleton); -``` - -> **注意**:此默认值与 Strategy/Chain 等模式不同(后者默认 Singleton),因为工厂的语义是"创建新实例"而非"解析同一实例"。EventAggregator 也使用 Transient 默认值(handler 通常无状态)。 - -无 DI 包时仍可使用静态 `Create()`(`new Impl()`)。 - ---- - -## 示例 - -- 手动 Builder:[DesignPatterns.Samples.Factory](https://github.com/Skymly/DesignPatterns.Samples/tree/main/DesignPatterns.Samples.Factory) -- 生成器 + 集成测试:`tests/DesignPatterns.Tests/Integration/FactoryRegistryIntegrationTests.cs` - ---- - -## 不做 - -- 不做抽象工厂族(多个产品类型族)的完整框架 -- 不在 Core 中引用 DI 容器 -- 不用反射扫描程序集注册 factory - ---- - -## 参考 - -- GoF: Factory Method / 注册表变体(Creational) -- [Strategy.md](Strategy.md) — 策略注册表对比 -- [AGENTS.md](../AGENTS.md) — 项目规则与里程碑 diff --git a/docs/README.md b/docs/README.md index d42615b..54de9c0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -29,19 +29,19 @@ | [spec/](spec/README.md) | Spec — 模式稳定契约(API 面、诊断 ID、不变量) | | [design/](design/README.md) | Design Doc — 实现细节、设计权衡、已知局限 | -> 现有模式文档(`docs/.md`)将逐步拆分迁移至 `spec/` + `design/`,见 [spec/README.md](spec/README.md#迁移计划)。 - -## 模式设计(现有文档,待迁移) - -| 文档 | 模式 | -|------|------| -| [Strategy.md](Strategy.md) | Strategy + `[RegisterStrategy]` | -| [ChainOfResponsibility.md](ChainOfResponsibility.md) | 责任链 + `[HandlerOrder]` | -| [Composite.md](Composite.md) | Composite + `[CompositePart]` | -| [FactoryRegistry.md](FactoryRegistry.md) | Factory Registry + `[RegisterFactory]` | -| [Decorator.md](Decorator.md) | Decorator + `[Decorator]` | -| [EventAggregator.md](EventAggregator.md) | Event Aggregator | -| [StateTransitionTable.md](StateTransitionTable.md) | State 转换表 + `[StateMachine]` / `[Transition]` | +> 所有模式文档已从 `docs/.md` 拆分迁移至 `spec/` + `design/`,见 [spec/README.md](spec/README.md)。 + +## 模式索引 + +| 模式 | Spec | Design Doc | +|------|------|------------| +| Strategy | [spec/Strategy.md](spec/Strategy.md) | [design/Strategy.md](design/Strategy.md) | +| Chain of Responsibility | [spec/ChainOfResponsibility.md](spec/ChainOfResponsibility.md) | [design/ChainOfResponsibility.md](design/ChainOfResponsibility.md) | +| Composite | [spec/Composite.md](spec/Composite.md) | [design/Composite.md](design/Composite.md) | +| Factory Registry | [spec/FactoryRegistry.md](spec/FactoryRegistry.md) | [design/FactoryRegistry.md](design/FactoryRegistry.md) | +| Decorator | [spec/Decorator.md](spec/Decorator.md) | [design/Decorator.md](design/Decorator.md) | +| Event Aggregator | [spec/EventAggregator.md](spec/EventAggregator.md) | [design/EventAggregator.md](design/EventAggregator.md) | +| State Transition Table | [spec/StateTransitionTable.md](spec/StateTransitionTable.md) | [design/StateTransitionTable.md](design/StateTransitionTable.md) | ## 横切约定 diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 13eb5f7..da8f7f1 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -20,7 +20,7 @@ | Event Aggregator | `{Event}EventHandlerRegistry` | | State | `{StateEnum}TransitionTable`、partial `{Holder}` 便捷方法 | -新增生成器必须沿用此命名风格(详见 [Decorator.md](Decorator.md))。诊断 ID 续接现有区段,下一个可用 ID 为 **DP066**(DP063–DP065 为 Composite 树 schema 校验,DP060–DP062 为 DI 生命周期校验,DP056–DP059 为 State hierarchy,DP053–DP055 为 Factory async + pooling 签名/池化校验,DP050–DP052 为 Handler guard 签名校验,DP047–DP049 为 Strategy guard 签名校验,DP044–DP046 为 EventAggregator 源生成器 + Analyzer 诊断,DP042–DP043 为 Decorator DI + async 签名校验,DP040–DP041 为 Composite DI + visitor 覆盖校验,DP037–DP039 为 State entry/exit action 诊断,DP032–DP035 为 State guard 诊断,DP036 为 State 字面量边校验;ID 一经发布不复用,详见 [AGENTS.md](../AGENTS.md))。 +新增生成器必须沿用此命名风格(详见 [Decorator.md](design/Decorator.md))。诊断 ID 续接现有区段,下一个可用 ID 为 **DP066**(DP063–DP065 为 Composite 树 schema 校验,DP060–DP062 为 DI 生命周期校验,DP056–DP059 为 State hierarchy,DP053–DP055 为 Factory async + pooling 签名/池化校验,DP050–DP052 为 Handler guard 签名校验,DP047–DP049 为 Strategy guard 签名校验,DP044–DP046 为 EventAggregator 源生成器 + Analyzer 诊断,DP042–DP043 为 Decorator DI + async 签名校验,DP040–DP041 为 Composite DI + visitor 覆盖校验,DP037–DP039 为 State entry/exit action 诊断,DP032–DP035 为 State guard 诊断,DP036 为 State 字面量边校验;ID 一经发布不复用,详见 [AGENTS.md](../AGENTS.md))。 诊断 ID 预分配(F2+ 增强项,登记后不提前占用,实现时按序领取): @@ -112,7 +112,7 @@ | DI 健康检查 + 生命周期校验 | 生成 `IHealthCheck` 校验注册表键可解析;Analyzer 警告无效 lifetime 组合(Singleton registry + Transient impl) | ⭐⭐ 生产场景刚需 | | Singleton 生命周期诊断 | Analyzer 检测 singleton 捕获 scoped/transient 引用;async 初始化支持 | ⭐⭐ 防 DI 反模式 | -State 转换表 v1 已于 0.1.0-preview4 发布;v2(guard 委托、DI 集成、DP036 字面量边校验)已实现,见 [StateTransitionTable.md](StateTransitionTable.md) 与 [rfc/StateTransitionTable.md](rfc/StateTransitionTable.md)。EventAggregator 联动示例仍为候选。 +State 转换表 v1 已于 0.1.0-preview4 发布;v2(guard 委托、DI 集成、DP036 字面量边校验)已实现,见 [StateTransitionTable.md](design/StateTransitionTable.md) 与 [rfc/StateTransitionTable.md](rfc/StateTransitionTable.md)。EventAggregator 联动示例仍为候选。 --- @@ -168,19 +168,19 @@ State 转换表 v1 已于 0.1.0-preview4 发布;v2(guard 委托、DI 集成 - Handler `AllowMultiple`:单类多 `[HandlerOrder]` / `[HandlerOrder]`。 - 工程化:Nuke `Ci` / `CiPack`、GitHub Actions、warnings-as-errors、Pack 消费者验证、元包 `lib/net8.0` 打包与校验、共享注册生成器 Helper、共享未注册契约 Analyzer 基类。 - `Composite.Sample` 手动 `CompositeTreeBuilder` 演示。 -- F2 Composite 多根 / 森林:`AssembleForest`、`BuildForest()`、`TraverseForest` / `TraverseForestAsync`(见 [Composite.md](Composite.md))。 -- F2 Decorator 排序 / 条件:`DecoratorStackBuilder` 谓词 `Add`、`{Contract}DecoratorOrder` 常量(见 [Decorator.md](Decorator.md))。 -- F2 Strategy 异步路径:`StrategyRegistryExtensions.ExecuteAsync` / `TryExecuteAsync`、`IAsyncStrategy` 契约的生成器与 DI 测试覆盖(见 [Strategy.md](Strategy.md))。 -- F2 Handler 增强:`HandlerPipeline.InvokeTracedAsync`、`HandlerPipelineTrace` 短路逐步可观测(见 [ChainOfResponsibility.md](ChainOfResponsibility.md))。 -- State 转换表 v1:`ITransitionTable` / `TransitionTableBuilder`(M1)、`[StateMachine]` / `[Transition]` 生成器(M2,DP026–DP031);见 [StateTransitionTable.md](StateTransitionTable.md)。 -- State 转换表 v2:guard 委托运行时 + 生成器(PR #124/#125,DP032/DP034/DP035)、DI 集成(PR #126,`RegisterDi` 生成 + `AddTransitionTable` 扩展)、DP036 字面量边校验 Analyzer(PR #127);见 [StateTransitionTable.md](StateTransitionTable.md)。 -- F2+ Strategy/Chain guard 谓词:`TryGetWithGuard` + `[RegisterStrategy(Guard=)]`(DP047–DP049)、`[HandlerOrder(Guard=)]` + `Skipped` trace 状态(DP050–DP052);见 [Strategy.md](Strategy.md)、[ChainOfResponsibility.md](ChainOfResponsibility.md)。 -- F2+ State entry/exit actions + IStateMachine:`[Transition(OnEnter/OnExit)]` + `IStateMachine` 实例包装器(DP037–DP039);见 [StateTransitionTable.md](StateTransitionTable.md)。 +- F2 Composite 多根 / 森林:`AssembleForest`、`BuildForest()`、`TraverseForest` / `TraverseForestAsync`(见 [Composite.md](design/Composite.md))。 +- F2 Decorator 排序 / 条件:`DecoratorStackBuilder` 谓词 `Add`、`{Contract}DecoratorOrder` 常量(见 [Decorator.md](design/Decorator.md))。 +- F2 Strategy 异步路径:`StrategyRegistryExtensions.ExecuteAsync` / `TryExecuteAsync`、`IAsyncStrategy` 契约的生成器与 DI 测试覆盖(见 [Strategy.md](design/Strategy.md))。 +- F2 Handler 增强:`HandlerPipeline.InvokeTracedAsync`、`HandlerPipelineTrace` 短路逐步可观测(见 [ChainOfResponsibility.md](design/ChainOfResponsibility.md))。 +- State 转换表 v1:`ITransitionTable` / `TransitionTableBuilder`(M1)、`[StateMachine]` / `[Transition]` 生成器(M2,DP026–DP031);见 [StateTransitionTable.md](design/StateTransitionTable.md)。 +- State 转换表 v2:guard 委托运行时 + 生成器(PR #124/#125,DP032/DP034/DP035)、DI 集成(PR #126,`RegisterDi` 生成 + `AddTransitionTable` 扩展)、DP036 字面量边校验 Analyzer(PR #127);见 [StateTransitionTable.md](design/StateTransitionTable.md)。 +- F2+ Strategy/Chain guard 谓词:`TryGetWithGuard` + `[RegisterStrategy(Guard=)]`(DP047–DP049)、`[HandlerOrder(Guard=)]` + `Skipped` trace 状态(DP050–DP052);见 [Strategy.md](design/Strategy.md)、[ChainOfResponsibility.md](design/ChainOfResponsibility.md)。 +- F2+ State entry/exit actions + IStateMachine:`[Transition(OnEnter/OnExit)]` + `IStateMachine` 实例包装器(DP037–DP039);见 [StateTransitionTable.md](design/StateTransitionTable.md)。 - 工程整改:ROADMAP DP050–DP052 ID 冲突修复(PR #154);StateTransitionTable docs entry/exit actions + IStateMachine 文档补全(PR #156);Strategy docs guard 文档 + 接口签名修正(PR #158)。 - 工程整改:Nullable `[MaybeNullWhen]` 注解(Polyfill)+ FactoryRegistry `FrozenDictionary` 优化(PR #160)。 - 工程整改:`StateMachine.CurrentState` setter 改 internal + 线程安全 XML 文档(PR #162,**破坏性变更**)。 -- F2+ TransitionTrace:`TransitionTrace` + `TryTransitionTracedAsync` 部分执行可观测性(PR #164);见 [StateTransitionTable.md](StateTransitionTable.md)。 -- F2+ EventAggregator 错误隔离:`EventPublishErrorHandling` 枚举(StopOnError/ContinueOnError/AggregateErrors)+ `PublishAsync` 重载(PR #166);见 [EventAggregator.md](EventAggregator.md)。 +- F2+ TransitionTrace:`TransitionTrace` + `TryTransitionTracedAsync` 部分执行可观测性(PR #164);见 [StateTransitionTable.md](design/StateTransitionTable.md)。 +- F2+ EventAggregator 错误隔离:`EventPublishErrorHandling` 枚举(StopOnError/ContinueOnError/AggregateErrors)+ `PublishAsync` 重载(PR #166);见 [EventAggregator.md](design/EventAggregator.md)。 - F2+ State Autofac 集成:生成 `RegisterAutofac` 到 transition table + state machine 类;`AutofacStateTransitionExtensions` 手动注册(PR #170)。 - 工程整改:README 清理 + 徽章 + NuGet 元数据增强(PR #171/#172)。 - 发版 `0.2.0-preview1`:minor 版本升级,含 State v2 完整能力、Composite/Decorator/EventAggregator 生成器、guard 谓词、错误隔离、Autofac 集成(PR #173,tag `v0.2.0-preview1`)。 diff --git a/docs/StateTransitionTable.md b/docs/StateTransitionTable.md deleted file mode 100644 index a62145c..0000000 --- a/docs/StateTransitionTable.md +++ /dev/null @@ -1,385 +0,0 @@ -# State 转换表 — 设计与实现文档 - -## 概述 - -State 转换表为 **(当前状态, 触发器) → 下一状态** 的有限图提供轻量 primitive。本库提供: - -- **运行时**:`ITransitionTable`、`TransitionTableBuilder`、`Transition()` 扩展 -- **编译期**:`[StateMachine]` / `[Transition]` 源生成器 → `{StateEnum}TransitionTable` + partial holder 便捷方法 -- **v2 增强**:guard 委托(运行时 + 生成器)、DI 集成(`RegisterDi` 生成 + `AddTransitionTable` 扩展)、字面量边校验(DP036 Analyzer) - -**不是**通用 UML 状态机框架(无层次状态、历史、持久化)。需要完整状态机 DSL 时继续使用 Stateless 等库。 - -设计 RFC:[rfc/StateTransitionTable.md](rfc/StateTransitionTable.md)(v1 已落地,v2 guard/DI/DP036 已实现)。 - ---- - -## 运行时 API(`DesignPatterns/Behavioral/`) - -| 类型 | 职责 | -|------|------| -| `ITransitionTable` | 只读转换表:`TryTransition`、`GetAllowedTriggers`、`CanTransitionFrom` | -| `TransitionTableBuilder` | 手动注册边 + `WithInitial` | -| `TransitionTable` | 不可变实现 | -| `TransitionTableExtensions.Transition` | 非法边时抛 `InvalidTransitionException` | -| `InvalidTransitionException` | 含当前态与触发器信息 | - -约束:`TState`、`TTrigger` 均为 **enum**;v1 不支持 string/int 键。 - -### 手动 Builder(无生成器时) - -```csharp -using DesignPatterns.Behavioral; - -public enum OrderStatus { Draft, Submitted, Paid } -public enum OrderTrigger { Submit, Pay } - -var table = new TransitionTableBuilder() - .WithInitial(OrderStatus.Draft) - .Add(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted) - .Add(OrderStatus.Submitted, OrderTrigger.Pay, OrderStatus.Paid) - .Build(); - -if (table.TryTransition(OrderStatus.Draft, OrderTrigger.Submit, out var next)) -{ - // next == Submitted -} - -// 非法边返回 false;Transition() 扩展抛 InvalidTransitionException -``` - ---- - -## 源生成器 - -### 用法 - -1. 定义 **state enum** 与 **trigger enum**(v1 须为两个独立 enum)。 -2. 声明 **static partial** holder 类,标注 `[StateMachine(typeof(TState), typeof(TTrigger), Initial = ...)]`。 -3. 在 holder 上标注一条或多条 `[Transition(from, trigger, to)]`。 -4. 生成器输出 `{StateEnum}TransitionTable` 与 holder 上的 `TryTransition` / `InitialState` 等成员。 - -```csharp -[StateMachine(typeof(OrderStatus), typeof(OrderTrigger), Initial = OrderStatus.Draft)] -[Transition(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted)] -[Transition(OrderStatus.Submitted, OrderTrigger.Pay, OrderStatus.Paid)] -public static partial class OrderMachine; - -// 生成:OrderStatusTransitionTable.Instance -// holder:OrderMachine.TryTransition(...)、OrderMachine.InitialState -``` - -### 命名规则 - -对 state enum `OrderStatus`: - -| 生成物 | 名称 | -|--------|------| -| 转换表类型 | `OrderStatusTransitionTable` | -| 单例 | `OrderStatusTransitionTable.Instance` | -| Holder 便捷 API | `OrderMachine.TryTransition`、`InitialState` | - ---- - -## 编译期诊断(DP026–DP039) - -| ID | 级别 | 归属 | 说明 | -|----|------|------|------| -| **DP026** | Error | 生成器 | 重复边 `(from, trigger)` | -| **DP027** | Error | 生成器 | `[Transition]` 的 state 非 enum 成员 | -| **DP028** | Error | 生成器 | trigger 非 enum 成员 | -| **DP029** | Error | 生成器 | `Initial` 非 state enum 成员 | -| **DP030** | Error | 生成器 | holder 非 static partial class | -| **DP031** | Info | 生成器 | state 从未作为 `from` 出现(终态提示) | -| **DP032** | Error | 生成器 | guard 方法在 holder 类上未找到 | -| **DP034** | Error | 生成器 | guard 方法非 static | -| **DP035** | Error | 生成器 | guard 方法签名错误(须 `bool Method(TState, TTrigger)`) | -| **DP036** | Info | Analyzer | `TryTransition` 字面量 (state, trigger) 对未声明(与 DP025 对称) | -| **DP037** | Error | 生成器 | entry/exit action 方法在 holder 类上未找到 | -| **DP038** | Error | 生成器 | entry/exit action 方法非 static(防御性;CS0708 先于生成器拒绝) | -| **DP039** | Error | 生成器 | entry/exit action 方法签名错误(须 `void Method(TState, TState, TTrigger)` 或 `ValueTask Method(TState, TState, TTrigger, CancellationToken)`) | - -常量与文案:[`DesignPatterns.Diagnostics/DiagnosticIds.cs`](../DesignPatterns.Diagnostics/DiagnosticIds.cs)、[`DesignPatternsDiagnosticDescriptors.cs`](../DesignPatterns.Diagnostics/DesignPatternsDiagnosticDescriptors.cs)。 - -用户向说明:[DesignPatterns.Docs — diagnostics](https://skymly.github.io/DesignPatterns.Docs/diagnostics#state-transition-table-dp026-dp031)。 - ---- - -## v2:Guard 委托 - -v2 为转换边增加可选的 **guard 委托**,在 `TryTransition` 返回目标状态前求值;guard 返回 `false` 时该次转换视为不存在。 - -### 运行时 API - -`TransitionTableBuilder.Add` 提供 guard 重载: - -```csharp -var table = new TransitionTableBuilder() - .WithInitial(OrderStatus.Draft) - .Add(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted, - guard: (state, trigger) => !string.IsNullOrEmpty(orderId)) - .Build(); -``` - -`TransitionEdge.Guard` 存储委托;`TransitionTable.TryTransition` 在命中边后调用 guard。 - -### 源生成器 - -`[Transition]` 特性新增 `Guard` 命名属性,指定 holder 类上的 **static** 方法名: - -```csharp -[StateMachine(typeof(OrderStatus), typeof(OrderTrigger), Initial = OrderStatus.Draft)] -[Transition(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted, Guard = nameof(CanSubmit))] -[Transition(OrderStatus.Submitted, OrderTrigger.Pay, OrderStatus.Paid)] -public static partial class OrderMachine -{ - public static bool CanSubmit(OrderStatus state, OrderTrigger trigger) => true; -} -``` - -生成器将 guard 方法引用编译为委托并嵌入生成的转换表。guard 方法须满足: - -- **static**(DP034) -- 签名 `bool Method(TState, TTrigger)`(DP035) -- 在 holder 类上声明(DP032) - ---- - -## v2:DI 集成 - -### 生成的 `RegisterDi` 方法 - -当消费项目引用 `DesignPatterns.Extensions.DependencyInjection`(自动 Import `build/DesignPatterns.Extensions.DependencyInjection.targets`)时,生成器在转换表类上额外输出: - -```csharp -public static IServiceCollection RegisterDi( - IServiceCollection services, - ServiceLifetime lifetime = ServiceLifetime.Singleton) -{ - services.TryAdd(new ServiceDescriptor( - typeof(ITransitionTable), - _ => Instance, - lifetime)); - return services; -} -``` - -调用方式: - -```csharp -OrderStatusTransitionTable.RegisterDi(services); -// 或链式 -services.AddOtherStuff().RegisterDi(); -``` - -启用开关:MSBuild 属性 `DesignPatterns_EnableDiIntegration`(引用 DI 扩展包时自动为 `true`)。 - -### 手动注册扩展 - -`DesignPatterns.Extensions.DependencyInjection.DesignPatternsServiceCollectionExtensions` 提供 `AddTransitionTable` 扩展方法,用于注册预构建的表实例: - -```csharp -services.AddTransitionTable(manualTable); -services.AddTransitionTable(OrderStatusTransitionTable.Instance, ServiceLifetime.Singleton); -``` - -使用 `TryAdd` 语义,重复注册不会覆盖。 - ---- - -## v2:字面量边校验(DP036) - -`StateTransitionLiteralEdgeAnalyzer`(`DesignPatterns.Analyzers`)在编译期扫描所有程序集的 `[StateMachine]` + `[Transition]` 特性,构建有效边集合,然后校验 `TryTransition` 调用的字面量 (state, trigger) 参数对: - -```csharp -// DP036: (Cancelled, Submit) 未声明 -table.TryTransition(OrderStatus.Cancelled, OrderTrigger.Submit, out _); -``` - -- 严重级别 **Info**(与 DP025 一致;运行时 `TryTransition` 仅返回 `false`,非错误) -- 跨程序集支持(表可在引用程序集中声明) -- 非常量参数跳过(运行时计算的值无法编译期校验) -- 仅校验 `TryTransition`(`CanTransitionFrom` 只接收 state,无 trigger 可校验) - ---- - -## v2:Entry / Exit Actions - -v2 为转换边增加可选的 **entry/exit action** 委托,在 `TryTransitionAsync` 成功时按序调用。Action 仅通过 async 路径(`TryTransitionAsync`)触发;同步 `TryTransition` 不调用 action。 - -### 运行时 API - -`TransitionTableBuilder.Add` 提供 `onEnterSync` / `onExitSync` / `onEnterAsync` / `onExitAsync` 参数: - -```csharp -var table = new TransitionTableBuilder() - .WithInitial(OrderStatus.Draft) - .Add( - OrderStatus.Draft, - OrderTrigger.Submit, - OrderStatus.Submitted, - guard: null, - onEnterSync: (from, to, trigger) => Console.WriteLine($"Entering {to}"), - onExitSync: (from, to, trigger) => Console.WriteLine($"Exiting {from}")) - .Build(); -``` - -执行顺序(`TryTransitionAsync`):guard → OnExit(sync → async)→ OnEnter(sync → async)→ 返回结果。 - -> **部分执行**:若 OnExit 成功后 OnEnter 抛异常,异常传播给调用方,OnExit 副作用已发生。调用方需自行处理补偿。`TransitionResult` 不暴露执行进度。 - -### 源生成器 - -`[Transition]` 特性新增 `OnEnter` / `OnExit` 命名属性,指定 holder 类上的 **static** 方法名: - -```csharp -[StateMachine(typeof(OrderStatus), typeof(OrderTrigger), Initial = OrderStatus.Draft)] -[Transition(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted, - OnEnter = nameof(OnSubmitted), OnExit = nameof(OnLeaveDraft))] -public static partial class OrderMachine -{ - public static void OnSubmitted(OrderStatus from, OrderStatus to, OrderTrigger trigger) { } - public static void OnLeaveDraft(OrderStatus from, OrderStatus to, OrderTrigger trigger) { } -} -``` - -Action 方法须满足: -- **static**(DP038) -- 签名 `void Method(TState from, TState to, TTrigger trigger)`(sync)或 `ValueTask Method(TState from, TState to, TTrigger trigger, CancellationToken)`(async)(DP039) -- 在 holder 类上声明(DP037) - ---- - -## v2:IStateMachine 实例包装器 - -`IStateMachine` 是 `ITransitionTable` 的有状态包装,自动跟踪 `CurrentState` 并在每次成功转换后更新。 - -### 运行时 API - -```csharp -var machine = new StateMachine(table); -// machine.CurrentState == table.InitialState - -if (machine.TryTransition(OrderTrigger.Submit, out var next)) -{ - // machine.CurrentState == OrderStatus.Submitted -} -``` - -`TryTransition` 委托给底层 table;成功时更新 `CurrentState`。`TryTransitionAsync` 同理,并触发 entry/exit action。 - -> **线程安全**:`StateMachine` 非线程安全,设计为单线程使用。多线程场景应在调用方同步,或每线程使用独立实例。 - ---- - -## 非目标(v1/v2) - -- ~~层次 / 并发 / 历史状态~~ — **层次状态已在 v3 实现**(见下方「层次状态机(v3)」章节);并发 / 历史状态仍为非目标 -- `string` / `int` 状态键 -- guard 的编译期表达式求值(v2 仅运行时委托 + 方法引用校验) - ---- - -## 层次状态机(v3) - -v3 引入层次状态机(Hierarchical State Machine),支持父子状态关系、边继承展平、entry/exit action 链合成与 `IStateHierarchy` DI 集成。设计 RFC:[rfc/HierarchicalStateMachine.md](rfc/HierarchicalStateMachine.md) §7–§8。 - -### v3 交付阶段 - -| 阶段 | 内容 | 状态 | -|------|------|------| -| **v3.1** | 运行时 `IStateHierarchy`、`TransitionTableBuilder.WithParent` | 已完成 | -| **v3.2** | 源生成器 `[StateParent]` 收集、边继承展平、诊断 DP056–DP059 | 已完成 | -| **v3.3** | LCA 算法 + entry/exit action 链合成 + 复合委托生成 | 已完成 | -| **v3.4** | DI 集成(`AddStateHierarchy` + 生成器 `RegisterDi` 注册 `IStateHierarchy`)+ 示例 + 文档 | 已完成 | - -### 启用层次模式 - -在 `[StateMachine]` 特性上设置 `Hierarchical = true`,并用 `[StateParent]` 声明父子关系: - -```csharp -[StateMachine(typeof(OrderStatus), typeof(OrderTrigger), Initial = OrderStatus.Draft, Hierarchical = true)] -[StateParent(OrderStatus.Submitted, OrderStatus.Active)] -[StateParent(OrderStatus.Paid, OrderStatus.Active)] -[Transition(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted)] -[Transition(OrderStatus.Submitted, OrderTrigger.Pay, OrderStatus.Paid)] -[Transition(OrderStatus.Active, OrderTrigger.Cancel, OrderStatus.Cancelled, OnExit = nameof(OnExitActive))] -public static partial class OrderMachine -{ - public static void OnExitActive(OrderStatus from, OrderStatus to, OrderTrigger trigger) { } -} -``` - -### 边继承展平(v3.2) - -`[StateParent]` 声明的父子关系在编译期被 `HierarchyFlattener` 展平:父状态上的边自动继承到所有子状态。上例中 `Active + Cancel → Cancelled` 会被展平为 `Submitted + Cancel → Cancelled` 和 `Paid + Cancel → Cancelled`,无需重复声明。 - -诊断 DP056–DP059 覆盖:循环父链、自引用父、未知子/父状态、孤立父声明。 - -### Entry/Exit Action 链合成(v3.3) - -当层次模式下一条展平后的边跨越多个层级时,源生成器合成复合委托(composite delegate),按 RFC §8 的 LCA(最低公共祖先)算法计算 exit/enter 链: - -- **Exit 链**:从 `from` 向上到 LCA(不含 LCA),按从子到父的顺序执行 -- **Enter 链**:从 LCA 向下到 `to`(不含 LCA),按从父到子的顺序执行 -- **复合委托**:仅当链有 2+ action 时生成 `CompositeExit_{From}_{Trigger}` / `CompositeEnter_{From}_{Trigger}` 静态方法;1 action 直接用原引用;0 action 用 null - -**边界场景(RFC §8.4)**: - -| 场景 | Exit 链 | Enter 链 | -|------|---------|----------| -| 自环(from == to) | `[from]` | `[from]` | -| LCA == from(进入后代) | `[]`(空) | LCA → to 的子链 | -| LCA == to(返回祖先) | from → LCA 的子链 | `[]`(空) | -| 无公共祖先 | from 全祖链 | to 全祖链 | - -复合委托对运行时透明:`TransitionEdge` 结构不变,复合委托是单个 `Action` 或 `Func<...,ValueTask>`。 - -### `IStateHierarchy` DI 集成(v3.4) - -生成的 `OrderStatusTransitionTable` 同时实现 `ITransitionTable` 和 `IStateHierarchy`。当启用 DI 集成时,生成的 `RegisterDi` 方法自动注册两者: - -```csharp -var services = new ServiceCollection(); -OrderStatusTransitionTable.RegisterDi(services); // 注册 ITransitionTable + IStateHierarchy -services.AddStateMachine(); - -var provider = services.BuildServiceProvider(); -var table = provider.GetRequiredService>(); -var hierarchy = provider.GetRequiredService>(); -``` - -手动注册时使用 `AddStateHierarchy` 扩展方法(从容器解析 table 并转型)。 - -### `TransitionTableBuilder.Add` 重载简化 - -v3.4 将 `Add` 的多个重载合并为单一签名,所有可选参数(`guard`、`onEnterSync`、`onExitSync`、`onEnterAsync`、`onExitAsync`)均有默认值 `null`,调用方可按命名参数传任意子集,无需为占位提供 `null`: - -```csharp -// v3.4:直接用命名参数,无需 guard 占位 -.Add(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted, onExitSync: OnExitDraft) - -// v3.4 之前:需要 guard 占位 -.Add(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted, guard: null, onEnterSync: null, onExitSync: OnExitDraft) -``` - ---- - -## 测试 - -| 层级 | 位置 | -|------|------| -| 运行时单元 | `tests/DesignPatterns.Tests/Behavioral/TransitionTableTests.cs` | -| 生成器集成 | `tests/DesignPatterns.Tests/Integration/StateTransitionIntegrationTests.cs` | -| 生成器快照 | `tests/DesignPatterns.SourceGenerators.Tests/Generators/StateTransitionGeneratorTests.cs` | -| Guard 生成器快照 | `tests/DesignPatterns.SourceGenerators.Tests/Generators/StateTransitionGeneratorTests.cs`(guard 用例) | -| Action 链合成单元 | `tests/DesignPatterns.SourceGenerators.Tests/Generators/ActionChainComposerTests.cs` | -| 层次 action 链快照 | `tests/DesignPatterns.SourceGenerators.Tests/Generators/StateTransitionGeneratorTests.cs`(`EmitsHierarchicalTableWith*ActionChain`) | -| 层次 DI 快照 | `tests/DesignPatterns.SourceGenerators.Tests/Generators/StateTransitionGeneratorTests.cs`(`EmitsHierarchicalTableWithDiIntegrationRegistersIStateHierarchy`) | -| DI 扩展 | `tests/DesignPatterns.Extensions.DependencyInjection.Tests/TransitionTableDiExtensionsTests.cs` | -| 字面量边 Analyzer | `tests/DesignPatterns.Analyzers.Tests/StateTransitionLiteralEdgeAnalyzerTests.cs` | - -## 示例 - -- [DesignPatterns.Samples.State](https://github.com/Skymly/DesignPatterns.Samples/tree/main/DesignPatterns.Samples.State) — 基础状态机(guard、entry/exit action、DI) -- [DesignPatterns.Samples.HierarchicalState](https://github.com/Skymly/DesignPatterns.Samples/tree/main/DesignPatterns.Samples.HierarchicalState) — 层次状态机(`[StateParent]`、边继承展平、action 链合成、`IStateHierarchy` DI) diff --git a/docs/adr/ADR-005-state-transition-table.md b/docs/adr/ADR-005-state-transition-table.md index 6bc6797..1cff716 100644 --- a/docs/adr/ADR-005-state-transition-table.md +++ b/docs/adr/ADR-005-state-transition-table.md @@ -34,6 +34,5 @@ DesignPatterns 缺少有限状态机 primitive。现有库(Stateless、Akka.NE ## 参考 -- [docs/spec/StateTransitionTable.md](../spec/StateTransitionTable.md)(待创建) -- [docs/design/StateTransitionTable.md](../design/StateTransitionTable.md)(待创建) -- [docs/StateTransitionTable.md](../StateTransitionTable.md)(现有模式文档,待迁移) +- [docs/spec/StateTransitionTable.md](../spec/StateTransitionTable.md) +- [docs/design/StateTransitionTable.md](../design/StateTransitionTable.md) diff --git a/docs/adr/ADR-006-composite-parallel-traversal.md b/docs/adr/ADR-006-composite-parallel-traversal.md index 7dc3ad2..e18689c 100644 --- a/docs/adr/ADR-006-composite-parallel-traversal.md +++ b/docs/adr/ADR-006-composite-parallel-traversal.md @@ -34,6 +34,5 @@ Composite 模式已有串行遍历(`Traverse` / `TraverseAsync`),但大树 ## 参考 -- [docs/spec/Composite.md](../spec/Composite.md)(待创建) -- [docs/design/Composite.md](../design/Composite.md)(待创建) -- [docs/Composite.md](../Composite.md)(现有模式文档,待迁移) +- [docs/spec/Composite.md](../spec/Composite.md) +- [docs/design/Composite.md](../design/Composite.md) diff --git a/docs/adr/ADR-007-composite-tree-schema-validation.md b/docs/adr/ADR-007-composite-tree-schema-validation.md index 92eab07..00de761 100644 --- a/docs/adr/ADR-007-composite-tree-schema-validation.md +++ b/docs/adr/ADR-007-composite-tree-schema-validation.md @@ -34,6 +34,5 @@ ## 参考 -- [docs/spec/Composite.md](../spec/Composite.md)(待创建) -- [docs/design/Composite.md](../design/Composite.md)(待创建) -- [docs/Composite.md](../Composite.md)(现有模式文档,待迁移) +- [docs/spec/Composite.md](../spec/Composite.md) +- [docs/design/Composite.md](../design/Composite.md) diff --git a/docs/design/ChainOfResponsibility.md b/docs/design/ChainOfResponsibility.md new file mode 100644 index 0000000..424e94f --- /dev/null +++ b/docs/design/ChainOfResponsibility.md @@ -0,0 +1,111 @@ +# Design Doc: Chain of Responsibility + +> **关联 Spec**:[docs/spec/ChainOfResponsibility.md](../spec/ChainOfResponsibility.md) +> **关联 RFC**:[docs/rfc/XXX.md](../rfc/XXX.md)(如有) +> **关联 ADR**:ADR-XXX(如有) + +## 概述 + +责任链模式将请求沿 handler 链传递,每个 handler 决定是处理请求还是转发给下一个。本库提供 **中间件式管道 primitives**;与 MediatR 或 ASP.NET Core 管道在能力上可能重叠,但本库聚焦「源生成排序 + 编译期诊断」的探索价值(见 [AGENTS.md](../../AGENTS.md)「项目是什么」)。 + +## 设计目标 + +1. 与 ASP.NET Core 中间件一致的 **onion 调用模型**(先注册先执行 inbound) +2. 通过 **是否调用 `next`** 实现短路,不强制业务 context 实现额外接口 +3. **异步一等**:`ValueTask` + `CancellationToken` +4. 不依赖 DI;handler 由调用方手动组装 + +## 实现概览 + +### 运行时 + +**Onion 调用模型**:先注册的 handler 先执行 inbound,后执行 outbound,与 ASP.NET Core 中间件一致。handler 通过 `HandlerDelegate` 调用下一阶段,形成嵌套调用栈。 + +**异步一等**:所有 handler 与管道入口均使用 `ValueTask` + `CancellationToken`,避免不必要的 `Task` 分配并支持可取消的异步链。 + +**短路可观测性(`InvokeTracedAsync`)**:在不引入完整中间件框架的前提下,记录**每个已注册 handler** 在一次调用中的结局,便于调试鉴权拦截、早退逻辑等。 + +```csharp +var trace = await pipeline.InvokeTracedAsync(context, cancellationToken); + +foreach (var step in trace.Steps) +{ + Console.WriteLine($"{step.Index}: {step.Name} → {step.Status}"); +} + +if (trace.Steps.Any(s => s.Status == HandlerPipelineStepStatus.NotReached)) +{ + // 有 handler 因更早的短路而从未执行 +} +``` + +**解读提示**: + +- 区分「提前拦截」与「正常终端」:看**后续** handler 是否为 `NotReached`。例如鉴权 handler 不写 `401` 且不调用 `next` 时,其后 handler 应为 `NotReached`;而链末 handler 不调用 `next` 属于常见终端写法,其自身为 `ShortCircuited`,但**不代表**前面 handler 失败。 + +集成测试见 `tests/DesignPatterns.Tests/Integration/HandlerPipelineIntegrationTests.cs`(生成管道 + 鉴权通过 / 失败两条 trace 路径)。 + +### 源生成器 + +**`[HandlerOrder]` 排序**:`HandlerOrderGenerator` 扫描标注 `[HandlerOrder(order)]` 或 `[HandlerOrder(order, typeof(TContext))]` 的 handler 类,按 `order` 数值升序排列(数值越小越先执行,与手动 `Use` 注册顺序一致)。同一 handler 类可标注多个 `[HandlerOrder<...>]`(`AllowMultiple = true`),分别加入不同 `TContext` 的生成管道。 + +**`{Context}HandlerPipeline` 生成**:对每种 `TContext` 生成 `public static partial class {Context}HandlerPipeline`,暴露 `Instance`(`HandlerPipeline`);引用 DI 扩展包后额外生成 `RegisterDi(services)` 与 `Create(IServiceProvider sp)`。 + +### 诊断 + +| ID | 说明 | +|----|------| +| DP005 | 同一 context 下重复的 Order | +| DP008 | 未实现 `IHandler` | +| DP009 | 缺少 public 无参构造 | +| DP024 | Info \| Analyzer \| 实现了 `IHandler` 但未加 `[HandlerOrder]`(该 context 已有其它 handler 注册) | +| DP024 | Info \| CodeFix \| 一键添加 `[HandlerOrder(order, typeof(TContext))]` | + +DP024 属 **Analyzer**;DP005 / DP008 / DP009 属**生成器**。 + +## 设计权衡 + +### 选择了 onion 模型而非线性模型,因为... + +与 ASP.NET Core 中间件一致的 onion 调用模型(先注册先执行 inbound,后执行 outbound)允许 handler 在 `next` 返回后执行后续逻辑(如日志后置、资源清理),线性模型只能单向传递、无法在「之后」做事。 + +### 短路语义:通过是否调用 `next` 实现 + +Handler **不调用** `next` 即短路,后续 handler(inbound 与 outbound 均跳过)不再执行。不强制业务 context 实现额外接口(如 `ICanShortCircuit`),保持 context 为纯数据载体。 + +### trace vs no-trace + +`InvokeTracedAsync` 不改变执行顺序与短路语义,仅额外返回 `HandlerPipelineTrace`,便于调试;`InvokeAsync` 为零开销热路径。委托 handler 在 trace 中显示名为 `""`。 + +## 与生态的边界 + +| 模式 | 关注点 | +|------|--------| +| **Chain** | 请求在 **有序 pipeline** 中流动,可短路 | +| **Strategy** | 按 key **选一个** 实现 | +| **Factory** | 按 key **创建一个** 新实例 | + +**DI 集成**:引用 `DesignPatterns.Extensions.DependencyInjection` 后,`{Context}HandlerPipeline` 生成 `RegisterDi(services)`;`Create(IServiceProvider sp)` 按 `[HandlerOrder]` 顺序 `GetRequiredService` 各 handler。Singleton 管道在首次构建时固定 handler 实例;需要每次解析新管道时使用 `registryLifetime: ServiceLifetime.Transient`。手动注册:`services.AddHandlerPipeline(builder => ...)`(扩展包)。 + +## 已知局限 + +- **无 DI 自动注册**:Core 不引用 MSDI,handler 不会自动注册到容器;需引用 `DesignPatterns.Extensions.DependencyInjection` 并调用 `RegisterDi`,或手动 `AddHandlerPipeline`。 +- **无源生成器时需手动组装**:未使用 `[HandlerOrder]` 源生成器时,handler 须由调用方手动 `Use` 组装(见用法示例)。 + +```csharp +var pipeline = new HandlerPipelineBuilder() + .Use(new LoggingHandler()) + .Use(new AuthorizationHandler()) // 未授权时不调用 next → 短路 + .Use(new ResourceHandler()) + .Build(); + +await pipeline.InvokeAsync(new RequestContext("/api/orders", isAuthenticated: true)); +``` + +示例见 [DesignPatterns.Samples.Chain](https://github.com/Skymly/DesignPatterns.Samples/tree/main/DesignPatterns.Samples.Chain):日志 → 鉴权 → 资源 handler,演示授权与未授权两种路径。 + +## 参考 + +- GoF: Chain of Responsibility (Behavioral) +- [docs/DEVELOPMENT.md](../DEVELOPMENT.md) +- [AGENTS.md](../../AGENTS.md) diff --git a/docs/design/Composite.md b/docs/design/Composite.md new file mode 100644 index 0000000..4781c78 --- /dev/null +++ b/docs/design/Composite.md @@ -0,0 +1,193 @@ +# Design Doc: Composite + +> **关联 Spec**:[docs/spec/Composite.md](../spec/Composite.md) +> **关联 ADR**:[ADR-006](../adr/ADR-006-composite-parallel-traversal.md)、[ADR-007](../adr/ADR-007-composite-tree-schema-validation.md) + +## 概述 + +Composite 模式将对象组织成树形结构,以统一方式对待单个对象与组合对象。本库提供 **遍历 primitives** 与 **编译期 catalog 校验**,不替代完整 UI 组件框架或持久化树存储。 + +## 设计目标 + +1. **统一节点接口**:`ICompositeNode` 通过 `Children` 区分叶子与分支 +2. **多种遍历策略**:深度优先(前/后序)、广度优先 +3. **Catalog 装配**:`[CompositePart]` 生成 flat catalog,`BuildRoot()` 组装单根树,`BuildForest()` 组装多根森林 +4. 不依赖 DI;树由编译期定义、运行时一次性装配 + +## 实现概览 + +### 运行时 + +#### 树遍历(DFS / BFS) + +`CompositeTraverser` 是静态类,提供同步 / 异步 / 并行遍历单根树与森林的方法。遍历顺序由 `CompositeTraversalOrder` 枚举控制: + +- **`DepthFirstPreOrder`**:先访问节点,再递归子节点 +- **`DepthFirstPostOrder`**:先递归子节点,再访问节点 +- **`BreadthFirst`**:逐层访问 + +`CompositeTraversalOptions` 提供运行时控制: + +- `MaxDepth`:限制访问深度(0 = 仅根,`null` = 无限制) +- `VisitLeavesOnly`:仅将叶子节点传给 visitor +- `ShouldSkipSubtree`:谓词返回 `true` 时跳过该子树 + +visitor 签名:`Action`(node, depth, siblingIndex);异步版本 `Func`。 + +#### 并行遍历 + +`TraverseParallel` / `TraverseParallelAsync` / `TraverseForestParallel` / `TraverseForestParallelAsync` 提供并行遍历能力(详见 [ADR-006](../adr/ADR-006-composite-parallel-traversal.md)): + +- **BFS 同层并行**:同一层级节点并行处理 +- **DFS 子节点并行递归**:子节点并行递归,配合 `MaxParallelDepth`(默认 32)超过阈值后回退串行,避免过度任务创建 +- **`MaxDegreeOfParallelism`** 限流(`null` = `Environment.ProcessorCount`) +- **`AggregateException`** 聚合错误:visitor 异常收集到 `ConcurrentQueue`,遍历结束后统一抛出 +- **`ConfigureAwait(false)`** async 路径 +- **`#if` 分裂**:net8.0 用 `Parallel.ForEachAsync`,netstandard2.0 用 `SemaphoreSlim` + `Task.WhenAll` +- **线程安全责任**:用户责任 + 文档契约(不提供线程安全收集器);建议使用 `ConcurrentBag` 累积、`lock` / `SemaphoreSlim` 保护共享状态 +- **森林并行**:根间串行遍历,并行性在每个根的子树内;异常跨所有根收集后统一抛出 + +#### 森林支持 + +`TraverseForest` / `TraverseForestAsync` / `TraverseForestParallel` / `TraverseForestParallelAsync` 接受 `IReadOnlyList roots`,对多根森林遍历。visitor 的第三个参数 `siblingIndex` 在森林模式下表示 root index。 + +`CompositeCatalogAssembler.AssembleForest` 从 flat catalog 装配多根森林:根按 `Order` 升序再 key 排序返回。 + +#### 手动构建 + +`CompositeTreeBuilder` 提供无 catalog 元数据的手动树构建: + +```csharp +var root = new MenuBranch("Home"); +var child = new MenuLeaf("Settings"); + +var tree = new CompositeTreeBuilder() + .Branch(root, b => b.Leaf(child)) + .Build(); + +CompositeTraverser.Traverse(tree, (node, depth, _) => + Console.WriteLine($"{new string(' ', depth * 2)}{node.Title}")); +``` + +`Build()` 要求恰好一个顶层节点(`Leaf` 或 `Branch` 添加),否则抛 `InvalidOperationException`。`Branch` 通过嵌套 builder 配置子节点,内部调用 `ICompositeBuildable.SetChildren` 注入子节点。 + +### 源生成器 + +#### Flat catalog 从 `[CompositePart]` 生成 + +`CompositePartGenerator`(`IIncrementalGenerator`)通过 `ForAttributeWithMetadataName` 同时收集非泛型 `CompositePartAttribute` 与泛型 `CompositePartAttribute` 标注的类。增量管线阶段: + +1. **Transform**:从 attribute 参数提取 key、parentKey、order、contract、allowedChildTypes、schema 约束;校验实现类型是否实现 contract、是否有 public 无参构造、是否实现 `ICompositeBuildable` +2. **Collect + Combine**:按 contract `FullyQualifiedName` 分组 +3. **Execute**:对每组报告诊断(DP010–DP015, DP063–DP065),过滤有效注册,生成 `{Contract}CompositeKeys` + `{Contract}CompositeCatalog` + +```csharp +[CompositePart("root")] +public sealed class HomeMenu : IMenuNode, ICompositeBuildable { ... } + +[CompositePart("settings", ParentKey = "root", Order = 10)] +public sealed class SettingsMenu : IMenuNode, ICompositeBuildable { ... } + +var root = MenuNodeCompositeCatalog.BuildRoot(); +``` + +非泛型变体(netstandard2.0): + +```csharp +[CompositePart("settings", typeof(IMenuNode), ParentKey = "root", Order = 10)] +public sealed class SettingsMenu : IMenuNode, ICompositeBuildable { } +``` + +#### 树装配 + +`{Contract}CompositeCatalog` 内部持有 `CompositeCatalogEntry` 列表,`BuildRoot()` / `BuildForest()` 委托 `CompositeCatalogAssembler.Assemble` / `AssembleForest` 装配: + +- 从 flat parent-key 映射构建 `entryByKey` + `childrenByParent` 字典 +- 根 = `ParentKey == null` 的条目,按 `Order` 再 key 排序 +- 递归 `AssembleSubtree`:先递归创建子节点,再创建当前节点并调用 `SetChildren` 注入已创建的子节点列表 +- 实例化方式:`Activator.CreateInstance(type)` 或 `IServiceProvider.GetService(type)`(DI 重载) + +#### Schema 校验 + +`[CompositeSchema(MaxDepth, MaxNodes)]` 标注于契约接口/基类时,源生成器在编译期从 flat parent-key 映射重建完整树拓扑并执行结构校验(详见 [ADR-007](../adr/ADR-007-composite-tree-schema-validation.md)): + +- **深度检查(DP063)**:计算树的实际深度,与 `MaxDepth` 比较,超限报告 Warning +- **类型检查(DP064)**:父节点声明 `AllowedChildTypes` 时,校验每个子节点的实现类型是否在允许集合中,不在则报告 Error +- **计数检查(DP065)**:统计所有 `[CompositePart]` 节点总数,与 `MaxNodes` 比较,超限报告 Warning + +所有 schema 约束均为 opt-in — 未标注 `[CompositeSchema]` 的契约行为不变。校验在编译期完成,运行时零开销。 + +### 诊断 + +诊断检测逻辑与报告位置: + +| ID | 检测逻辑 | 报告位置 | +|----|----------|----------| +| DP010 | 按 contract 分组后按 key 分组,`Count > 1` 的组中每个注册报告 | `[CompositePart]` attribute 位置 | +| DP011 | 收集所有 key 到 `HashSet`,`ParentKey != null && !keys.Contains(ParentKey)` | `[CompositePart]` attribute 位置 | +| DP012 | 构建 parent map,对每个 key 检测是否参与环 | `[CompositePart]` attribute 位置 | +| DP013 | `ImplementsContract` 为 false(符号检查实现接口/继承基类) | `[CompositePart]` attribute 位置 | +| DP014 | `HasPublicParameterlessConstructor` 为 false | `[CompositePart]` attribute 位置 | +| DP015 | `ImplementsBuildable` 为 false(未实现 `ICompositeBuildable`) | `[CompositePart]` attribute 位置 | +| DP040 | `BuildRoot(IServiceProvider)` 时节点类型未注册到容器 | 生成器报告 | +| DP041 | 保留 ID — C# 编译器 CS0535 自动强制接口覆盖,诊断不实际触发 | — | +| DP063 | 编译期计算树深度,与 `[CompositeSchema(MaxDepth)]` 比较 | `[CompositeSchema]` 位置 | +| DP064 | 编译期检查子节点实现类型是否在父节点 `AllowedChildTypes` 集合中 | `[CompositePart]` attribute 位置 | +| DP065 | 编译期统计节点总数,与 `[CompositeSchema(MaxNodes)]` 比较 | `[CompositeSchema]` 位置 | + +## 设计权衡 + +### Flat catalog 而非嵌套结构 + +选择 flat catalog(`CompositeCatalogEntry` 列表 + parent-key 映射)而非嵌套 attribute 结构,因为: + +- flat catalog 易于源生成器收集与去重 +- parent-key 映射天然支持环检测、孤儿检测 +- 用户无需深层嵌套标注,`[CompositePart]` 标注独立类即可 +- 运行时装配通过字典查找 O(1) 定位父子关系 + +### `BuildRoot` vs `BuildForest` + +同时提供单根 `BuildRoot()` 与多根 `BuildForest()`: + +- `BuildRoot()` 强制单根约束(恰好一个 `ParentKey == null`),运行时抛 `CompositeAssemblyException` 明确失败 +- `BuildForest()` 允许零个或多个根,按 `Order` 再 key 排序返回 +- 用户根据场景选择;多根 catalog 调用 `BuildRoot()` 会明确报错而非静默取第一个 + +### 并行遍历设计 + +详见 [ADR-006](../adr/ADR-006-composite-parallel-traversal.md): + +- **BFS 同层并行**:同一层级节点并行处理,适合宽树 +- **DFS 子节点并行递归**:子节点并行递归,配合 `MaxParallelDepth`(默认 32)超过阈值后回退串行,避免过度任务创建导致调度开销 +- **`MaxDegreeOfParallelism = 1` 不保证顺序**:退化为串行但非有序;需要顺序时使用 `Traverse` / `TraverseAsync` +- **森林并行**:根间串行(保持 root 顺序),并行性在每个根的子树内 +- **线程安全**:用户责任 + 文档契约,不提供线程安全收集器 + +## 与生态的边界 + +| 模式 | 结构 | 关注点 | +|------|------|--------| +| **Composite** | 树 | 统一访问部分与整体、遍历 | +| **Chain** | 线性 pipeline | 有序执行、可短路 | +| **Strategy** | 扁平 key → 实现 | 选一个算法 | + +Composite 关注树形结构与统一遍历;Chain 关注线性有序执行与短路;Strategy 关注扁平 key 到单一实现的分发。三者结构不同,不存在替代关系。 + +## 已知局限 + +- **无运行时动态节点**:树由编译期 `[CompositePart]` 定义、运行时一次性装配,不支持运行时动态增删节点 +- **无 DI 自动注册**:`BuildRoot()` / `BuildForest()` 结果不自动注册到 DI 容器;DI 扩展不处理装配产物 +- **并行遍历顺序非确定**:`TraverseParallel` 系列方法访问顺序非确定,需要顺序时使用串行 `Traverse` / `TraverseAsync` +- **容器化环境 CPU 计数**:`MaxDegreeOfParallelism = null` 时使用 `Environment.ProcessorCount`,在 Docker/Kubernetes 中可能返回宿主机 CPU 数而非容器限制,需手动设置 + +## 参考 + +- GoF: Composite (Structural) +- [ADR-006: Composite parallel traversal](../adr/ADR-006-composite-parallel-traversal.md) +- [ADR-007: Composite tree schema validation](../adr/ADR-007-composite-tree-schema-validation.md) +- [Spec: Composite](../spec/Composite.md) +- [docs/DEVELOPMENT.md](../DEVELOPMENT.md) +- 示例:[DesignPatterns.Samples.Composite](https://github.com/Skymly/DesignPatterns.Samples/tree/main/DesignPatterns.Samples.Composite) + - **Catalog 森林**:`MenuNodeCompositeCatalog.BuildForest()` + `TraverseForest`(多根 `[CompositePart]`;多根时 `BuildRoot()` 抛错) + - **手动**:`CompositeTreeBuilder()` 的 `Leaf` / `Branch` / `Build()`(`ManualMenuNodes`,无 catalog 特性) diff --git a/docs/design/Decorator.md b/docs/design/Decorator.md new file mode 100644 index 0000000..388ea75 --- /dev/null +++ b/docs/design/Decorator.md @@ -0,0 +1,128 @@ +# Design Doc: Decorator + +> **关联 Spec**:[docs/spec/Decorator.md](../spec/Decorator.md) +> **关联 RFC**:[docs/rfc/XXX.md](../rfc/XXX.md)(如有) +> **关联 ADR**:ADR-XXX(如有) + +## 概述 + +Decorator(装饰器)在**不修改核心类型**的前提下,为同一契约接口叠加横切或增强行为(日志、缓存、指标等)。本库提供 **可组合的包装栈 primitives** 与 **`[Decorator]` 编译期排序**,不替代 ASP.NET Core 中间件、动态代理或完整 AOP 框架。 + +## 设计目标 + +1. **同一契约、显式内核**:`DecoratorStackBuilder.Build(core)` 必须传入真实核心实现 +2. **顺序可预测**:先注册 / `Order` 越小越靠外(先接到调用,再委托向内) +3. **零 DI 依赖**:手动组装;DI 扩展留 P3 +4. **编译期胶水**:生成 `{Contract}DecoratorStack.Build(core)` 与 `{Contract}DecoratorOrder` 常量,不生成装饰方法体 + +## 实现概览 + +### 运行时 + +**`DecoratorStackBuilder`**:可组合的包装栈 builder。通过 `Add()` / `Add(instance)` / `Add(..., Func)` 注册装饰器,`Build(core)` 按 `Add` 顺序(`Order` 越小越靠外)逐层包装 `core`,返回最外层 `TService`。 + +手动组装: + +```csharp +var service = new DecoratorStackBuilder() + .Add() + .Add() + .Build(new PaymentService()); +``` + +**条件装饰(`Func`)**:`Add` 的重载可传入 `Func`;谓词在 **`Build` 时**求值,为 `false` 时跳过该装饰器(返回 inner 不变): + +```csharp +var enableMetrics = configuration.GetValue("Metrics:Enabled"); + +var service = new DecoratorStackBuilder() + .Add() + .Add(() => enableMetrics) + .Build(new PaymentService()); +``` + +生成器产出的 `{Contract}DecoratorStack.Build(core)` 仍注册全部 `[Decorator]` 类型;条件开关仅适用于手动 `DecoratorStackBuilder` 组装。 + +**边界行为**: + +| 场景 | 行为 | +|------|------| +| `Build(null)` / `Add(null)` | `ArgumentNullException` | +| `Add(..., null)` 谓词 | `ArgumentNullException` | +| 无装饰器 | 返回同一 `core` 引用 | + +### 源生成器 + +**`[Decorator]` 排序**:`DecoratorGenerator` 扫描标注 `[Decorator(order)]` 或 `[Decorator(order, typeof(TService))]` 的装饰器类,按 `order` 数值升序排列(数值越小越靠外,先接到调用)。 + +**`{Contract}DecoratorStack.Build` 生成**:对每种 `TService` 生成 `public static partial class {Contract}DecoratorStack`,暴露 `Build(core)` 方法,按 `Order` 升序逐层包装 `core`。生成器注册全部 `[Decorator]` 类型,**不**应用运行时谓词。 + +**`{Contract}DecoratorOrder` 常量生成**:为每个装饰器生成 `public const int`,常量名取装饰器**简单类型名**;同一 contract 下类型名须唯一(跨命名空间同名会导致重复常量编译错误)。重复 `Order` 仍由 **DP016** 在编译期报告。 + +特性中可引用常量替代魔法数: + +```csharp +[Decorator(PaymentServiceDecoratorOrder.LoggingPaymentDecorator)] +public sealed class LoggingPaymentDecorator : IPaymentService, IDecorator { ... } +``` + +### 诊断 + +| ID | 说明 | +|----|------| +| DP016 | 同一 contract 下重复 Order | +| DP017 | 未实现 service contract | +| DP018 | 未实现 `IDecorator` | +| DP019 | 缺少 public 无参构造 | +| DP042 | async 装饰器 `DecorateAsync` 签名不匹配 | +| DP043 | 装饰器无法从 DI 容器解析(无 public 无参构造) | + +CodeFix:DP017(加契约接口)、DP019(加无参构造)。DP018 需手写 `Decorate` 方法。 + +## 设计权衡 + +### Decorator vs Chain of Responsibility + +| 维度 | Chain | Decorator | +|------|-------|-----------| +| 抽象 | `IHandler` | 装饰器与核心均实现 **`TService`** | +| 调用 | `next` 可短路 | 通常委托 `inner` | +| 核心 | 无单一内核服务 | 显式 **`core`** 实例 | +| 特性 | `[HandlerOrder]` | `[Decorator]` | + +Decorator 与核心实现**同一契约**,装饰器通过 `IDecorator.Decorate(inner)` 包装并委托向内;Chain 的 handler 实现 `IHandler`,通过 `next` 委托且可短路。Decorator 要求**显式 `core`** 实例作为内核,Chain 无单一内核服务。 + +### 选择了显式 core 而非无 core,因为... + +Decorator 语义上需要一个真实的核心实现作为包装起点;`Build(core)` 必须传入非 `null` 的 `core`。无装饰器时返回同一 `core` 引用,保持透明。这与 Chain(无单一内核服务、终端 handler 即处理者)形成对比。 + +### 条件装饰仅在手动 builder + +`Func` 谓词仅在手动 `DecoratorStackBuilder` 组装时可用;生成器产出的 `{Contract}DecoratorStack.Build(core)` 注册全部 `[Decorator]` 类型,不在编译期胶水中嵌入运行时谓词。这保持生成器产出的确定性与简单性,条件开关留给调用方在手动组装时决定。 + +## 与生态的边界 + +| 维度 | Chain | Decorator | +|------|-------|-----------| +| 抽象 | `IHandler` | 装饰器与核心均实现 **`TService`** | +| 调用 | `next` 可短路 | 通常委托 `inner` | +| 核心 | 无单一内核服务 | 显式 **`core`** 实例 | +| 特性 | `[HandlerOrder]` | `[Decorator]` | + +Decorator 聚焦「同一契约的横切增强」,不替代 ASP.NET Core 中间件、动态代理(`DispatchProxy`)或完整 AOP 框架。 + +## 已知局限 + +- **无 `DispatchProxy` / 透明代理**:装饰器须显式实现 `IDecorator` 并手写 `Decorate` 方法,不提供透明代理式自动包装。 +- **无 DI 自动注册**:Core 不引用 MSDI,装饰器不会自动注册到容器;DI 扩展留 P3。 +- **条件装饰不在生成栈中**:生成器产出的 `{Contract}DecoratorStack.Build(core)` 注册全部 `[Decorator]` 类型,运行时谓词(`Func`)仅适用于手动 `DecoratorStackBuilder` 组装。 + +示例见 [DesignPatterns.Samples.Decorator](https://github.com/Skymly/DesignPatterns.Samples/tree/main/DesignPatterns.Samples.Decorator):日志 + 耗时装饰、`DecoratorOrder` 常量引用、条件 `Add` 与 `PaymentServiceDecoratorStack.Build(core)` 对比。 + +## 参考 + +- GoF: Decorator (Structural) +- [docs/spec/Decorator.md](../spec/Decorator.md) +- [docs/spec/ChainOfResponsibility.md](../spec/ChainOfResponsibility.md) +- [docs/DEVELOPMENT.md](../DEVELOPMENT.md) +- [AGENTS.md](../../AGENTS.md) diff --git a/docs/design/EventAggregator.md b/docs/design/EventAggregator.md new file mode 100644 index 0000000..f7d71f4 --- /dev/null +++ b/docs/design/EventAggregator.md @@ -0,0 +1,108 @@ +# Design Doc: Event Aggregator + +> **关联 Spec**:[docs/spec/EventAggregator.md](../spec/EventAggregator.md) +> **关联 RFC**:[docs/rfc/XXX.md](../rfc/XXX.md)(如有) +> **关联 ADR**:ADR-XXX(如有) + +## 概述 + +Event Aggregator 提供进程内、轻量的 **发布/订阅(pub/sub)** 机制:按事件类型路由到已订阅的处理器,无需引入完整消息总线框架。 + +## 设计目标 + +1. 最小 API:`Subscribe` / `Unsubscribe` / `PublishAsync` +2. 异步一等:`IEventHandler.HandleAsync` 与 `CancellationToken` +3. 线程安全:订阅表在锁下修改,发布时对处理器列表做快照 +4. 不侵入 Core 的 DI 依赖;可选 `AddEventAggregator` 扩展 + +## 实现概览 + +### 运行时 + +`EventAggregator` 内部以 `Dictionary>` 维护事件类型到处理器列表的映射。 + +- `Subscribe` / `Unsubscribe` 在内部锁上修改 `Dictionary>`。 +- `PublishAsync` 在锁内复制当前处理器列表为 **快照**,随后在锁外调用,避免在 `HandleAsync` 执行期间持有锁。 +- 同一 `handler` 实例可重复 `Subscribe`(会多次出现在列表中);`Unsubscribe` 移除**第一次**匹配项。 + +#### 发布语义 + +- **顺序**:同一事件类型的处理器按 **订阅顺序** 依次调用。 +- **并发**:`PublishAsync` 在快照列表上顺序 `await` 各 `HandleAsync`;不同事件类型互不影响。 +- **空订阅**:无处理器时 `PublishAsync` 立即完成。 +- **取消**:在调用每个处理器前检查 `cancellationToken`;处理器内也应协作取消。 + +#### DI 集成 + +独立包 `DesignPatterns.Extensions.DependencyInjection`: + +```csharp +services.AddEventAggregator(); // 默认 Singleton +``` + +注册 `IEventAggregator` → `EventAggregator` 实例。处理器本身仍由应用注册到 DI 并手动 `Subscribe`,或由应用在启动时注入 `IEventAggregator` 后订阅。 + +### 源生成器 + +`RegisterEventHandlerGenerator`(增量源生成器,`IIncrementalGenerator` + `ForAttributeWithMetadataName`)在编译期扫描 `[RegisterEventHandler]` 特性: + +1. **扫描**:收集所有标注 `[RegisterEventHandler]` 的 handler 类。 +2. **按 event type 分组**:将 handler 按 `TEvent` 类型聚合。 +3. **生成 `{Event}EventHandlerRegistry`**:`Event` 后缀自动剥离(如 `OrderPlacedEvent` → `OrderPlacedEventHandlerRegistry`)。 +4. **静态路径 vs DI 路径**: + - **静态路径**:`SubscribeAll(IEventAggregator)`,仅包含有公共无参构造的 handler,以 `new Handler()` + `Subscribe` 订阅。 + - **DI 路径**:`RegisterDi(IServiceCollection, ServiceLifetime)` 注册 handler 实现到容器;`SubscribeAll(IEventAggregator, IServiceProvider)` 从容器解析 handler + `Subscribe`。条件为 `DesignPatterns.EnableDiIntegration=true`。 + - 静态路径仅包含有公共无参构造的 handler;DI 路径包含全部有效 handler。这与其他生成器的无参构造约束一致(DP007/DP009/DP014/DP019/DP022 同源)。 + +### 诊断 + +| ID | 归属 | 语义 | +|----|------|------| +| DP044 | Analyzer | 实现 `IEventHandler` 但未标注 `[RegisterEventHandler]`(Info) | +| DP045 | Generator | 同一 handler 类对同一 event type 重复标注 `[RegisterEventHandler]`(Error) | +| DP046 | Generator | 标注 `[RegisterEventHandler]` 但类未实现 `IEventHandler`(Error) | + +## 设计权衡 + +### 线程安全:lock + 快照 + +`Subscribe` / `Unsubscribe` 在内部锁上修改订阅表;`PublishAsync` 在锁内复制处理器列表为快照,随后在锁外顺序 `await` 各 `HandleAsync`。这样避免在用户代码(`HandleAsync`)执行期间持有锁,降低死锁风险与锁竞争。 + +### 发布语义:顺序 await + 取消检查 + +`PublishAsync` 在快照列表上**顺序** `await` 各 `HandleAsync`(非并行扇出),保证处理器按订阅顺序执行且异常传播可预期。在调用每个处理器前检查 `cancellationToken`;处理器内也应协作取消。 + +### 静态路径 vs DI 路径 + +- **静态路径**(`SubscribeAll(IEventAggregator)`)仅包含有**公共无参构造**的 handler,以 `new Handler()` 实例化。这与其他生成器的无参构造约束一致(DP007/DP009/DP014/DP019/DP022 同源)。 +- **DI 路径**(`RegisterDi` + `SubscribeAll(IEventAggregator, IServiceProvider)`)包含**全部有效 handler**,从容器解析实例。条件为 `DesignPatterns.EnableDiIntegration=true`。 + +### RegisterDi 默认 Transient + +`RegisterDi` 默认 `implementationLifetime: ServiceLifetime.Transient`(每次解析新实例,因 handler 通常无状态)。调用方可显式传入其他 `ServiceLifetime`。 + +## 与生态的边界 + +> 本库以技术探索为目的,**允许与 MediatR 能力重叠**。下表用于说明当前实现的设计取向差异,供选型参考,**非**「不实现」的理由(见 [AGENTS.md](../../AGENTS.md)「项目是什么」)。 + +| | Event Aggregator | 典型 MediatR | +|---|---|---| +| 范围 | 进程内、同 AppDomain | 可扩展管道、行为、请求/通知模型 | +| 路由 | 按 `TEvent` 类型 | 按 Request/Notification 类型 + 管道 | +| 编译期 | `[RegisterEventHandler]` 源生成器 + DP044/045/046 诊断 | 通常基于约定或显式注册 | + +## 已知局限 + +- 不做跨进程通信 +- 不做持久化 +- 不做重试策略 +- 不做请求/响应关联 ID + +以上为后续探索候选,准入标准见 [ROADMAP.md](../ROADMAP.md) F3。 + +## 参考 + +- 进程内 Observer / Mediator 变体(Behavioral) +- [AGENTS.md](../../AGENTS.md) — 项目规则与里程碑 +- [docs/DEVELOPMENT.md](../DEVELOPMENT.md) — 通用开发约定 +- 示例:[DesignPatterns.Samples.EventAggregator](https://github.com/Skymly/DesignPatterns.Samples/tree/main/DesignPatterns.Samples.EventAggregator) diff --git a/docs/design/FactoryRegistry.md b/docs/design/FactoryRegistry.md new file mode 100644 index 0000000..139ec4d --- /dev/null +++ b/docs/design/FactoryRegistry.md @@ -0,0 +1,169 @@ +# Design Doc: Factory Registry + +> **关联 Spec**:[docs/spec/FactoryRegistry.md](../spec/FactoryRegistry.md) +> **关联 RFC**:[docs/rfc/XXX.md](../rfc/XXX.md)(如有) +> **关联 ADR**:ADR-XXX(如有) + +## 概述 + +Factory Registry 按 key 解析**工厂实现**,每次 `Create` 调用对应 factory 委托得到新产品实例。本库提供: + +- **运行时**:`IFactoryRegistry`、`FactoryRegistryBuilder` 手动注册 +- **编译期**:`[RegisterFactory]` / `[RegisterFactory]` 源生成器 → 强类型 Keys + 静态 Registry +- **Analyzer**:DP023 提示未标记的工厂实现(与 Strategy DP006 对称) + +## 设计目标 + +1. 最小 API:`TryCreate` / `Create` / `Keys` +2. 显式失败:`FactoryNotFoundException` 带 key 信息 +3. 不侵入 Core 的 DI 依赖;生命周期由 factory 委托或 DI 扩展包负责 + +## 实现概览 + +### 运行时 + +**`FactoryRegistry`**:不可变实现,内部持有 `IReadOnlyDictionary>`。net8.0+ 上构造时将字典冻结为 `FrozenDictionary` 以加速查找。`Create(key)` 通过 `TryCreate` 解析 factory 委托并执行,未命中时抛 `FactoryNotFoundException.ForKey(key)`。 + +**`FactoryRegistryBuilder`**:手动注册入口,支持两种 factory 委托形式: + +- `Register(key, Func)`:每次 `Create(key)` 调用该 factory +- `Register(key, Func)`:factory 接收 key(内部包装为 `() => factory(key)`) + +重复 key 在 `Register` 时抛 `ArgumentException`(**运行时**检测)。 + +```csharp +using DesignPatterns.Creational; + +public interface IProduct +{ + string Name { get; } +} + +var registry = new FactoryRegistryBuilder() + .Register("standard", () => new StandardProduct()) + .Register("premium", () => new PremiumProduct()) + .Build(); + +var product = registry.Create("standard"); +if (!registry.TryCreate("unknown", out _)) +{ + // key 不存在 +} +``` + +**异步适配**:`FactoryRegistryAsyncExtensions.AsAsync()` 将同步 `IFactoryRegistry` 包装为 `IAsyncFactoryRegistry`,`CreateAsync` 内部调用同步 `Create` 并包装为 `ValueTask`;`cancellationToken` 对同步 factory 被忽略。 + +### 源生成器 + +**`RegisterFactoryGenerator`**:增量源生成器,扫描标注 `[RegisterFactory]` / `[RegisterFactory]` 的类,按契约分组。 + +**扫描阶段**:`ForAttributeWithMetadataName` 收集所有标注类,提取 `Key`、`Contract`(泛型版本从类型参数推断,非泛型版本从 `Contract` 属性读取)、`IsAsync`、`PoolSize`。 + +**Keys 生成**:对每个契约 `TContract` 生成 `{Contract}FactoryKeys`(命名规则与 Strategy 相同:去掉前缀 `I` 等),包含 `public const string` 常量。 + +```csharp +// ProductFactoryKeys.g.cs +public static partial class ProductFactoryKeys +{ + public const string Standard = "standard"; + public const string Premium = "premium"; +} +``` + +**Registry 生成**:对每个契约生成 `{Contract}FactoryRegistry`,暴露 `Create()` 返回 `IFactoryRegistry`。无 DI 时使用 `() => new Implementation()` 注册工厂委托;引用 DI 扩展包后额外生成 `Create(IServiceProvider)` 与 `RegisterDi(services)`。 + +```csharp +// ProductFactoryRegistry.g.cs +public static partial class ProductFactoryRegistry +{ + public static IFactoryRegistry Create() { ... } +} +``` + +`Create()` 返回的注册表在每次 `Create(key)` 时**新建**产品实例(与 Strategy 注册表返回单例实例不同)。 + +**异步路径**:当 `IsAsync=true` 或实现类实现 `IAsyncFactory` 时,生成器额外输出 `IAsyncFactoryRegistry` 与对应的 `CreateAsync` / `TryCreateAsync` 路径。 + +**池化路径**:当 `PoolSize > 0` 且工厂为异步时,生成器输出 `IPooledFactoryRegistry`,按 key 维护对象池。 + +### 诊断 + +| ID | 严重性 | 来源 | 触发条件 | +|----|--------|------|----------| +| DP020 | Error | 源生成器 | 同一 `TContract` 下 key 重复 | +| DP021 | Error | 源生成器 | 标记的类未实现指定的 `TContract` | +| DP022 | Error | 源生成器 | 标记的类缺少 public 无参构造 | +| DP023 | Info | Analyzer | 实现了某工厂契约但未加 `[RegisterFactory]` | +| DP053 | Error | 源生成器 | `IsAsync=true` 但未实现 `IAsyncFactory` | +| DP054 | Error | 源生成器 | `PoolSize` 为负数 | +| DP055 | Warning | 源生成器 | `PoolSize` 过大(可能导致内存过高) | + +DP023 属 **Analyzer**;DP020 / DP021 / DP022 / DP053 / DP054 / DP055 属**生成器**。 + +CodeFix:DP023 可一键添加 `[RegisterFactory("suggested-key", typeof(TContract))]`(与 DP006 对称)。 + +## 设计权衡 + +### Factory vs Strategy:实例 vs 创建新实例 + +Strategy Registry 注册的是**实例**(`key → TStrategy`),`Get(key)` 返回同一实例;Factory Registry 注册的是**工厂委托**(`key → Func`),`Create(key)` 每次调用返回新产品实例。二者语义不同:Strategy 用于「选一个算法执行」,Factory 用于「按需创建对象」。 + +### `IReadOnlyRegistry` 共享抽象 + +`IFactoryRegistry` 继承 `IReadOnlyRegistry`,与 `IStrategyRegistry` 共享该抽象(提供 `Keys` 和 `TryGet`)。Factory 的 `TryCreate` / `Create` 语义(执行委托产生新实例)在 `IReadOnlyRegistry` 之外扩展。 + +### DI 默认 Transient 而非 Singleton + +工厂模式的语义是每次 `Create` / `CreateAsync` 返回新产品实例,因此 `RegisterDi` 的 `implementationLifetime` 默认值为 `Transient`。这与 Strategy / Chain 等模式默认 `Singleton` 不同。 + +```csharp +// 默认:Transient(每次 Create 返回新产品) +ProductFactoryRegistry.RegisterDi(services); + +// 显式 Singleton(多次 Create 返回同一实现实例) +ProductFactoryRegistry.RegisterDi(services, implementationLifetime: ServiceLifetime.Singleton); +``` + +> **注意**:此默认值与 Strategy/Chain 等模式不同(后者默认 Singleton),因为工厂的语义是"创建新实例"而非"解析同一实例"。EventAggregator 也使用 Transient 默认值(handler 通常无状态)。 + +## 与生态的边界 + +### DI 集成 + +引用 `DesignPatterns.Extensions.DependencyInjection` 后,生成器为 `{Contract}FactoryRegistry` 增加: + +```csharp +ProductFactoryRegistry.RegisterDi(services); // TryAdd 各实现 + IFactoryRegistry<,> +// 或 +services.AddFactoryRegistry(builder => { ... }); // 手动 Builder +``` + +`Create(IServiceProvider sp)` 使用 `() => sp.GetRequiredService()` 注册工厂委托;**每次** `registry.Create(key)` 调用委托。 + +无 DI 包时仍可使用静态 `Create()`(`new Impl()`)。 + +### 与 Strategy Registry 的对比 + +| | Strategy Registry | Factory Registry | +|---|---|---| +| 注册内容 | key → **实例** | key → **每次 Create 新建** | +| 编译期 | `[RegisterStrategy]` | `[RegisterFactory]` | +| 共享抽象 | `IStrategyRegistry` → `IReadOnlyRegistry` | `IFactoryRegistry` → `IReadOnlyRegistry` | +| 未找到 | `StrategyNotFoundException` | `FactoryNotFoundException` | +| 未注册实现 | DP006 (Info) | DP023 (Info) | +| DI 默认 lifetime | Singleton | Transient | + +详见 [Strategy.md](../spec/Strategy.md)。 + +## 已知局限 + +- **不做抽象工厂族**:不提供多个产品类型族(Abstract Factory)的完整框架 +- **v1 无 async factory 完整框架**:异步工厂(`IAsyncFactory`)与池化(`IPooledFactoryRegistry`)在 v1 中为初步支持,后续版本完善 +- **无 DI 自动注册(Core)**:Core 不引用 MSDI,需引用 `DesignPatterns.Extensions.DependencyInjection` 并调用 `RegisterDi`,或使用手动 Builder +- **不用反射扫描程序集**:工厂注册通过 `[RegisterFactory]` 源生成器在编译期完成 + +## 参考 + +- GoF: Factory Method / 注册表变体(Creational) +- [Strategy.md](../spec/Strategy.md) — 策略注册表对比 +- [AGENTS.md](../../AGENTS.md) — 项目规则与里程碑 diff --git a/docs/design/README.md b/docs/design/README.md index e9941db..9f9543e 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -8,8 +8,16 @@ ## 已有 Design Doc -(Design Doc 将随模式文档迁移逐步创建。现有模式文档位于 `docs/.md`,将拆分迁移至 `docs/spec/` + `docs/design/`。) +| 模式 | Design Doc | Spec | 关联 ADR | +|------|------------|------|----------| +| Strategy | [Strategy.md](Strategy.md) | [Strategy.md](../spec/Strategy.md) | — | +| Chain of Responsibility | [ChainOfResponsibility.md](ChainOfResponsibility.md) | [ChainOfResponsibility.md](../spec/ChainOfResponsibility.md) | — | +| Composite | [Composite.md](Composite.md) | [Composite.md](../spec/Composite.md) | [ADR-006](../adr/ADR-006-composite-parallel-traversal.md)、[ADR-007](../adr/ADR-007-composite-tree-schema-validation.md) | +| Factory Registry | [FactoryRegistry.md](FactoryRegistry.md) | [FactoryRegistry.md](../spec/FactoryRegistry.md) | — | +| Decorator | [Decorator.md](Decorator.md) | [Decorator.md](../spec/Decorator.md) | — | +| Event Aggregator | [EventAggregator.md](EventAggregator.md) | [EventAggregator.md](../spec/EventAggregator.md) | — | +| State Transition Table | [StateTransitionTable.md](StateTransitionTable.md) | [StateTransitionTable.md](../spec/StateTransitionTable.md) | [ADR-005](../adr/ADR-005-state-transition-table.md) | -## 迁移计划 +## 迁移状态 -见 [docs/spec/README.md](../spec/README.md#迁移计划) 中的迁移计划表。 +所有 7 个模式文档已从 `docs/.md` 拆分迁移至 `docs/spec/` + `docs/design/`。旧文件已删除。 diff --git a/docs/design/StateTransitionTable.md b/docs/design/StateTransitionTable.md new file mode 100644 index 0000000..b5d9ed5 --- /dev/null +++ b/docs/design/StateTransitionTable.md @@ -0,0 +1,197 @@ +# Design Doc: State 转换表 + +> **关联 Spec**:[docs/spec/StateTransitionTable.md](../spec/StateTransitionTable.md) +> **关联 ADR**:[ADR-005](../adr/ADR-005-state-transition-table.md) +> **关联 RFC(归档)**:[docs/rfc/archive/StateTransitionTable.md](../rfc/archive/StateTransitionTable.md)、[docs/rfc/archive/HierarchicalStateMachine.md](../rfc/archive/HierarchicalStateMachine.md) + +## 概述 + +State 转换表为 **(当前状态, 触发器) → 下一状态** 的有限图提供轻量 primitive。本库提供: + +- **运行时**:`ITransitionTable`、`TransitionTableBuilder`、`Transition()` 扩展 +- **编译期**:`[StateMachine]` / `[Transition]` 源生成器 → `{StateEnum}TransitionTable` + partial holder 便捷方法 +- **v2 增强**:guard 委托(运行时 + 生成器)、DI 集成(`RegisterDi` 生成 + `AddTransitionTable` 扩展)、字面量边校验(DP036 Analyzer) +- **v3 增强**:层次状态机(`[StateParent]` + 编译期展平 + entry/exit action 链合成 + `IStateHierarchy` DI 集成) + +**不是**通用 UML 状态机框架(无并发状态、历史状态、持久化)。需要完整状态机 DSL 时继续使用 Stateless 等库。 + +## 设计目标 + +1. 提供与 Strategy/Factory/Chain 同级的轻量状态机 primitive +2. enum 状态 + enum 触发器 + 编译期生成冻结查找表,运行时零分配查找 +3. 声明式标注 + 生成胶水 + 可选手动 Builder,不侵入实现类的继承链 +4. 编译期诊断拦截非法转换、重复边、guard/action 签名错误 +5. 层次状态机(v3)通过编译期展平实现,运行时无层级开销 +6. 同时支持同步与异步路径(entry/exit action 仅 async 路径触发) + +## 实现概览 + +### 运行时 + +运行时核心位于 `DesignPatterns/Behavioral/`,提供以下关键类型: + +- **`ITransitionTable`**:只读转换表接口,方法 `TryTransition`、`GetAllowedTriggers`、`CanTransitionFrom`,属性 `InitialState`。约束 `where TState : struct, Enum`、`where TTrigger : struct, Enum`。 +- **`TransitionTable`**:不可变实现。net8.0 上内部使用 `FrozenDictionary` 优化查找;netstandard2.0 回退到普通 `Dictionary`。冻结查找表——`Build()` 后不可修改。 +- **`TransitionTableBuilder`**:手动注册 Builder。`WithInitial` 设置初始状态;`Add` 注册边,支持可选的 `guard`、`onEnterSync`、`onExitSync`、`onEnterAsync`、`onExitAsync` 参数(v3.4 合并为单一签名,所有可选参数默认 `null`)。 +- **`TransitionTableExtensions.Transition`**:扩展方法,非法边时抛 `InvalidTransitionException`(含当前态与触发器信息)。 +- **`InvalidTransitionException`**:含当前态与触发器信息的异常类型。 +- **`TransitionEdge`**:内部结构,存储 `Guard` 委托与 entry/exit action 委托。`TryTransition` 在命中边后调用 guard;guard 返回 `false` 时该次转换视为不存在。 +- **`IStateMachine` / `StateMachine`**:有状态包装器,自动跟踪 `CurrentState` 并在每次成功转换后更新。`TryTransition` 委托给底层 table;`TryTransitionAsync` 同理并触发 entry/exit action。**非线程安全**,设计为单线程使用。 +- **`TransitionResult`**:async 路径的返回类型,不暴露执行进度(部分执行语义见「设计权衡」)。 +- **`IStateHierarchy`**(v3):层次状态接口,方法 `GetParent`、`IsInState`、`GetAncestors`。生成的 `OrderStatusTransitionTable` 在层次模式下同时实现 `ITransitionTable` 和 `IStateHierarchy`。 + +执行顺序(`TryTransitionAsync`):guard → OnExit(sync → async)→ OnEnter(sync → async)→ 返回结果。 + +### 源生成器 + +`StateTransitionGenerator` 为增量源生成器(`IIncrementalGenerator`),使用 `ForAttributeWithMetadataName` 管线: + +- **`[StateMachine]` / `[Transition]` 扫描**:以 `[StateMachine]` 元数据名注册管线,收集 holder 类上的 `[Transition]` 特性。对每个 state enum 生成 `{StateEnum}TransitionTable` 类与 holder 上的便捷方法(`TryTransition`、`InitialState`)。 +- **`{StateEnum}TransitionTable` 生成**:输出 `Instance` 静态单例属性、`InitialState` 属性、`TryTransition` / `CanTransitionFrom` / `GetAllowedTriggers` 方法。net8.0 上使用 `FrozenDictionary` 优化查找。 +- **Guard 委托生成**:`[Transition]` 的 `Guard` 命名属性指定 holder 类上的 static 方法名。生成器将 guard 方法引用编译为委托并嵌入生成的转换表。校验 guard 方法须 static(DP034)、签名 `bool Method(TState, TTrigger)`(DP035)、在 holder 类上声明(DP032)。 +- **Entry/Exit action 生成**:`[Transition]` 的 `OnEnter` / `OnExit` 命名属性指定 holder 类上的 static 方法名。校验 action 方法须 static(DP038)、签名 `void Method(TState, TState, TTrigger)` 或 `ValueTask Method(TState, TState, TTrigger, CancellationToken)`(DP039)、在 holder 类上声明(DP037)。 +- **`[StateParent]` 收集**(v3.2):收集 holder 类上的 `[StateParent]` 特性,构建父子关系图。诊断 DP056–DP059 覆盖:循环父链、自引用父、未知子/父状态、孤立父声明。 +- **`HierarchyFlattener` 边继承展平**(v3.2):`[StateParent]` 声明的父子关系在编译期被展平——父状态上的边自动继承到所有子状态。例如 `Active + Cancel → Cancelled` 会被展平为 `Submitted + Cancel → Cancelled` 和 `Paid + Cancel → Cancelled`,无需重复声明。 +- **LCA 算法 + entry/exit action 链合成**(v3.3):当层次模式下一条展平后的边跨越多个层级时,源生成器按 RFC §8 的 LCA(最低公共祖先)算法计算 exit/enter 链并合成复合委托(composite delegate): + - **Exit 链**:从 `from` 向上到 LCA(不含 LCA),按从子到父的顺序执行 + - **Enter 链**:从 LCA 向下到 `to`(不含 LCA),按从父到子的顺序执行 + - **复合委托**:仅当链有 2+ action 时生成 `CompositeExit_{From}_{Trigger}` / `CompositeEnter_{From}_{Trigger}` 静态方法;1 action 直接用原引用;0 action 用 null + - 复合委托对运行时透明:`TransitionEdge` 结构不变,复合委托是单个 `Action` 或 `Func<...,ValueTask>` + + **边界场景(RFC §8.4)**: + + | 场景 | Exit 链 | Enter 链 | + |------|---------|----------| + | 自环(from == to) | `[from]` | `[from]` | + | LCA == from(进入后代) | `[]`(空) | LCA → to 的子链 | + | LCA == to(返回祖先) | from → LCA 的子链 | `[]`(空) | + | 无公共祖先 | from 全祖链 | to 全祖链 | + +- **DI 集成生成**:当消费项目引用 `DesignPatterns.Extensions.DependencyInjection`(自动 Import `build/DesignPatterns.Extensions.DependencyInjection.targets`,设置 `DesignPatterns_EnableDiIntegration=true`)时,生成器在转换表类上额外输出 `RegisterDi` 方法。v3 层次模式下 `RegisterDi` 自动注册 `ITransitionTable` + `IStateHierarchy`。 + +### 诊断 + +#### 生成器诊断 + +| 检测点 | 逻辑 | 报告位置 | +|--------|------|----------| +| DP026(重复边) | 同一 `(from, trigger)` 对出现多次 | 重复的 `[Transition]` 特性 | +| DP027(state 非 enum 成员) | `[Transition]` 的 from/to 非 state enum 成员 | 特性参数位置 | +| DP028(trigger 非 enum 成员) | `[Transition]` 的 trigger 非 trigger enum 成员 | 特性参数位置 | +| DP029(Initial 非 enum 成员) | `Initial` 非 state enum 成员 | `Initial` 属性位置 | +| DP030(holder 非 static partial) | holder 类非 static partial class | 类声明 | +| DP031(终态提示) | state 从未作为 `from` 出现 | state enum 成员(Info) | +| DP032(guard 未找到) | guard 方法在 holder 类上未找到 | `Guard` 属性位置 | +| DP034(guard 非 static) | guard 方法非 static | `Guard` 属性位置 | +| DP035(guard 签名错误) | guard 方法签名非 `bool Method(TState, TTrigger)` | `Guard` 属性位置 | +| DP037(action 未找到) | entry/exit action 方法在 holder 类上未找到 | `OnEnter`/`OnExit` 属性位置 | +| DP038(action 非 static) | entry/exit action 方法非 static(防御性;CS0708 先于生成器拒绝 static 类中的实例成员,无法通过生成器测试触发) | `OnEnter`/`OnExit` 属性位置 | +| DP039(action 签名错误) | entry/exit action 方法签名非 `void Method(TState, TState, TTrigger)` 或 `ValueTask Method(TState, TState, TTrigger, CancellationToken)` | `OnEnter`/`OnExit` 属性位置 | +| DP056(父链循环) | `[StateParent]` 父链存在循环 | `[StateParent]` 特性 | +| DP057(非法 enum 成员) | `[StateParent]` 的 child/parent 非 state enum 成员 | 特性参数位置 | +| DP058(自引用父) | `[StateParent]` 的 child == parent | `[StateParent]` 特性 | +| DP059(孤立父声明) | `[StateParent]` 的 parent 从未作为任何边的 state 出现 | `[StateParent]` 特性 | + +#### Analyzer 诊断 + +| 检测点 | 逻辑 | 报告位置 | +|--------|------|----------| +| DP036(字面量边未声明) | `StateTransitionLiteralEdgeAnalyzer` 在编译期扫描所有程序集的 `[StateMachine]` + `[Transition]` 特性,构建有效边集合,然后校验 `TryTransition` 调用的字面量 (state, trigger) 参数对 | `TryTransition` 调用参数 | + +DP036 特性: +- 严重级别 **Info**(与 DP025 一致;运行时 `TryTransition` 仅返回 `false`,非错误) +- 跨程序集支持(表可在引用程序集中声明) +- 非常量参数跳过(运行时计算的值无法编译期校验) +- 仅校验 `TryTransition`(`CanTransitionFrom` 只接收 state,无 trigger 可校验) + +```csharp +// DP036: (Cancelled, Submit) 未声明 +table.TryTransition(OrderStatus.Cancelled, OrderTrigger.Submit, out _); +``` + +## 设计权衡 + +### enum-only 状态(简洁 vs 灵活) + +选择 **enum-only** 状态/触发器,不支持 `string` / `int` 键。原因: + +- 编译期生成冻结查找表需要编译期已知的键集合;enum 满足,string/int 不满足。 +- enum 提供编译期类型安全与 IDE 补全;string/int 需运行时校验。 +- 与 Stateless 等库差异化定位——Stateless 支持 string/enum,本库专注 enum 场景做到编译期极致。 + +代价:不支持任意类型状态。需要 string/int 键的场景使用 Stateless。 + +### 编译期展平(零运行时层级开销) + +v3 层次状态选择 **编译期展平**(`HierarchyFlattener`)而非运行时层级遍历。原因: + +- 运行时零层级开销——展平后的转换表与扁平表结构相同,`TryTransition` 查找路径无差异。 +- 编译期已知所有 `[StateParent]` 关系,可在生成时展平。 +- `IStateHierarchy` 仍提供运行时层级查询(`GetParent`、`IsInState`、`GetAncestors`),但转换路径不经过层级遍历。 + +代价:生成器复杂度增加(展平 + LCA + 复合委托合成);层级关系变更需重新编译。 + +### LCA-based entry/exit 链 + +v3.3 选择 **LCA(最低公共祖先)算法**计算 entry/exit action 链。原因: + +- LCA 确定从 `from` 到 `to` 转换时的最小 action 集合——不重复执行公共祖先的 action。 +- Exit 链从 `from` 向上到 LCA(不含 LCA),Enter 链从 LCA 向下到 `to`(不含 LCA),符合 UML 状态机语义。 +- 复合委托对运行时透明:`TransitionEdge` 结构不变。 + +### 部分执行语义 + +Entry/exit action 选择 **部分执行**语义——若 OnExit 成功后 OnEnter 抛异常,异常传播给调用方,OnExit 副作用已发生。原因: + +- 补偿/回滚是业务逻辑,不在此库范围。 +- `TransitionResult` 不暴露执行进度,避免增加复杂度。 +- 调用方需自行处理补偿。 + +## 与生态的边界 + +### 与 Stateless 的关系 + +| | DesignPatterns State 转换表 | Stateless | +|---|---|---| +| 状态类型 | enum-only | enum / string / int | +| 转换表 | 编译期生成冻结查找表 | 运行时配置 | +| 诊断 | 编译期诊断(DP026–DP039, DP056–DP059) | 运行时异常 | +| 层次状态 | v3 编译期展平 | 运行时层级遍历 | +| 历史/并发状态 | 不支持 | 支持 | +| 持久化 | 不支持 | 支持(Stateless 本身不持久化,但可序列化配置) | +| 定位 | 轻量 primitive + 编译期胶水 | 通用状态机 DSL | + +需要完整 UML 状态机(历史、并发、持久化)时使用 Stateless;需要编译期诊断与零运行时开销的 enum 状态机时使用本库。 + +### DI 集成 + +**当前已实现**:`DesignPatterns.Extensions.DependencyInjection`(MSDI)+ 生成器 `RegisterDi`。 + +- `RegisterDi` 使用 `TryAdd` 语义注册 `ITransitionTable`(v3 层次模式时额外注册 `IStateHierarchy`)。 +- 手动注册扩展:`AddTransitionTable`(注册预构建表实例)、`AddStateMachine`、`AddStateHierarchy`(v3)。 +- Core 不引用 DI;启用 DI 生成路径需引用扩展包(targets 打开 `DesignPatterns_EnableDiIntegration`)。 + +### Autofac + +`DesignPatterns.Extensions.Autofac` — `RegisterAutofac`(见 [Autofac.md](../Autofac.md))。 + +## 已知局限 + +- **不支持并发状态 / 历史状态**:v3 实现了层次状态,但并发 / 历史状态仍为非目标。 +- **不支持 `string` / `int` 状态键**:仅支持 enum 状态/触发器。 +- **不支持 guard 的编译期表达式求值**:v2 仅运行时委托 + 方法引用校验。 +- **`StateMachine` 非线程安全**:设计为单线程使用。多线程场景应在调用方同步,或每线程使用独立实例。 +- **部分执行语义**:OnExit 成功后 OnEnter 抛异常时,OnExit 副作用已发生,调用方需自行处理补偿。 +- **Entry/Exit action 仅 async 路径触发**:同步 `TryTransition` 不调用 action。 + +## 参考 + +- [ADR-005: State transition table](../adr/ADR-005-state-transition-table.md) +- [RFC: StateTransitionTable(归档)](../rfc/archive/StateTransitionTable.md) +- [RFC: HierarchicalStateMachine(归档)](../rfc/archive/HierarchicalStateMachine.md) +- [Spec: State 转换表](../spec/StateTransitionTable.md) — 稳定契约(API 面、诊断 ID、不变量) +- [AGENTS.md](../../AGENTS.md) — 项目规则与里程碑 +- [docs/DEVELOPMENT.md](../DEVELOPMENT.md) — 通用开发约定 +- [Autofac.md](../Autofac.md) — Autofac 扩展 +- [DesignPatterns.Samples.State](https://github.com/Skymly/DesignPatterns.Samples/tree/main/DesignPatterns.Samples.State) — 基础状态机(guard、entry/exit action、DI) +- [DesignPatterns.Samples.HierarchicalState](https://github.com/Skymly/DesignPatterns.Samples/tree/main/DesignPatterns.Samples.HierarchicalState) — 层次状态机(`[StateParent]`、边继承展平、action 链合成、`IStateHierarchy` DI) diff --git a/docs/design/Strategy.md b/docs/design/Strategy.md new file mode 100644 index 0000000..c549eea --- /dev/null +++ b/docs/design/Strategy.md @@ -0,0 +1,137 @@ +# Design Doc: Strategy + +> **关联 Spec**:[docs/spec/Strategy.md](../spec/Strategy.md) + +## 概述 + +Strategy 模式允许在运行时按条件选择算法/行为实现,避免大量 `if/switch` 分支。本库的定位是: + +- 提供 **注册表 + 编译期生成** 减少手写胶水 +- **不替代** DI 容器的生命周期管理 +- **不做** 策略选择路由引擎(那是业务逻辑) + +## 设计目标 + +1. 用户只需在实现类上打一个特性,即可获得:强类型 Key 常量 + 注册表 + 可选 DI 扩展 +2. 编译期检查 key 重复、接口不匹配 +3. 不侵入实现类的继承链 +4. 同时支持同步与异步策略 + +## 实现概览 + +### 运行时 + +运行时核心位于 `DesignPatterns/Behavioral/`,提供以下关键类型: + +- **`IStrategy` / `IAsyncStrategy`**:可选标记接口,便于表达同步/异步算法形状。注册表与生成器不依赖它们。 +- **`IStrategyRegistry`**:按 key 解析策略实现的只读注册表接口,约束 `where TKey : notnull`。提供 `TryGet`、`Get`(找不到抛 `StrategyNotFoundException`)、`TryGetWithGuard`、`Keys`。 +- **`StrategyRegistry`**:不可变实现。net8.0 上内部使用 `FrozenDictionary` 优化查找;netstandard2.0 回退到普通 `Dictionary`。 +- **`StrategyRegistryBuilder`**:手动注册 Builder,支持 `Register(key, strategy)` 与 `Register(key, factory)` 两种重载,`Build()` 返回 `IStrategyRegistry`。 +- **`StrategyRegistryExtensions`**:提供 `ExecuteAsync` / `TryExecuteAsync` 便利方法,按 key 解析并执行异步策略。`TryExecuteAsync` 与 `ExecuteAsync` 对称,未命中 key 时返回 `false` 而不抛异常。 + +### 源生成器 + +`RegisterStrategyGenerator` 为增量源生成器(`IIncrementalGenerator`),使用 `ForAttributeWithMetadataName` 管线: + +- **双注册**:同时注册泛型版(`RegisterStrategyAttribute`1`)与非泛型版(`RegisterStrategyAttribute`)两个元数据名,合并处理。消费者使用 `net7.0+` 的项目自动获得泛型版;低版本使用非泛型版,体验稍弱但功能等价。 +- **按 `TContract` 分组**:对于每个 `TContract`,生成器输出三类产物: + 1. **强类型 Key 常量类**(`{ContractName}Keys`,`public const string`),命名规则为 `{接口名去掉前缀I和后缀Strategy}Keys`,可通过特性参数 override。 + 2. **静态注册表**(`{ContractName}Registry`),包含 `Instance` 静态属性(`new()` 静态单例,无 DI 生命周期)。 + 3. **DI 集成方法**(引用 `DesignPatterns.Extensions.DependencyInjection` 时自动启用):`Create(IServiceProvider)` 返回 `ServiceProviderStrategyRegistry`,`RegisterDi(IServiceCollection, ...)` 注册到容器。 + +### 诊断 + +| 检测点 | 逻辑 | 报告位置 | +|--------|------|----------| +| DP003(key 重复) | 同一 `TContract` 下出现相同 key | 重复标注的类声明 | +| DP004(接口不匹配) | 标记的类未实现指定的 `TContract` | 标注特性位置 | +| DP006(未注册策略) | 实现了某策略契约但未加 `[RegisterStrategy]` | 类声明(Analyzer + CodeFix) | +| DP007(缺少无参构造) | 标记的类缺少 public 无参构造 | 类声明 | +| DP047–DP049(guard 校验) | guard 方法未找到 / 非 static / 签名错误 | `Guard` 属性位置 | + +> DP006 属 Analyzer(含 CodeFix);DP003/DP004/DP007/DP047–DP049 属生成器。DP005 属于 Handler,不属于 Strategy。 + +## 设计权衡 + +### 泛型 Attribute 兼容策略(`#if NET7_0_OR_GREATER`) + +库目标 `netstandard2.0`,泛型 Attribute 需要 C# 11 + `net7.0+`。采用条件编译方案: + +- **定义侧**:在 `netstandard2.0` 目标下用 `#if NET7_0_OR_GREATER` 条件编译泛型版 `RegisterStrategyAttribute`;非泛型版始终可用。 +- **生成器侧**:`ForAttributeWithMetadataName` 同时注册两个元数据名(泛型版与非泛型版),合并处理。 +- **消费者侧**:使用 `net7.0+` 的项目自动获得泛型版;低版本使用非泛型版,体验稍弱但功能等价。 + +```csharp +// 条件编译示例 +#if NET7_0_OR_GREATER +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] +public sealed class RegisterStrategyAttribute : Attribute { ... } +#endif + +// 非泛型版始终可用 +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] +public sealed class RegisterStrategyAttribute : Attribute { ... } +``` + +### Guard 为 `Func` 而非 `Func` + +guard 签名固定为 `Func`(仅接收 key),不接收 `TInput`。原因:注册表层面不知道 `TInput`(`TStrategy` 不要求实现 `IStrategy`),因此无法基于输入判断。基于输入的动态路由是业务逻辑,不在此库范围。 + +### `TStrategy` 不变(invariant) + +`IStrategyRegistry` 的 `TStrategy` 非协变 `out`——`TryGetWithGuard` 的 `out TStrategy` 参数阻止协变。这是 API 设计的自然结果,非刻意限制。 + +## 与生态的边界 + +### DI 集成 + +**当前已实现**:`DesignPatterns.Extensions.DependencyInjection`(MSDI)+ 生成器 `RegisterDi` / `Create(IServiceProvider)`(Strategy / Factory / Handler)。 + +- 生命周期由 DI 控制;注册表通过 `ServiceProviderStrategyRegistry` 在 `TryGet` 时解析实现类型。 +- Core 不引用 DI;启用 DI 生成路径需引用扩展包(targets 打开 `DesignPatterns_EnableDiIntegration`)。 + +| 生命周期组合 | 行为 | +|--------------|------| +| Singleton 实现 + Singleton 注册表 | 默认;`TryGet` 每次解析同一实现实例 | +| Transient 实现 + Singleton 注册表 | `TryGet` 每次从容器取新实例(推荐需要可变策略时) | +| Transient 注册表 | 每次解析注册表时重建 `Create(sp)` | + +手动 Builder 仍可用:`services.AddStrategyRegistry(...)`(见扩展包),与生成器互不冲突。 + +### Autofac + +`DesignPatterns.Extensions.Autofac` — `RegisterAutofac`(见 [Autofac.md](../Autofac.md))。 + +### 插件程序集 + +多项目 `[RegisterStrategy]` 布局见 [PluginAssemblies.md](../PluginAssemblies.md)。 + +### 与 Factory Registry 的关系 + +| | Strategy Registry | Factory Registry | +|---|---|---| +| 注册内容 | key → 实例(或 build 时 invoke 一次的 factory) | key → `Func` | +| 生命周期 | 通常 Singleton(生成器用 `new()` 静态单例) | 每次 `Create` 调用 factory | +| 编译期 | `[RegisterStrategy]` 生成 Keys + Registry | `[RegisterFactory]` 生成 Keys + `Create()` | +| 共享抽象 | `IStrategyRegistry` 继承 `IReadOnlyRegistry` | `IFactoryRegistry` 继承 `IReadOnlyRegistry` | +| DI | `PaymentStrategyRegistry.RegisterDi(services)` | `ProductFactoryRegistry.RegisterDi(services)` | + +详见 [FactoryRegistry.md](../spec/FactoryRegistry.md)。 + +## 已知局限 + +- **不做 DI 生命周期管理**:注册表不替代 DI 容器的生命周期管理;`Instance` 为 `new()` 静态单例,无 DI 生命周期。 +- **不做策略路由引擎**:策略选择/路由逻辑是业务代码,不在此库范围。guard 仅基于 key,不基于输入。 +- **不通过反射扫描程序集**:注册依赖编译期源生成器,不做运行时反射扫描。 +- **不做 `Func` 的无限委托展开**。 +- **不强制所有策略实现 `IStrategy<,>`**:`IStrategy<,>` / `IAsyncStrategy<,>` 为可选标记接口。 + +## 参考 + +- GoF: Strategy (Behavioral) +- [AGENTS.md](../../AGENTS.md) — 项目规则与里程碑 +- [docs/DEVELOPMENT.md](../DEVELOPMENT.md) — 通用开发约定 +- [Spec: Strategy](../spec/Strategy.md) — 稳定契约(API 面、诊断 ID、不变量) +- [FactoryRegistry.md](../spec/FactoryRegistry.md) — Factory Registry 模式文档 +- [Autofac.md](../Autofac.md) — Autofac 扩展 +- [PluginAssemblies.md](../PluginAssemblies.md) — 插件程序集布局 diff --git a/docs/rfc/archive/CompositeTreeSchemaValidation.md b/docs/rfc/archive/CompositeTreeSchemaValidation.md index 6bd70a8..12a0476 100644 --- a/docs/rfc/archive/CompositeTreeSchemaValidation.md +++ b/docs/rfc/archive/CompositeTreeSchemaValidation.md @@ -270,7 +270,7 @@ private sealed record CompositeSchemaInfo( | `DesignPatterns.SourceGenerators/AnalyzerReleases.Unshipped.md` | **修改** — 新增 DP063/DP064/DP065 条目 | | `tests/DesignPatterns.SourceGenerators.Tests/Generators/CompositePartGeneratorTests.cs` | **修改** — 新增 10 个测试 | | `tests/DesignPatterns.Tests/Structural/CompositeSchemaAttributeTests.cs` | **新增** — 特性构造测试 | -| `docs/Composite.md` | **修改** — 文档补充 schema 校验说明 | +| `docs/spec/Composite.md` + `docs/design/Composite.md` | **修改** — 文档补充 schema 校验说明 | | `docs/ROADMAP.md` | **修改** — 标记完成 | **PR 边界**:跨 Runtime + Diagnostics + SourceGenerators 三个模块。按 AGENTS.md 规范,应拆分为: diff --git a/docs/rfc/archive/StateTransitionTable.md b/docs/rfc/archive/StateTransitionTable.md index 3f94c15..8d1d71f 100644 --- a/docs/rfc/archive/StateTransitionTable.md +++ b/docs/rfc/archive/StateTransitionTable.md @@ -42,7 +42,7 @@ - 层次 / 并发 / 历史状态(UML 状态机超集) - 持久化快照、分布式状态、超时转换 -- ~~Entry / exit / internal transition 动作~~(v1 非目标;**v2 已实现** entry/exit action 委托 + `[Transition(OnEnter/OnExit)]` 源生成,见 [StateTransitionTable.md](../../StateTransitionTable.md)) +- ~~Entry / exit / internal transition 动作~~(v1 非目标;**v2 已实现** entry/exit action 委托 + `[Transition(OnEnter/OnExit)]` 源生成,见 [StateTransitionTable.md](../../design/StateTransitionTable.md)) - Guard 的编译期表达式求值(v1 无 guard;v2 仅运行时委托) - `string` / `int` 状态键(v1 仅 **enum**;与 DP025 字面量键校验正交,后续再评) - 反射扫描或 AppDomain 自动发现转换 @@ -295,7 +295,7 @@ OrderStatusTransitionTable.RegisterDi(services); // Singleton ITransitionTable<, services.AddTransitionTable(OrderStatusTransitionTable.Instance); ``` -启用开关:MSBuild 属性 `DesignPatterns_EnableDiIntegration`(引用 DI 扩展包时自动为 `true`)。详见 [StateTransitionTable.md](../../StateTransitionTable.md#v2di-集成)。 +启用开关:MSBuild 属性 `DesignPatterns_EnableDiIntegration`(引用 DI 扩展包时自动为 `true`)。详见 [StateTransitionTable.md](../../design/StateTransitionTable.md#v2di-集成)。 --- @@ -356,9 +356,9 @@ services.AddTransitionTable(OrderStatusTransitionTable.Instance); ## 15. 参考 -- [Strategy.md](../../Strategy.md) — 注册表 + 生成器模式参考 +- [Strategy.md](../../spec/Strategy.md) — 注册表 + 生成器模式参考 - [FactoryKeyConventions.md](../../FactoryKeyConventions.md) — 键约定(State v1 不用 string key) -- [EventAggregator.md](../../EventAggregator.md) — 组合用 pub/sub +- [EventAggregator.md](../../spec/EventAggregator.md) — 组合用 pub/sub - [ROADMAP.md](../../ROADMAP.md) F3 准入标准 - Stateless 文档 — 生态边界对照 diff --git a/docs/spec/ChainOfResponsibility.md b/docs/spec/ChainOfResponsibility.md new file mode 100644 index 0000000..56aa126 --- /dev/null +++ b/docs/spec/ChainOfResponsibility.md @@ -0,0 +1,96 @@ +# Spec: Chain of Responsibility + +> **版本**:v0.2.2(与 NuGet 包版本对齐) +> **关联 Design Doc**:[docs/design/ChainOfResponsibility.md](../design/ChainOfResponsibility.md) +> **关联 ADR**:ADR-XXX(如有) + +## API 面 + +### 运行时接口 + +运行时类型位于 `DesignPatterns/Behavioral/`。 + +| 类型 | 说明 | +|------|------| +| `HandlerDelegate` | 调用链中下一阶段的委托 | +| `IHandler` | 单个 handler:`InvokeAsync(context, next, ct)` | +| `HandlerPipelineBuilder` | `Use(handler)` / `Use(delegate)`,`Build()` | +| `HandlerPipeline` | 不可变管道,`InvokeAsync(context, ct)`、`InvokeTracedAsync`(见下文) | +| `HandlerPipelineTrace` | 一次 traced 调用的逐步结果 | +| `HandlerPipelineStep` | 单步:索引、handler 显示名、状态 | +| `HandlerPipelineStepStatus` | `Completed` / `ShortCircuited` / `NotReached` | + +`HandlerPipelineStepStatus` 取值含义: + +| `HandlerPipelineStepStatus` | 含义 | +|----------------------------|------| +| **Completed** | Handler 已执行且调用了 `next` | +| **ShortCircuited** | Handler 已执行但**未**调用 `next`(管道在此 handler 之后不再向下) | +| **NotReached** | Handler **未执行**(因更早的 handler 短路) | + +`InvokeTracedAsync` 不改变执行顺序与短路语义,仅额外返回 `HandlerPipelineTrace`;委托 handler 在 trace 中显示名为 `""`。 + +`HandlerPipelineTrace.WasShortCircuited` 为「是否存在任一步为 `ShortCircuited`」;终端 handler 不调用 `next` 时也会为 `true`,不宜单独作为「业务短路」判据。 + +### 特性(Attribute) + +```csharp +// 泛型(推荐,C# 11+ / net8.0) +[HandlerOrder(10)] +public sealed class LoggingHandler : IHandler { } + +// 非泛型(netstandard2.0) +[HandlerOrder(10, typeof(RequestContext))] +public sealed class LoggingHandler : IHandler { } +``` + +数值越小越先执行(与手动 `Use` 注册顺序一致)。 + +同一 handler 类可标注多个 `[HandlerOrder<...>]`(`AllowMultiple = true`),分别加入不同 `TContext` 的生成管道,例如共享日志 handler 同时实现 `IHandler` 与 `IHandler`。同一 context 下重复的 Order 仍报 **DP005**。 + +### 生成器产出 + +对每种 `TContext` 生成 `{Context}HandlerPipeline`: + +```csharp +public static partial class RequestContextHandlerPipeline +{ + public static HandlerPipeline Instance { get; } +} +``` + +引用 `DesignPatterns.Extensions.DependencyInjection` 后,`{Context}HandlerPipeline` 额外生成: + +```csharp +RequestContextHandlerPipeline.RegisterDi(services); +var pipeline = provider.GetRequiredService>(); +``` + +`Create(IServiceProvider sp)` 按 `[HandlerOrder]` 顺序 `GetRequiredService` 各 handler。Singleton 管道在首次构建时固定 handler 实例;需要每次解析新管道时使用 `registryLifetime: ServiceLifetime.Transient`。 + +手动注册:`services.AddHandlerPipeline(builder => ...)`(扩展包)。 + +## 诊断 ID + +| ID | 级别 | 触发条件 | 消息格式 | +|----|------|----------|----------| +| DP005 | Error | 同一 context 下重复的 Order | ... | +| DP008 | Error | 标注 `[HandlerOrder]` 但未实现 `IHandler` | ... | +| DP009 | Error | 标注 `[HandlerOrder]` 的 handler 缺少 public 无参构造 | ... | +| DP024 | Info | Analyzer:实现了 `IHandler` 但未加 `[HandlerOrder]`(该 context 已有其它 handler 注册) | ... | +| DP024 | Info | CodeFix:一键添加 `[HandlerOrder(order, typeof(TContext))]` | ... | + +## 不变量 + +1. Handler **调用** `await next(context, ct)` → 继续后续 handler;Handler **不调用** `next` → 后续 handler 不再执行(inbound 与 outbound 均跳过)——即**通过是否调用 `next` 实现短路**,不强制业务 context 实现额外接口。 +2. 空管道:`InvokeAsync` / `InvokeTracedAsync` 正常完成,trace 为空。 +3. `Use(null)` 抛 `ArgumentNullException`。 + +## 兼容基线 + +- netstandard2.0 + net8.0(两者均须可用并随包分发) +- Roslyn 组件 4.8.0(`Microsoft.CodeAnalysis.CSharp` / Workspaces;Analyzers 3.3.4) + +## 不在范围内 + +- Autofac / DryIoc 扩展(可选,后续) diff --git a/docs/spec/Composite.md b/docs/spec/Composite.md new file mode 100644 index 0000000..8f016b7 --- /dev/null +++ b/docs/spec/Composite.md @@ -0,0 +1,144 @@ +# Spec: Composite + +> **版本**:v0.2.2(与 NuGet 包版本对齐) +> **关联 Design Doc**:[docs/design/Composite.md](../design/Composite.md) +> **关联 ADR**:[ADR-006](../adr/ADR-006-composite-parallel-traversal.md)、[ADR-007](../adr/ADR-007-composite-tree-schema-validation.md) + +## API 面 + +### 运行时接口 + +命名空间:`DesignPatterns.Structural` + +| 类型 | 签名 | 说明 | +|------|------|------| +| `ICompositeNode` | `IReadOnlyList Children { get; }` where `TSelf : ICompositeNode` | 节点契约:通过 `Children` 区分叶子与分支,空列表为叶子 | +| `ICompositeBuildable` | `void SetChildren(IReadOnlyList children)` where `TNode : ICompositeNode` | 装配时接收子节点;实现应在装配后冻结 children,后续调用视为无效 | +| `CompositeTraverser` | `static void Traverse(TNode root, Action visitor, CompositeTraversalOptions? options = null)` | 同步遍历单根树(visitor: node, depth, siblingIndex) | +| | `static void TraverseForest(IReadOnlyList roots, Action visitor, CompositeTraversalOptions? options = null)` | 同步遍历森林(多根树) | +| | `static ValueTask TraverseAsync(TNode root, Func visitor, CompositeTraversalOptions? options = null, CancellationToken cancellationToken = default)` | 异步遍历单根树 | +| | `static ValueTask TraverseForestAsync(IReadOnlyList roots, Func visitor, CompositeTraversalOptions? options = null, CancellationToken cancellationToken = default)` | 异步遍历森林 | +| | `static void TraverseParallel(TNode root, Action visitor, CompositeTraversalOptions? options = null)` | 并行遍历单根树(访问顺序非确定) | +| | `static void TraverseForestParallel(IReadOnlyList roots, Action visitor, CompositeTraversalOptions? options = null)` | 并行遍历森林(根间串行,子树内并行) | +| | `static ValueTask TraverseParallelAsync(TNode root, Func visitor, CompositeTraversalOptions? options = null, CancellationToken cancellationToken = default)` | 异步并行遍历单根树 | +| | `static ValueTask TraverseForestParallelAsync(IReadOnlyList roots, Func visitor, CompositeTraversalOptions? options = null, CancellationToken cancellationToken = default)` | 异步并行遍历森林 | +| `CompositeTraversalOptions` | `CompositeTraversalOrder Order { get; set; }`(默认 `DepthFirstPreOrder`) | 遍历顺序 | +| | `int? MaxDepth { get; set; }`(`null` = 无限制) | 最大访问深度(0 = 仅根) | +| | `bool VisitLeavesOnly { get; set; }` | 仅访问叶子节点 | +| | `Func? ShouldSkipSubtree { get; set; }` | 返回 `true` 时跳过该子树 | +| | `int? MaxDegreeOfParallelism { get; set; }`(`null` = `Environment.ProcessorCount`) | 并行度上限 | +| | `int MaxParallelDepth { get; set; }`(默认 32) | 并行递归深度上限,超过后回退串行 | +| `CompositeTreeBuilder` | `CompositeTreeBuilder Leaf(TNode node)` | 添加叶子节点 | +| | `CompositeTreeBuilder Branch(TNode node, Action> configure)` | 添加分支节点(嵌套 builder 配置子节点) | +| | `TNode Build()` | 构建单根树(要求恰好一个顶层节点) | +| `CompositeCatalogEntry` | `CompositeCatalogEntry(string key, string? parentKey, int order, Type implementationType)` | Catalog 条目 | +| | `string Key { get; }` | 唯一键 | +| | `string? ParentKey { get; }` | 父键(`null` 为根候选) | +| | `int Order { get; }` | 同父兄弟间排序(升序) | +| | `Type ImplementationType { get; }` | 实现类型 | +| `CompositeCatalogAssembler` | `static TNode Assemble(IReadOnlyList> entries)` where `TNode : class, ICompositeNode` | 从 catalog 装配单根树(`Activator.CreateInstance`) | +| | `static TNode Assemble(IReadOnlyList> entries, IServiceProvider serviceProvider)` | 从 catalog 装配单根树(DI 解析节点) | +| | `static IReadOnlyList AssembleForest(IReadOnlyList> entries)` | 从 catalog 装配多根森林(`Activator.CreateInstance`) | +| | `static IReadOnlyList AssembleForest(IReadOnlyList> entries, IServiceProvider serviceProvider)` | 从 catalog 装配多根森林(DI 解析节点) | + +`CompositeTraversalOrder` 枚举值:`DepthFirstPreOrder`、`DepthFirstPostOrder`、`BreadthFirst`。 + +### 特性(Attribute) + +命名空间:`DesignPatterns.Structural` + +#### `CompositePartAttribute`(非泛型,netstandard2.0) + +```csharp +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +public sealed class CompositePartAttribute : Attribute +{ + public CompositePartAttribute(string key, Type @for); + + public string Key { get; } + public Type For { get; } + public string? ParentKey { get; set; } // null = 根候选 + public int Order { get; set; } // 同父兄弟间排序,升序 + public Type[]? AllowedChildTypes { get; set; } // 允许的子节点实现类型;null = 不限制 +} +``` + +#### `CompositePartAttribute`(泛型,C# 11+ / net8.0) + +```csharp +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +public sealed class CompositePartAttribute : Attribute +{ + public CompositePartAttribute(string key); + + public string Key { get; } + public string? ParentKey { get; set; } + public int Order { get; set; } + public Type[]? AllowedChildTypes { get; set; } +} +``` + +#### `CompositeSchemaAttribute`(契约级约束) + +```csharp +[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +public sealed class CompositeSchemaAttribute : Attribute +{ + public int MaxDepth { get; set; } // 最大树深度(root = 1);0 = 无限制;默认 0 + public int MaxNodes { get; set; } // 最大节点总数(所有根合计);0 = 无限制;默认 0 +} +``` + +标注于 composite 契约接口或基类,启用编译期树结构 schema 校验。未标注时行为不变。 + +### 生成器产出 + +源生成器 `CompositePartGenerator` 对每个 composite contract 生成以下类型(`IPaymentStrategy` → `PaymentStrategyCompositeKeys`;`IMenuNode` → `MenuNodeCompositeKeys`): + +| 生成类型 | 说明 | +|----------|------| +| `{Contract}CompositeKeys` | key 常量(`public const string`) | +| `{Contract}CompositeCatalog` | entry 列表 + `BuildRoot()` + `BuildForest()` 方法 | + +`{Contract}CompositeCatalog` 方法: + +| 方法 | 说明 | +|------|------| +| `BuildRoot()` | 从 catalog 装配单根树;要求 catalog 中**恰好一个** `ParentKey == null` 的根,否则运行时抛 `CompositeAssemblyException` | +| `BuildForest()` | 从 catalog 装配多根森林;一个或多个 `ParentKey == null`,按 `Order` 再 key 排序 | + +## 诊断 ID + +| ID | 级别 | 触发条件 | 消息格式 | +|----|------|----------|----------| +| DP010 | Error | 同一 contract 下 key 重复 | `Composite key '{0}' is already registered for contract '{1}'. Use a unique key or remove the duplicate [CompositePart] attribute.` | +| DP011 | Error | `ParentKey` 引用的 key 不存在 | `Composite parent key '{0}' was not found for contract '{1}'. Register the parent part first or correct the ParentKey value.` | +| DP012 | Error | parent 链形成环 | `Composite key '{0}' participates in a parent-key cycle for contract '{1}'. Remove or reassign ParentKey values to break the cycle.` | +| DP013 | Error | 标注类型未实现 composite contract | `Type '{0}' does not implement composite contract '{1}'. Implement the contract or fix the [CompositePart] contract argument.` | +| DP014 | Error | 缺少 public 无参构造 | `Type '{0}' must declare a public parameterless constructor for generated composite catalogs.` | +| DP015 | Error | 未实现 `ICompositeBuildable` | `Type '{0}' must implement ICompositeBuildable<{1}> to be used with generated composite catalogs.` | +| DP040 | Error | `BuildRoot(IServiceProvider)` 时节点类型未注册到 DI 容器 | `Composite node type '{0}' was not registered in the service collection. Call {1}.RegisterDi(services) before BuildRoot(serviceProvider), or register the type manually.` | +| DP041 | Info | Visitor 覆盖不全(保留 ID — C# 编译器通过接口实现 CS0535 自动强制覆盖,诊断不实际触发) | `Visitor '{0}' does not implement all Visit methods of '{1}'. The C# compiler enforces full coverage via interface implementation (CS0535); this diagnostic is reserved for future use.` | +| DP063 | Warning | `[CompositeSchema(MaxDepth)]` 约束被超过 | `Composite tree for contract '{0}' has depth {1}, exceeding the maximum depth of {2} declared by [CompositeSchema]. Consider flattening the tree structure or increasing MaxDepth.` | +| DP064 | Error | 子节点实现类型不在父节点 `AllowedChildTypes` 集合中 | `Composite part '{0}' (type '{1}') is not in the AllowedChildTypes of its parent '{2}' (type '{3}'). Add '{1}' to the parent's AllowedChildTypes or change the ParentKey to a compatible parent.` | +| DP065 | Warning | `[CompositeSchema(MaxNodes)]` 约束被超过 | `Composite contract '{0}' has {1} parts, exceeding the maximum of {2} declared by [CompositeSchema]. Consider splitting into multiple contracts or increasing MaxNodes.` | + +## 不变量 + +1. **单根约束(`BuildRoot`)**:`BuildRoot()` / `Assemble` 要求 catalog 中恰好一个 `ParentKey == null` 的根;零根或多根时运行时抛 `CompositeAssemblyException`。 +2. **`ICompositeBuildable` 要求**:catalog 装配的节点类型必须实现 `ICompositeBuildable`,`TContract` 为 composite 接口(如 `IMenuNode`)。 +3. **public 无参构造**:catalog 装配的节点类型必须声明 public 无参构造函数(与 Strategy/Handler 生成器一致),供 `Activator.CreateInstance` 实例化。 +4. **`SetChildren` 一次性**:`SetChildren` 在装配时调用一次;实现类应在装配后冻结 children,后续调用视为无效。 +5. **多根森林(`BuildForest`)**:`BuildForest()` / `AssembleForest` 允许一个或多个 `ParentKey == null` 的根,按 `Order` 升序再 key 排序返回。 +6. **并行遍历顺序非确定**:`TraverseParallel` / `TraverseParallelAsync` / `TraverseForestParallel` / `TraverseForestParallelAsync` 的访问顺序非确定;需要顺序时使用 `Traverse` / `TraverseAsync`。 + +## 兼容基线 + +- `netstandard2.0` + `net8.0`(两者均须可用并随包分发) +- 泛型 `CompositePartAttribute` 仅在 C# 11+ / net7.0+ 可用(`#if NET7_0_OR_GREATER`);netstandard2.0 使用非泛型 `CompositePartAttribute(string key, Type @for)` +- 并行遍历:net8.0 用 `Parallel.ForEachAsync`,netstandard2.0 用 `SemaphoreSlim` + `Task.WhenAll`(`#if` 分裂) + +## 不在范围内 + +- 运行时动态增删节点(树由编译期定义、运行时一次性装配) +- DI 自动注册 `BuildRoot()` / `BuildForest()` 结果(DI 扩展不自动注册装配产物) diff --git a/docs/spec/Decorator.md b/docs/spec/Decorator.md new file mode 100644 index 0000000..d242378 --- /dev/null +++ b/docs/spec/Decorator.md @@ -0,0 +1,109 @@ +# Spec: Decorator + +> **版本**:v0.2.2(与 NuGet 包版本对齐) +> **关联 Design Doc**:[docs/design/Decorator.md](../design/Decorator.md) +> **关联 ADR**:ADR-XXX(如有) + +## API 面 + +### 运行时接口 + +运行时类型位于 `DesignPatterns/Structural/`。 + +| 类型 | 说明 | +|------|------| +| `IDecorator` | `TService Decorate(TService inner)` | +| `IDecoratorOf` | 可选标记接口(生成器不强制) | +| `DecoratorStackBuilder` | `Add()` / `Add(instance)` / `Add(..., Func)` / `Build(core)` | + +`DecoratorStackBuilder` 方法签名: + +| 方法 | 说明 | +|------|------| +| `Add()` where `TDecorator : TService, IDecorator, new()` | 注册一个装饰器类型(无参构造) | +| `Add(TService decorator)` | 注册一个装饰器实例(须实现 `IDecorator`) | +| `Add(Func predicate)` | 注册一个带运行时谓词的装饰器类型;谓词在 `Build` 时求值,为 `false` 时跳过该装饰器 | +| `Add(TService decorator, Func predicate)` | 注册一个带运行时谓词的装饰器实例 | +| `Build(TService core)` | 按 `Add` 顺序(`Order` 越小越靠外)包装 `core`,返回最外层 `TService` | + +### 特性(Attribute) + +```csharp +// 泛型(推荐,C# 11+ / net8.0) +[Decorator(10)] +public sealed class LoggingPaymentDecorator : IPaymentService, IDecorator { ... } + +// 非泛型(netstandard2.0) +[Decorator(10, typeof(IPaymentService))] +public sealed class LoggingPaymentDecorator : IPaymentService, IDecorator { ... } +``` + +`[Decorator]` / `[Decorator]` 构造函数签名: + +| 特性 | 构造函数 | 说明 | +|------|----------|------| +| `DecoratorAttribute` | `(int order)` | 泛型形式,C# 11+ / net8.0 | +| `DecoratorAttribute` | `(int order, Type serviceType)` | 非泛型形式,netstandard2.0 | + +`Order` 数值越小越靠外(先接到调用,再委托向内)。 + +### 生成器产出 + +对 `IPaymentService` → `PaymentServiceDecoratorStack` 与 `PaymentServiceDecoratorOrder`: + +```csharp +public static partial class PaymentServiceDecoratorStack +{ + public static IPaymentService Build(IPaymentService core) { ... } +} + +public static partial class PaymentServiceDecoratorOrder +{ + public const int LoggingPaymentDecorator = 10; + public const int MetricsPaymentDecorator = 20; +} +``` + +- `{Contract}DecoratorStack.Build(core)`:按 `[Decorator]` 的 `Order` 升序包装 `core`,返回最外层 `TService`。注册全部 `[Decorator]` 类型,**不**应用运行时谓词。 +- `{Contract}DecoratorOrder`:为每个装饰器生成 `public const int`,常量名取装饰器**简单类型名**。同一 contract 下类型名须唯一(跨命名空间同名会导致重复常量编译错误)。 + +特性中可引用常量替代魔法数: + +```csharp +[Decorator(PaymentServiceDecoratorOrder.LoggingPaymentDecorator)] +public sealed class LoggingPaymentDecorator : IPaymentService, IDecorator { ... } +``` + +## 诊断 ID + +| ID | 级别 | 触发条件 | 消息格式 | +|----|------|----------|----------| +| DP016 | Error | 同一 contract 下重复 `Order` | `Decorator order '{0}' is already used for service contract '{1}'. Assign a unique order value or remove the duplicate [Decorator] attribute.` | +| DP017 | Error | 标注 `[Decorator]` 但未实现 service contract | `Type '{0}' does not implement service contract '{1}'. Implement the contract or fix the [Decorator] service argument.` | +| DP018 | Error | 标注 `[Decorator]` 但未实现 `IDecorator` | `Type '{0}' does not implement IDecorator<{1}>. Implement IDecorator<{1}> to participate in generated decorator stacks.` | +| DP019 | Error | 标注 `[Decorator]` 的装饰器缺少 public 无参构造 | `Type '{0}' must declare a public parameterless constructor for generated decorator stacks.` | +| DP042 | Error | async 装饰器 `DecorateAsync` 签名不匹配 | `Async decorator '{0}' must implement 'ValueTask<{1}> DecorateAsync({1} inner, CancellationToken cancellationToken = default)'. Fix the IAsyncDecorator<{1}> implementation.` | +| DP043 | Warning | 装饰器无法从 DI 容器解析(无 public 无参构造) | `Decorator '{0}' has no public parameterless constructor. Register it in the DI container via RegisterDi(), or add a parameterless constructor for Build() without IServiceProvider.` | + +CodeFix:DP017(加契约接口)、DP019(加无参构造)。DP018 需手写 `Decorate` 方法。 + +## 不变量 + +1. `Build(core)` 要求 `core` 非 `null`,否则抛 `ArgumentNullException`。 +2. `Order` 越小越靠外(先接到调用,再委托向内);先注册的装饰器包装在后注册的外层。 +3. 无装饰器时 `Build(core)` 返回同一 `core` 引用(不创建包装)。 +4. `DecoratorOrder` 常量名取装饰器**简单类型名**;同一 contract 下类型名须唯一。 +5. `Add(null)` / `Add(..., null)` 谓词抛 `ArgumentNullException`。 +6. 生成器产出的 `{Contract}DecoratorStack.Build(core)` 注册全部 `[Decorator]` 类型,条件开关(`Func`)仅适用于手动 `DecoratorStackBuilder` 组装。 + +## 兼容基线 + +- netstandard2.0 + net8.0(两者均须可用并随包分发) +- Roslyn 组件 4.8.0(`Microsoft.CodeAnalysis.CSharp` / Workspaces;Analyzers 3.3.4) + +## 不在范围内 + +- `DispatchProxy` / 透明代理 +- 装饰器 DAG 或按 key 选子集 +- 生成器为 `[Decorator]` 自动接线运行时谓词(条件装饰仅手动 builder) +- DI 自动注册 diff --git a/docs/spec/EventAggregator.md b/docs/spec/EventAggregator.md new file mode 100644 index 0000000..12d8c7e --- /dev/null +++ b/docs/spec/EventAggregator.md @@ -0,0 +1,134 @@ +# Spec: Event Aggregator + +> **版本**:v0.2.2 +> **关联 Design Doc**:[docs/design/EventAggregator.md](../design/EventAggregator.md) +> **关联 ADR**:ADR-XXX(如有) + +## API 面 + +### 运行时接口 + +| 类型 | 职责 | +|------|------| +| `IEventHandler` | 处理指定事件类型 | +| `IEventAggregator` | 订阅、取消订阅、发布 | +| `EventAggregator` | 默认实现 | + +#### 事件处理器 + +```csharp +public interface IEventHandler +{ + ValueTask HandleAsync(TEvent evt, CancellationToken cancellationToken = default); +} +``` + +任意类实现该接口即可作为处理器;**不要求**继承基类或注册特性。 + +#### 聚合器 + +```csharp +public interface IEventAggregator +{ + void Subscribe(IEventHandler handler); + void Unsubscribe(IEventHandler handler); + ValueTask PublishAsync(TEvent evt, CancellationToken cancellationToken = default); +} +``` + +事件类型 `TEvent` 可以是 `class`、`struct` 或 `record`;同一聚合器上可并存多种 `TEvent`。 + +#### 基本用法 + +```csharp +var aggregator = new EventAggregator(); + +aggregator.Subscribe(new EmailNotificationHandler()); +aggregator.Subscribe(new AuditLogHandler()); + +await aggregator.PublishAsync(new OrderPlacedEvent("ORD-001", 99.99m)); + +aggregator.Unsubscribe(auditHandler); +await aggregator.PublishAsync(new OrderPlacedEvent("ORD-002", 49.99m)); +``` + +### 特性(Attribute) + +| 特性 | 形态 | 适用 TFM | +|------|------|----------| +| `[RegisterEventHandler(typeof(FooEvent))]` | 非泛型 | 全部(netstandard2.0+) | +| `[RegisterEventHandler]` | 泛型 | `#if NET7_0_OR_GREATER`(C# 11+ generic attributes) | + +与 `[RegisterStrategy]` / `[RegisterFactory]` 的双形态模式一致。 + +### 生成器产出 + +`RegisterEventHandlerGenerator` 在编译期扫描 `[RegisterEventHandler]` 特性,按 **event type 分组** 生成 `{Event}EventHandlerRegistry` 静态类(`Event` 后缀自动剥离,如 `OrderPlacedEvent` → `OrderPlacedEventHandlerRegistry`): + +| 成员 | 路径 | 条件 | +|------|------|------| +| `SubscribeAll(IEventAggregator)` | 静态:`new Handler()` + `Subscribe` | handler 有公共无参构造 | +| `RegisterDi(IServiceCollection, ServiceLifetime)` | DI:注册 handler 实现到容器 | `DesignPatterns_EnableDiIntegration=true` | +| `SubscribeAll(IEventAggregator, IServiceProvider)` | DI:从容器解析 handler + `Subscribe` | `DesignPatterns_EnableDiIntegration=true` | + +**静态路径**仅包含有公共无参构造的 handler;**DI 路径**包含全部有效 handler。这与其他生成器的无参构造约束一致(DP007/DP009/DP014/DP019/DP022 同源)。 + +#### 静态路径(无 DI 依赖) + +```csharp +public record OrderPlacedEvent(string OrderId); + +[RegisterEventHandler] +public sealed class LogOrderHandler : IEventHandler +{ + public ValueTask HandleAsync(OrderPlacedEvent evt, CancellationToken ct = default) => default; +} + +// 启动时 +var aggregator = new EventAggregator(); +OrderPlacedEventHandlerRegistry.SubscribeAll(aggregator); +await aggregator.PublishAsync(new OrderPlacedEvent("ORD-001")); +``` + +#### DI 路径(两步) + +```csharp +// 1. 注册聚合器 + handler 实现 +services.AddEventAggregator(); +OrderPlacedEventHandlerRegistry.RegisterDi(services); + +// 2. 启动时从容器解析并订阅 +var provider = services.BuildServiceProvider(); +var aggregator = provider.GetRequiredService(); +OrderPlacedEventHandlerRegistry.SubscribeAll(aggregator, provider); +``` + +`RegisterDi` 默认 `implementationLifetime: ServiceLifetime.Transient`(每次解析新实例,因 handler 通常无状态)。 + +## 诊断 ID + +| ID | 级别 | 触发条件 | 消息格式 | +|----|------|----------|----------| +| DP044 | Info | 实现 `IEventHandler` 但未标注 `[RegisterEventHandler]` | 提示未注册的 EventHandler | +| DP045 | Error | 同一 handler 类对同一 event type 重复标注 `[RegisterEventHandler]` | 报告重复标注 | +| DP046 | Error | 标注 `[RegisterEventHandler]` 但类未实现 `IEventHandler` | 报告契约不匹配 | + +## 不变量 + +1. 同一事件类型的处理器按 **订阅顺序** 依次调用。 +2. `PublishAsync` 在发布前对处理器列表做 **快照**,随后在锁外调用,避免在 `HandleAsync` 执行期间持有锁。 +3. 同一 `handler` 实例可重复 `Subscribe`(会多次出现在列表中)。 +4. `Unsubscribe` 移除**第一次**匹配项。 +5. 无处理器时 `PublishAsync` 立即完成(空订阅)。 + +## 兼容基线 + +- netstandard2.0 / net8.0(运行时核心,两者均须可用并随包分发) +- DI 集成:独立包 `DesignPatterns.Extensions.DependencyInjection`,`services.AddEventAggregator()` 注册 `IEventAggregator` → `EventAggregator`(默认 Singleton) + +## 不在范围内 + +- 不做弱事件 / 委托语法糖包装 +- 不做按名称或 topic 字符串路由(仅按 CLR 类型) +- 不在 Core 中引用 DI 容器 +- 不用反射扫描程序集自动订阅(改由 `[RegisterEventHandler]` 源生成器在编译期收集 handler) diff --git a/docs/spec/FactoryRegistry.md b/docs/spec/FactoryRegistry.md new file mode 100644 index 0000000..1436ffe --- /dev/null +++ b/docs/spec/FactoryRegistry.md @@ -0,0 +1,232 @@ +# Spec: Factory Registry + +> **版本**:v0.2.2(与 NuGet 包版本对齐) +> **关联 Design Doc**:[docs/design/FactoryRegistry.md](../design/FactoryRegistry.md) +> **关联 ADR**:ADR-XXX(如有) + +## API 面 + +### 运行时接口 + +命名空间:`DesignPatterns.Creational` + +#### 注册表接口 + +```csharp +namespace DesignPatterns.Creational; + +/// +/// 按 key 解析工厂实现的只读注册表。每次 Create 调用对应 factory 委托得到新产品实例。 +/// +public interface IFactoryRegistry : IReadOnlyRegistry + where TKey : notnull +{ + bool TryCreate(TKey key, [MaybeNullWhen(false)] out TProduct product); + TProduct Create(TKey key); // 找不到抛 FactoryNotFoundException + IReadOnlyCollection Keys { get; } +} +``` + +> **注意**:`IFactoryRegistry` 继承 `IReadOnlyRegistry`,与 `IStrategyRegistry` 共享该抽象。 + +#### 不可变实现 + +```csharp +public sealed class FactoryRegistry : IFactoryRegistry + where TKey : notnull +``` + +net8.0+ 上内部使用 `FrozenDictionary` 优化查找。 + +#### Builder(手动注册,无生成器时) + +```csharp +public sealed class FactoryRegistryBuilder + where TKey : notnull +{ + // 每次 Create(key) 调用该 factory + public FactoryRegistryBuilder Register(TKey key, Func factory); + + // factory 接收 key + public FactoryRegistryBuilder Register(TKey key, Func factory); + + public IFactoryRegistry Build(); +} +``` + +- 重复 key 在 `Register` 时抛 `ArgumentException`(**运行时**检测)。 + +#### 异常 + +```csharp +public sealed class FactoryNotFoundException : Exception +{ + public FactoryNotFoundException(); + public FactoryNotFoundException(string message); + public FactoryNotFoundException(string message, Exception innerException); + public static FactoryNotFoundException ForKey(TKey key) where TKey : notnull; +} +``` + +`ForKey` 生成消息:`"No factory registered for key '{key}'."` + +#### 异步工厂(可选) + +```csharp +public interface IAsyncFactory +{ + ValueTask CreateAsync(CancellationToken cancellationToken = default); +} + +public interface IAsyncFactoryRegistry : IReadOnlyRegistry + where TKey : notnull +{ + ValueTask<(bool Success, TProduct? Product)> TryCreateAsync( + TKey key, CancellationToken cancellationToken = default); + ValueTask CreateAsync( + TKey key, CancellationToken cancellationToken = default); +} +``` + +`FactoryRegistryAsyncExtensions.AsAsync()` 可将同步 `IFactoryRegistry` 适配为 `IAsyncFactoryRegistry`。 + +### 特性(Attribute) + +命名空间:`DesignPatterns.Creational` + +#### 泛型版本(C# 11 / .NET 7+) + +```csharp +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] +public sealed class RegisterFactoryAttribute : Attribute +{ + public string Key { get; } + public bool IsAsync { get; set; } + public int PoolSize { get; set; } + + public RegisterFactoryAttribute(string key); +} +``` + +#### 非泛型版本(netstandard2.0 / C# 7.3) + +```csharp +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] +public sealed class RegisterFactoryAttribute : Attribute +{ + public string Key { get; } + public Type Contract { get; } + public bool IsAsync { get; set; } + public int PoolSize { get; set; } + + public RegisterFactoryAttribute(string key, Type contract); +} +``` + +#### 属性说明 + +| 属性 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `Key` | `string` | (构造函数必填) | 用于解析此工厂的 key | +| `Contract` | `Type` | (非泛型构造函数必填) | 工厂契约类型(接口或基类) | +| `IsAsync` | `bool` | `false` | 标记为异步工厂;`true` 时须实现 `IAsyncFactory`,生成器额外输出 `IAsyncFactoryRegistry`。`false` 时异步检测基于 `IAsyncFactory` 实现自动判断 | +| `PoolSize` | `int` | `0` | 启用对象池的最大池大小(per key)。`0` 禁用池化;正值使生成器输出 `IPooledFactoryRegistry`。仅异步工厂有效 | + +#### 用法示例 + +```csharp +// 泛型 Attribute(C# 11+) +[RegisterFactory("standard")] +public class StandardFactory : IProductFactory { ... } + +// 非泛型(netstandard2.0) +[RegisterFactory("standard", typeof(IProductFactory))] +public class StandardFactory : IProductFactory { ... } +``` + +工厂契约一般为**接口**(或基类);实现类需 **public 无参构造**,以便生成器注册 `() => new Implementation()`。 + +### 生成器产出 + +对每个契约 `TContract`(命名规则与 Strategy 相同:去掉前缀 `I` 等),生成器输出: + +#### 1. 强类型 Key 常量 + +```csharp +// ProductFactoryKeys.g.cs +public static partial class ProductFactoryKeys +{ + public const string Standard = "standard"; + public const string Premium = "premium"; +} +``` + +#### 2. 静态注册表(无 DI 场景) + +```csharp +// ProductFactoryRegistry.g.cs +public static partial class ProductFactoryRegistry +{ + public static IFactoryRegistry Create() { ... } +} +``` + +`Create()` 返回的注册表在每次 `Create(key)` 时**新建**产品实例(与 Strategy 注册表返回单例实例不同)。 + +#### 3. DI 集成(引用 `DesignPatterns.Extensions.DependencyInjection` 时) + +引用扩展包会自动 Import `build/DesignPatterns.Extensions.DependencyInjection.targets`,设置 `DesignPatterns_EnableDiIntegration=true`,生成器额外输出: + +```csharp +public static partial class ProductFactoryRegistry +{ + // Create() 仍保留(new() 静态实例,无 DI 生命周期) + + public static IFactoryRegistry Create(IServiceProvider serviceProvider) { ... } + + public static IServiceCollection RegisterDi( + IServiceCollection services, + ServiceLifetime implementationLifetime = ServiceLifetime.Transient, + ServiceLifetime registryLifetime = ServiceLifetime.Singleton); +} +``` + +> **注意**:`implementationLifetime` 默认值为 `Transient`(与 Strategy/Chain 等模式默认 `Singleton` 不同),因为工厂语义是每次 `Create` 返回新实例。 + +手动 Builder 仍可用:`services.AddFactoryRegistry(builder => { ... })`(扩展包),与生成器互不冲突。 + +## 诊断 ID + +| ID | 级别 | 触发条件 | 消息格式 | +|----|------|----------|----------| +| DP020 | Error | 同一 `TContract` 下 key 重复 | `Factory key '{0}' is already registered for contract '{1}'. Use a unique key or remove the duplicate [RegisterFactory] attribute.` | +| DP021 | Error | 标记的类未实现指定的 `TContract` | `Type '{0}' does not implement factory contract '{1}'. Implement the contract or fix the [RegisterFactory] contract argument.` | +| DP022 | Error | 标记的类缺少 public 无参构造 | `Type '{0}' must declare a public parameterless constructor for [RegisterFactory] static registration, or enable DI integration.` | +| DP023 | Info | 实现了某工厂契约但未加 `[RegisterFactory]` | `Type '{0}' implements factory contract '{1}' but has no [RegisterFactory] attribute. Add [RegisterFactory("key", typeof(...))] with a unique key and the contract type to register it.` | +| DP053 | Error | `IsAsync=true` 但未实现 `IAsyncFactory` | `Factory '{0}' is marked with IsAsync=true but does not implement IAsyncFactory<{1}>. Implement IAsyncFactory<{1}> or set IsAsync=false.` | +| DP054 | Error | `PoolSize` 为负数 | `Factory '{0}' has PoolSize={1}, which is negative. PoolSize must be >= 0 (0 disables pooling).` | +| DP055 | Warning | `PoolSize` 过大(可能导致内存过高) | `Factory '{0}' has PoolSize={1}, which may cause excessive memory usage. Consider a smaller value (recommended: 1-100).` | + +> **归属**:DP023 属 **Analyzer**(含 CodeFix,可一键添加 `[RegisterFactory("suggested-key", typeof(TContract))]`,与 DP006 对称);DP020 / DP021 / DP022 / DP053 / DP054 / DP055 属**生成器**。 + +## 不变量 + +1. **每次 Create 返回新实例**:`Create(key)` / `TryCreate(key, out _)` 每次调用均执行 factory 委托,得到新产品实例(与 Strategy 注册表返回同一实例不同)。 +2. **重复 key 抛 ArgumentException**:`FactoryRegistryBuilder.Register` 在运行时检测重复 key,抛 `ArgumentException`(`"A factory is already registered for key '{key}'."`)。编译期由 DP020 检测。 +3. **`IFactoryRegistry` 继承 `IReadOnlyRegistry`**:与 `IStrategyRegistry` 共享 `IReadOnlyRegistry` 抽象(提供 `Keys` 和 `TryGet`)。 +4. **标记的类须实现指定的 `TContract`**(DP021)。 +5. **标记的类须有 public 无参构造**:生成器使用 `new()` 实例化(DP022),或启用 DI 集成由容器解析。 +6. **key 唯一性**:同一 `TContract` 下 key 不可重复(DP020 在编译期强制)。 + +## 兼容基线 + +- 运行时 TFM:`netstandard2.0` + `net8.0`(两者均须可用并随包分发)。 +- 泛型 Attribute(`RegisterFactoryAttribute`)需要 C# 11+ / `net7.0+`;`netstandard2.0` 目标下用 `#if NET7_0_OR_GREATER` 条件编译。 +- 非泛型 `RegisterFactoryAttribute` 始终可用,功能等价。 +- net8.0 上 `FactoryRegistry` 内部使用 `FrozenDictionary` 优化查找。 + +## 不在范围内 + +- 不做抽象工厂族(多个产品类型族)的完整框架 +- 不在 Core 中引用 DI 容器 +- 不用反射扫描程序集注册 factory diff --git a/docs/spec/README.md b/docs/spec/README.md index 14f591d..2fc1fb2 100644 --- a/docs/spec/README.md +++ b/docs/spec/README.md @@ -8,18 +8,16 @@ ## 已有 Spec -(Spec 文档将随模式文档迁移逐步创建。现有模式文档位于 `docs/.md`,将拆分迁移至 `docs/spec/` + `docs/design/`。) +| 模式 | Spec | Design Doc | 关联 ADR | +|------|------|------------|----------| +| Strategy | [Strategy.md](Strategy.md) | [Strategy.md](../design/Strategy.md) | — | +| Chain of Responsibility | [ChainOfResponsibility.md](ChainOfResponsibility.md) | [ChainOfResponsibility.md](../design/ChainOfResponsibility.md) | — | +| Composite | [Composite.md](Composite.md) | [Composite.md](../design/Composite.md) | [ADR-006](../adr/ADR-006-composite-parallel-traversal.md)、[ADR-007](../adr/ADR-007-composite-tree-schema-validation.md) | +| Factory Registry | [FactoryRegistry.md](FactoryRegistry.md) | [FactoryRegistry.md](../design/FactoryRegistry.md) | — | +| Decorator | [Decorator.md](Decorator.md) | [Decorator.md](../design/Decorator.md) | — | +| Event Aggregator | [EventAggregator.md](EventAggregator.md) | [EventAggregator.md](../design/EventAggregator.md) | — | +| State Transition Table | [StateTransitionTable.md](StateTransitionTable.md) | [StateTransitionTable.md](../design/StateTransitionTable.md) | [ADR-005](../adr/ADR-005-state-transition-table.md) | -## 迁移计划 +## 迁移状态 -| 模式 | 现有文档 | Spec 目标 | Design Doc 目标 | -|------|----------|-----------|-----------------| -| Strategy | [docs/Strategy.md](../Strategy.md) | `docs/spec/Strategy.md` | `docs/design/Strategy.md` | -| Chain of Responsibility | [docs/ChainOfResponsibility.md](../ChainOfResponsibility.md) | `docs/spec/ChainOfResponsibility.md` | `docs/design/ChainOfResponsibility.md` | -| Composite | [docs/Composite.md](../Composite.md) | `docs/spec/Composite.md` | `docs/design/Composite.md` | -| Factory Registry | [docs/FactoryRegistry.md](../FactoryRegistry.md) | `docs/spec/FactoryRegistry.md` | `docs/design/FactoryRegistry.md` | -| Decorator | [docs/Decorator.md](../Decorator.md) | `docs/spec/Decorator.md` | `docs/design/Decorator.md` | -| Event Aggregator | [docs/EventAggregator.md](../EventAggregator.md) | `docs/spec/EventAggregator.md` | `docs/design/EventAggregator.md` | -| State Transition Table | [docs/StateTransitionTable.md](../StateTransitionTable.md) | `docs/spec/StateTransitionTable.md` | `docs/design/StateTransitionTable.md` | - -迁移可逐模式进行,不要求一次性完成。每个模式的迁移创建一个独立 PR。 +所有 7 个模式文档已从 `docs/.md` 拆分迁移至 `docs/spec/` + `docs/design/`。旧文件已删除。 diff --git a/docs/spec/StateTransitionTable.md b/docs/spec/StateTransitionTable.md new file mode 100644 index 0000000..aff79aa --- /dev/null +++ b/docs/spec/StateTransitionTable.md @@ -0,0 +1,393 @@ +# Spec: State 转换表 + +> **版本**:v0.2.2(与 NuGet 包版本对齐) +> **关联 Design Doc**:[docs/design/StateTransitionTable.md](../design/StateTransitionTable.md) +> **关联 ADR**:[ADR-005](../adr/ADR-005-state-transition-table.md) + +## API 面 + +### 运行时接口 + +命名空间:`DesignPatterns.Behavioral` + +#### 转换表 + +```csharp +namespace DesignPatterns.Behavioral; + +/// +/// 只读转换表:(当前状态, 触发器) → 下一状态。 +/// +public interface ITransitionTable + where TState : struct, Enum + where TTrigger : struct, Enum +{ + bool TryTransition(TState from, TTrigger trigger, out TState to); + bool CanTransitionFrom(TState from, TTrigger trigger); + IReadOnlyCollection GetAllowedTriggers(TState from); + TState InitialState { get; } +} +``` + +- `TryTransition`:命中边且 guard 通过时返回 `true` 并设置 `to`;否则返回 `false`。 +- `GetAllowedTriggers`:返回从 `from` 出发的所有合法触发器。 +- `CanTransitionFrom`:仅接收 state(无 trigger),判断是否存在以 `from` 为起点的边。 + +`TransitionTable` 为不可变实现;net8.0 上内部使用 `FrozenDictionary` 优化查找。 + +#### Builder(手动注册,无生成器时) + +```csharp +public sealed class TransitionTableBuilder + where TState : struct, Enum + where TTrigger : struct, Enum +{ + public TransitionTableBuilder WithInitial(TState initial); + public TransitionTableBuilder Add( + TState from, TTrigger trigger, TState to, + Func? guard = null, + Action? onEnterSync = null, + Action? onExitSync = null, + Func? onEnterAsync = null, + Func? onExitAsync = null); + public ITransitionTable Build(); +} +``` + +> **v3.4 重载简化**:`Add` 的多个重载合并为单一签名,所有可选参数(`guard`、`onEnterSync`、`onExitSync`、`onEnterAsync`、`onExitAsync`)均有默认值 `null`,调用方可按命名参数传任意子集,无需为占位提供 `null`。 + +手动 Builder 用法: + +```csharp +using DesignPatterns.Behavioral; + +public enum OrderStatus { Draft, Submitted, Paid } +public enum OrderTrigger { Submit, Pay } + +var table = new TransitionTableBuilder() + .WithInitial(OrderStatus.Draft) + .Add(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted) + .Add(OrderStatus.Submitted, OrderTrigger.Pay, OrderStatus.Paid) + .Build(); + +if (table.TryTransition(OrderStatus.Draft, OrderTrigger.Submit, out var next)) +{ + // next == Submitted +} + +// 非法边返回 false;Transition() 扩展抛 InvalidTransitionException +``` + +#### Transition 扩展 + +```csharp +public static class TransitionTableExtensions +{ + // 非法边时抛 InvalidTransitionException(含当前态与触发器信息) + public static TState Transition( + this ITransitionTable table, TState from, TTrigger trigger) + where TState : struct, Enum + where TTrigger : struct, Enum; +} +``` + +#### Guard 委托 + +`TransitionTableBuilder.Add` 提供 `guard` 参数;`TransitionEdge.Guard` 存储委托。`TransitionTable.TryTransition` 在命中边后调用 guard;guard 返回 `false` 时该次转换视为不存在。 + +```csharp +var table = new TransitionTableBuilder() + .WithInitial(OrderStatus.Draft) + .Add(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted, + guard: (state, trigger) => !string.IsNullOrEmpty(orderId)) + .Build(); +``` + +#### Entry / Exit Actions + +`TransitionTableBuilder.Add` 提供 `onEnterSync` / `onExitSync` / `onEnterAsync` / `onExitAsync` 参数。Action 仅通过 async 路径(`TryTransitionAsync`)触发;同步 `TryTransition` 不调用 action。 + +```csharp +var table = new TransitionTableBuilder() + .WithInitial(OrderStatus.Draft) + .Add( + OrderStatus.Draft, + OrderTrigger.Submit, + OrderStatus.Submitted, + guard: null, + onEnterSync: (from, to, trigger) => Console.WriteLine($"Entering {to}"), + onExitSync: (from, to, trigger) => Console.WriteLine($"Exiting {from}")) + .Build(); +``` + +执行顺序(`TryTransitionAsync`):guard → OnExit(sync → async)→ OnEnter(sync → async)→ 返回结果。 + +> **部分执行**:若 OnExit 成功后 OnEnter 抛异常,异常传播给调用方,OnExit 副作用已发生。调用方需自行处理补偿。`TransitionResult` 不暴露执行进度。 + +#### IStateMachine 实例包装器 + +```csharp +public interface IStateMachine + where TState : struct, Enum + where TTrigger : struct, Enum +{ + TState CurrentState { get; } + bool TryTransition(TTrigger trigger, out TState to); + ValueTask> TryTransitionAsync(TTrigger trigger, CancellationToken ct = default); +} + +public sealed class StateMachine : IStateMachine + where TState : struct, Enum + where TTrigger : struct, Enum +{ + public StateMachine(ITransitionTable table); + public TState CurrentState { get; } // 初始 == table.InitialState + public bool TryTransition(TTrigger trigger, out TState to); + public ValueTask> TryTransitionAsync(TTrigger trigger, CancellationToken ct = default); +} +``` + +`TryTransition` 委托给底层 table;成功时更新 `CurrentState`。`TryTransitionAsync` 同理,并触发 entry/exit action。 + +```csharp +var machine = new StateMachine(table); +// machine.CurrentState == table.InitialState + +if (machine.TryTransition(OrderTrigger.Submit, out var next)) +{ + // machine.CurrentState == OrderStatus.Submitted +} +``` + +> **线程安全**:`StateMachine` 非线程安全,设计为单线程使用。多线程场景应在调用方同步,或每线程使用独立实例。 + +#### IStateHierarchy(v3 层次状态) + +```csharp +public interface IStateHierarchy + where TState : struct, Enum +{ + TState? GetParent(TState state); + bool IsInState(TState state, TState ancestor); + IReadOnlyList GetAncestors(TState state); +} +``` + +- `GetParent`:返回直接父状态,无父时返回 `null`。 +- `IsInState`:判断 `state` 是否为 `ancestor` 的后代(含自身)。 +- `GetAncestors`:从 `state` 向上到根的祖先链(不含 `state` 自身)。 + +### 特性(Attribute) + +命名空间:`DesignPatterns.Behavioral` + +#### [StateMachine] + +```csharp +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +public sealed class StateMachineAttribute : Attribute +{ + public Type State { get; } // state enum 类型 + public Type Trigger { get; } // trigger enum 类型 + public object? Initial { get; set; } // 初始状态(state enum 成员) + public bool Hierarchical { get; set; } // v3:启用层次状态模式 + public StateMachineAttribute(Type state, Type trigger); +} +``` + +#### [Transition] + +```csharp +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] +public sealed class TransitionAttribute : Attribute +{ + public object From { get; } // state enum 成员 + public object Trigger { get; } // trigger enum 成员 + public object To { get; } // state enum 成员 + public string? Guard { get; set; } // holder 类上的 static guard 方法名 + public string? OnEnter { get; set; } // holder 类上的 static entry action 方法名 + public string? OnExit { get; set; } // holder 类上的 static exit action 方法名 + public TransitionAttribute(object from, object trigger, object to); +} +``` + +#### [StateParent](v3 层次状态) + +```csharp +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] +public sealed class StateParentAttribute : Attribute +{ + public object Child { get; } // state enum 成员 + public object Parent { get; } // state enum 成员 + public StateParentAttribute(object child, object parent); +} +``` + +#### 用法示例 + +```csharp +[StateMachine(typeof(OrderStatus), typeof(OrderTrigger), Initial = OrderStatus.Draft)] +[Transition(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted)] +[Transition(OrderStatus.Submitted, OrderTrigger.Pay, OrderStatus.Paid)] +public static partial class OrderMachine; + +// 生成:OrderStatusTransitionTable.Instance +// holder:OrderMachine.TryTransition(...)、OrderMachine.InitialState +``` + +Guard + Entry/Exit 示例: + +```csharp +[StateMachine(typeof(OrderStatus), typeof(OrderTrigger), Initial = OrderStatus.Draft)] +[Transition(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted, Guard = nameof(CanSubmit))] +[Transition(OrderStatus.Submitted, OrderTrigger.Pay, OrderStatus.Paid, + OnEnter = nameof(OnSubmitted), OnExit = nameof(OnLeaveDraft))] +public static partial class OrderMachine +{ + public static bool CanSubmit(OrderStatus state, OrderTrigger trigger) => true; + public static void OnSubmitted(OrderStatus from, OrderStatus to, OrderTrigger trigger) { } + public static void OnLeaveDraft(OrderStatus from, OrderStatus to, OrderTrigger trigger) { } +} +``` + +层次状态示例(v3): + +```csharp +[StateMachine(typeof(OrderStatus), typeof(OrderTrigger), Initial = OrderStatus.Draft, Hierarchical = true)] +[StateParent(OrderStatus.Submitted, OrderStatus.Active)] +[StateParent(OrderStatus.Paid, OrderStatus.Active)] +[Transition(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted)] +[Transition(OrderStatus.Submitted, OrderTrigger.Pay, OrderStatus.Paid)] +[Transition(OrderStatus.Active, OrderTrigger.Cancel, OrderStatus.Cancelled, OnExit = nameof(OnExitActive))] +public static partial class OrderMachine +{ + public static void OnExitActive(OrderStatus from, OrderStatus to, OrderTrigger trigger) { } +} +``` + +### 生成器产出 + +对 state enum `OrderStatus`: + +| 生成物 | 名称 | +|--------|------| +| 转换表类型 | `OrderStatusTransitionTable` | +| 单例 | `OrderStatusTransitionTable.Instance` | +| Holder 便捷 API | `OrderMachine.TryTransition`、`InitialState` | + +#### {StateEnum}TransitionTable + +```csharp +public sealed partial class OrderStatusTransitionTable + : ITransitionTable + , IStateHierarchy // v3:层次模式时实现 +{ + public static OrderStatusTransitionTable Instance { get; } + public OrderStatus InitialState { get; } + public bool TryTransition(OrderStatus from, OrderTrigger trigger, out OrderStatus to); + public bool CanTransitionFrom(OrderStatus from, OrderTrigger trigger); + public IReadOnlyCollection GetAllowedTriggers(OrderStatus from); + // v3 层次模式时额外实现 IStateHierarchy + public OrderStatus? GetParent(OrderStatus state); + public bool IsInState(OrderStatus state, OrderStatus ancestor); + public IReadOnlyList GetAncestors(OrderStatus state); +} +``` + +#### Holder 便捷方法 + +生成器在 holder partial 类上输出: + +- `TryTransition(...)` — 委托到 `Instance` +- `InitialState` — 委托到 `Instance.InitialState` + +#### RegisterDi(引用 `DesignPatterns.Extensions.DependencyInjection` 时) + +当消费项目引用 `DesignPatterns.Extensions.DependencyInjection`(自动 Import `build/DesignPatterns.Extensions.DependencyInjection.targets`)时,生成器在转换表类上额外输出: + +```csharp +public static IServiceCollection RegisterDi( + IServiceCollection services, + ServiceLifetime lifetime = ServiceLifetime.Singleton) +{ + services.TryAdd(new ServiceDescriptor( + typeof(ITransitionTable), + _ => Instance, + lifetime)); + // v3 层次模式时额外注册 IStateHierarchy + return services; +} +``` + +调用方式: + +```csharp +OrderStatusTransitionTable.RegisterDi(services); +// 或链式 +services.AddOtherStuff().RegisterDi(); +``` + +启用开关:MSBuild 属性 `DesignPatterns_EnableDiIntegration`(引用 DI 扩展包时自动为 `true`)。 + +#### 手动注册扩展 + +`DesignPatterns.Extensions.DependencyInjection.DesignPatternsServiceCollectionExtensions` 提供 `AddTransitionTable` 扩展方法,用于注册预构建的表实例: + +```csharp +services.AddTransitionTable(manualTable); +services.AddTransitionTable(OrderStatusTransitionTable.Instance, ServiceLifetime.Singleton); +``` + +使用 `TryAdd` 语义,重复注册不会覆盖。 + +v3 层次模式下使用 `AddStateHierarchy` 扩展方法(从容器解析 table 并转型)和 `AddStateMachine` 扩展方法。 + +## 诊断 ID + +| ID | 级别 | 触发条件 | 消息格式 | +|----|------|----------|----------| +| **DP026** | Error | 重复边 `(from, trigger)` | 重复的转换边 | +| **DP027** | Error | `[Transition]` 的 state 非 enum 成员 | state 非 enum 成员 | +| **DP028** | Error | trigger 非 enum 成员 | trigger 非 enum 成员 | +| **DP029** | Error | `Initial` 非 state enum 成员 | Initial 非 state enum 成员 | +| **DP030** | Error | holder 非 static partial class | holder 须为 static partial class | +| **DP031** | Info | state 从未作为 `from` 出现(终态提示) | state 从未作为 from 出现 | +| **DP032** | Error | guard 方法在 holder 类上未找到 | guard 方法未找到 | +| **DP034** | Error | guard 方法非 static | guard 方法须为 static | +| **DP035** | Error | guard 方法签名错误(须 `bool Method(TState, TTrigger)`) | guard 方法签名错误 | +| **DP036** | Info | `TryTransition` 字面量 (state, trigger) 对未声明(与 DP025 对称) | 字面量边未声明 | +| **DP037** | Error | entry/exit action 方法在 holder 类上未找到 | action 方法未找到 | +| **DP038** | Error | entry/exit action 方法非 static(防御性;CS0708 先于生成器拒绝) | action 方法须为 static | +| **DP039** | Error | entry/exit action 方法签名错误(须 `void Method(TState, TState, TTrigger)` 或 `ValueTask Method(TState, TState, TTrigger, CancellationToken)`) | action 方法签名错误 | +| **DP056** | Error | `[StateParent]` 父链存在循环 | 父链循环 | +| **DP057** | Error | `[StateParent]` 的 child/parent 非 state enum 成员 | 非法 enum 成员 | +| **DP058** | Error | `[StateParent]` 自引用(child == parent) | 自引用父 | +| **DP059** | Error | `[StateParent]` 的 parent 从未作为任何边的 state 出现(孤立父声明) | 孤立父声明 | + +常量与文案:[`DesignPatterns.Diagnostics/DiagnosticIds.cs`](../../DesignPatterns.Diagnostics/DiagnosticIds.cs)、[`DesignPatternsDiagnosticDescriptors.cs`](../../DesignPatterns.Diagnostics/DesignPatternsDiagnosticDescriptors.cs)。 + +用户向说明:[DesignPatterns.Docs — diagnostics](https://skymly.github.io/DesignPatterns.Docs/diagnostics#state-transition-table-dp026-dp031)。 + +## 不变量 + +1. `TState` 和 `TTrigger` 均须为 **enum**(`where TState : struct, Enum`)。 +2. Guard 方法签名须为 `bool Method(TState, TTrigger)`;须 **static**;须在 holder 类上声明。 +3. Entry/Exit action 方法签名须为 `void Method(TState from, TState to, TTrigger trigger)`(sync)或 `ValueTask Method(TState from, TState to, TTrigger trigger, CancellationToken)`(async);须 **static**;须在 holder 类上声明。 +4. `StateMachine` **非线程安全**,设计为单线程使用。多线程场景应在调用方同步,或每线程使用独立实例。 +5. 执行顺序(`TryTransitionAsync`):guard → OnExit(sync → async)→ OnEnter(sync → async)→ 返回结果。 +6. Entry/Exit action 仅通过 async 路径(`TryTransitionAsync`)触发;同步 `TryTransition` 不调用 action。 +7. 部分执行语义:若 OnExit 成功后 OnEnter 抛异常,异常传播给调用方,OnExit 副作用已发生;`TransitionResult` 不暴露执行进度。 +8. `TransitionTable` 不可变;`Build()` 后不可修改。 +9. DI 注册使用 `TryAdd` 语义,重复注册不会覆盖。 + +## 兼容基线 + +- netstandard2.0 + net8.0(两者均须可用并随包分发) +- Roslyn 组件 4.8.0(`Microsoft.CodeAnalysis.CSharp` / Workspaces;Analyzers 3.3.4) +- 生成器实现:`IIncrementalGenerator` + `ForAttributeWithMetadataName` + +## 不在范围内 + +- 并发状态(concurrent states)/ 历史状态(history states) +- `string` / `int` 状态键(仅支持 enum) +- Guard 的编译期表达式求值(v2 仅运行时委托 + 方法引用校验) diff --git a/docs/Strategy.md b/docs/spec/Strategy.md similarity index 63% rename from docs/Strategy.md rename to docs/spec/Strategy.md index 82a99e8..3b26e57 100644 --- a/docs/Strategy.md +++ b/docs/spec/Strategy.md @@ -1,25 +1,15 @@ -# Strategy 模式 — 设计与实现文档 +# Spec: Strategy -## 概述 +> **版本**:v0.2.2(与 NuGet 包版本对齐) +> **关联 Design Doc**:[docs/design/Strategy.md](../design/Strategy.md) -Strategy 模式允许在运行时按条件选择算法/行为实现,避免大量 `if/switch` 分支。本库的定位是: +## API 面 -- 提供 **注册表 + 编译期生成** 减少手写胶水 -- **不替代** DI 容器的生命周期管理 -- **不做** 策略选择路由引擎(那是业务逻辑) +### 运行时接口 -## 设计目标 +命名空间:`DesignPatterns.Behavioral` -1. 用户只需在实现类上打一个特性,即可获得:强类型 Key 常量 + 注册表 + 可选 DI 扩展 -2. 编译期检查 key 重复、接口不匹配 -3. 不侵入实现类的继承链 -4. 同时支持同步与异步策略 - ---- - -## 运行时 API(`DesignPatterns/Behavioral/`) - -### 策略接口(可选) +#### 策略接口(可选标记接口) ```csharp namespace DesignPatterns.Behavioral; @@ -45,7 +35,7 @@ public interface IAsyncStrategy > > `IStrategy<,>` / `IAsyncStrategy<,>` 为**可选标记接口**,便于表达同步/异步算法形状;注册表与生成器不依赖它们。用法见 `tests/DesignPatterns.Tests/Behavioral/StrategyMarkerInterfaceTests.cs`。 -### 注册表 +#### 注册表 ```csharp namespace DesignPatterns.Behavioral; @@ -67,7 +57,7 @@ public interface IStrategyRegistry 提供不可变实现 `StrategyRegistry`,net8.0 上内部使用 `FrozenDictionary` 优化查找。 -### Builder(手动注册,无生成器时) +#### Builder(手动注册,无生成器时) ```csharp public sealed class StrategyRegistryBuilder where TKey : notnull @@ -78,7 +68,7 @@ public sealed class StrategyRegistryBuilder where TKey : notnul } ``` -### 异步策略解析 +#### 异步策略解析 `IAsyncStrategy` 与 `[RegisterStrategy]` 使用**同一套** Keys / Registry / `RegisterDi` 路径;契约可以是继承 `IAsyncStrategy<,>` 的专用接口,无需额外 attribute 或并行注册表类型。 @@ -100,7 +90,7 @@ var length = await registry.Get(TextProcessorKeys.Length).ExecuteAsync("hello"); DI 场景:`{Contract}Registry.RegisterDi(services)` 后从容器解析 `IStrategyRegistry`,再 `await registry.ExecuteAsync<...>(...)` 或 `Get(key).ExecuteAsync(...)`。 -### Guard 谓词 +#### Guard 谓词 `TryGetWithGuard` 在解析策略时额外评估注册的 guard 谓词。guard 返回 `false` 时该策略视为未注册(返回 `false`)。 @@ -117,17 +107,13 @@ if (registry.TryGetWithGuard("alipay", out var strategy)) { ... } 源生成器支持:`[RegisterStrategy("key", Guard = nameof(CanEnable))]`,生成器校验 guard 方法签名(DP047-DP049)。 ---- +### 特性(Attribute) -## 编译期:`[RegisterStrategy]` 源生成器 +命名空间:`DesignPatterns.Behavioral` -### 特性设计 - -优先使用 **泛型 Attribute**(C# 11 / .NET 7+),同时为低版本提供非泛型重载: +#### 泛型版本(C# 11 / .NET 7+) ```csharp -namespace DesignPatterns.Behavioral; - /// /// 标记一个类为某策略接口的实现,并注册到编译期生成的策略注册表中。 /// @@ -151,7 +137,11 @@ public sealed class RegisterStrategyAttribute : Attribute Key = key; } } +``` +#### 非泛型版本(netstandard2.0 / C# 7.3) + +```csharp /// /// 非泛型版本,用于不支持泛型 Attribute 的目标框架。 /// @@ -178,7 +168,7 @@ public sealed class RegisterStrategyAttribute : Attribute } ``` -### 用法示例 +#### 用法示例 ```csharp // 泛型 Attribute(推荐,C# 11+) @@ -193,7 +183,7 @@ public class WechatPayment : IPaymentStrategy { ... } public class AlipayPayment : IPaymentStrategy { ... } ``` -### 生成器输出 +### 生成器产出 对于每个 `TContract`,生成器输出: @@ -264,103 +254,41 @@ var registry = services.BuildServiceProvider() 手动 Builder 仍可用:`services.AddStrategyRegistry(...)`(见扩展包),与生成器互不冲突。 ---- +## 诊断 ID -## 诊断规则 - -| ID | 严重性 | 来源 | 触发条件 | 说明 | -|----|--------|------|----------|------| -| DP003 | Error | 源生成器 | 同一 `TContract` 下 key 重复 | 编译期检测冲突 | -| DP004 | Error | 源生成器 | 标记的类未实现指定的 `TContract` | 接口不匹配 | -| DP006 | Info | Analyzer | 实现了某策略契约但未加 `[RegisterStrategy]` | 建议添加特性 | -| DP007 | Error | 源生成器 | 标记的类缺少 public 无参构造 | 无法 `new()` 实例化 | -| DP047 | Error | 源生成器 | `Guard` 指定的方法在实现类上未找到 | 添加 static 方法或移除 Guard | -| DP048 | Error | 源生成器 | `Guard` 指定的方法非 static | 改为 static | -| DP049 | Error | 源生成器 | `Guard` 指定的方法签名错误(须 `static bool Method(TKey key)`) | 修正参数类型或返回类型 | +| ID | 级别 | 触发条件 | 消息格式 | +|----|------|----------|----------| +| DP003 | Error | 同一 `TContract` 下 key 重复 | 编译期检测冲突 | +| DP004 | Error | 标记的类未实现指定的 `TContract` | 接口不匹配 | +| DP006 | Info | 实现了某策略契约但未加 `[RegisterStrategy]` | 建议添加特性 | +| DP007 | Error | 标记的类缺少 public 无参构造 | 无法 `new()` 实例化 | +| DP047 | Error | `Guard` 指定的方法在实现类上未找到 | 添加 static 方法或移除 Guard | +| DP048 | Error | `Guard` 指定的方法非 static | 改为 static | +| DP049 | Error | `Guard` 指定的方法签名错误(须 `static bool Method(TKey key)`) | 修正参数类型或返回类型 | > **注意**:DP005 属于 Handler(`[HandlerOrder]` 重复 Order),不属于 Strategy。 ---- - -## 泛型 Attribute 兼容策略 - -由于库目标 `netstandard2.0`,需要条件处理: - -- **定义侧**:泛型 Attribute 需要 C# 11 + `net7.0+`。在 `netstandard2.0` 目标下用 `#if NET7_0_OR_GREATER` 条件编译。 -- **生成器侧**:`ForAttributeWithMetadataName` 同时注册两个元数据名(泛型版与非泛型版),合并处理。 -- **消费者侧**:使用 `net7.0+` 的项目自动获得泛型版;低版本使用非泛型版,体验稍弱但功能等价。 - -```csharp -// 条件编译示例 -#if NET7_0_OR_GREATER -[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] -public sealed class RegisterStrategyAttribute : Attribute { ... } -#endif - -// 非泛型版始终可用 -[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] -public sealed class RegisterStrategyAttribute : Attribute { ... } -``` - ---- - -## 与 DI 容器集成 - -**当前已实现**:`DesignPatterns.Extensions.DependencyInjection`(MSDI)+ 生成器 `RegisterDi` / `Create(IServiceProvider)`(Strategy / Factory / Handler)。 - -- 生命周期由 DI 控制;注册表通过 `ServiceProviderStrategyRegistry` 在 `TryGet` 时解析实现类型。 -- Core 不引用 DI;启用 DI 生成路径需引用扩展包(targets 打开 `DesignPatterns_EnableDiIntegration`)。 -- **Autofac**:`DesignPatterns.Extensions.Autofac` — `RegisterAutofac`(见 [Autofac.md](Autofac.md)) -- **插件程序集**:多项目 `[RegisterStrategy]` 布局见 [PluginAssemblies.md](PluginAssemblies.md) - ---- - -## 与 Factory Registry 的关系 - -| | Strategy Registry | Factory Registry | -|---|---|---| -| 注册内容 | key → 实例(或 build 时 invoke 一次的 factory) | key → `Func` | -| 生命周期 | 通常 Singleton(生成器用 `new()` 静态单例) | 每次 `Create` 调用 factory | -| 编译期 | `[RegisterStrategy]` 生成 Keys + Registry | `[RegisterFactory]` 生成 Keys + `Create()` | -| 共享抽象 | `IStrategyRegistry` 继承 `IReadOnlyRegistry` | `IFactoryRegistry` 继承 `IReadOnlyRegistry` | -| DI | `PaymentStrategyRegistry.RegisterDi(services)` | `ProductFactoryRegistry.RegisterDi(services)` | +## 不变量 -详见 [FactoryRegistry.md](FactoryRegistry.md)。 +1. **key 唯一性**:同一 `TContract` 下 key 不可重复(DP003 在编译期强制)。 +2. **`TStrategy` 不变(invariant)**:`IStrategyRegistry` 的 `TStrategy` 非协变 `out`——`TryGetWithGuard` 的 `out TStrategy` 参数阻止协变。 +3. **guard 签名固定为 `Func`**:guard 仅接收 key,不接收 `TInput`。注册表层面不知道 `TInput`(`TStrategy` 不要求实现 `IStrategy`)。 +4. **`[RegisterStrategy]` 不要求实现 `IStrategy<,>`**:任何接口/基类均可作为策略契约。 +5. **标记的类须有 public 无参构造**:生成器使用 `new()` 实例化(DP007)。 +6. **标记的类须实现指定的 `TContract`**(DP004)。 +7. **`IStrategy<,>` / `IAsyncStrategy<,>` 为可选标记接口**:注册表与生成器不依赖它们。 ---- +## 兼容基线 -## 实施步骤 +- 运行时 TFM:`netstandard2.0` + `net8.0`(两者均须可用并随包分发)。 +- 泛型 Attribute(`RegisterStrategyAttribute`)需要 C# 11+ / `net7.0+`;`netstandard2.0` 目标下用 `#if NET7_0_OR_GREATER` 条件编译。 +- 非泛型 `RegisterStrategyAttribute` 始终可用,功能等价。 +- net8.0 上 `StrategyRegistry` 内部使用 `FrozenDictionary` 优化查找。 -| 步骤 | 内容 | 里程碑 | -|------|------|--------| -| 1 | `IStrategyRegistry` + `StrategyRegistry` + Builder | M1 | -| 2 | `RegisterStrategyAttribute` / `RegisterStrategyAttribute` 定义 | M1 | -| 3 | `RegisterStrategyGenerator`:扫描特性 → 生成 Keys + 注册表 | R1 | -| 4 | 诊断 DP003/DP004/DP007(生成器)+ DP006(Analyzer) | R1 | -| 5 | 示例项目 [DesignPatterns.Samples.Strategy](https://github.com/Skymly/DesignPatterns.Samples/tree/main/DesignPatterns.Samples.Strategy) | R1 | -| 6 | DI 扩展包 + 生成器 `RegisterDi` | 已完成(MSDI) | - ---- - -## 规划中的能力 - -- Autofac / DryIoc 扩展包 -- IDE CompletionProvider(维护成本高) - ---- - -## 不做 +## 不在范围内 - 不做策略选择/路由逻辑(这是业务代码) - 不通过反射扫描程序集 - 不在注册表里管理对象生命周期(那是 DI 的事) - 不做 `Func` 的无限委托展开 - 不强制所有策略实现 `IStrategy<,>` - ---- - -## 参考 - -- GoF: Strategy (Behavioral) -- [AGENTS.md](../AGENTS.md) — 项目规则与里程碑 -- [docs/DEVELOPMENT.md](DEVELOPMENT.md) — 通用开发约定