diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 9745def..f1b8902 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -48,15 +48,8 @@ Closes # ## Documentation checklist - - -- [ ] New/changed public API → Spec updated (`docs/spec/`) -- [ ] New/changed diagnostic ID → Spec updated + `AnalyzerReleases.Unshipped.md` updated -- [ ] Implementation detail changed → Design Doc updated (`docs/design/`) -- [ ] New RFC → used `docs/rfc/_template.md` template -- [ ] RFC status changed → frontmatter updated + archived to `docs/rfc/archive/` if Implemented/Rejected -- [ ] New ADR → numbered from `docs/adr/README.md` next available -- [ ] New Plan / Review → used `docs/plans/_template.md` / `docs/review/_template.md` template -- [ ] Plan / Review status changed → frontmatter updated + archived + index README updated -- [ ] `CHANGELOG.md` `[Unreleased]` entry added + + +- [ ] Design Doc updated if API / diagnostic / implementation changed +- [ ] User-facing docs synced (separate PRs if multi-repo) - [ ] No documentation changes needed diff --git a/AGENTS.md b/AGENTS.md index dbbe7b5..1e57ac2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,7 @@ DesignPatterns.slnx ├── DesignPatterns.Extensions.Autofac/ # Autofac 扩展 + Autofac 生成器 targets ├── DesignPatterns.Package/ # NuGet 元包(PackageId=Skymly.DesignPatterns) ├── tests/ # 单元 / 生成器 Verify / Analyzer / DI -├── docs/ # 维护者文档(DOCUMENTATION、DEVELOPMENT、ROADMAP、rfc/、adr/、spec/、design/、模式文档) +├── docs/ # 维护者文档(DOCUMENTATION、DEVELOPMENT、ROADMAP、adr/、design/) ├── .github/ # Issue/PR 模板、CI └── AGENTS.md ``` @@ -115,7 +115,7 @@ dotnet test DesignPatterns.slnx -c Release | State(M1–M2) | `ITransitionTable`、`[StateMachine]`、`[Transition]` | `StateTransitionGenerator` | | DI Health Checks | `AddDesignPatternsHealthChecks`、`IHealthCheck` | —(运行时扩展) | -模式文档: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)。 +模式文档见 [docs/design/](docs/design/README.md)。横切约定见 [docs/FactoryKeyConventions.md](docs/FactoryKeyConventions.md)。架构决策见 [docs/adr/](docs/adr/README.md)。 --- @@ -160,7 +160,7 @@ dotnet test DesignPatterns.slnx -c Release 诊断 ID 规范(**本表为唯一登记源**,其他文档不得另立分类): -- 下一个可用 ID:**DP067**;ID 一经发布不复用、不改语义(DP067–DP071 已由 [RFC: Singleton 生命周期诊断](docs/rfc/SingletonLifecycleDiagnostics.md) 预留)。 +- 下一个可用 ID:**DP067**;ID 一经发布不复用、不改语义(DP067–DP071 已由 [ADR-008](docs/adr/ADR-008-singleton-lifecycle-diagnostics.md) 预留)。 - 新增 / 修改诊断必须同步 [`DiagnosticIds.cs`](DesignPatterns.Diagnostics/DiagnosticIds.cs)、[`DesignPatternsDiagnosticDescriptors.cs`](DesignPatterns.Diagnostics/DesignPatternsDiagnosticDescriptors.cs)(经 Compile Link 编入 SourceGenerators / Analyzers)与 [`AnalyzerReleases.Unshipped.md`](DesignPatterns.SourceGenerators/AnalyzerReleases.Unshipped.md)。 - 归属:DP006 / DP023 / DP024 / DP025 / DP033 / DP036 / DP044 / DP060 / DP061 / DP062 / DP066 属 **Analyzer**;其余属**生成器**。 - 文案:`messageFormat` 须含可操作建议;`description` 供 IDE 悬停;`helpLinkUri` 指向 [`DesignPatterns.Docs` diagnostics 页](https://skymly.github.io/DesignPatterns.Docs/diagnostics)(`#dp###` 片段,见 [`DiagnosticHelpLinks.cs`](DesignPatterns.Diagnostics/DiagnosticHelpLinks.cs))。 @@ -313,40 +313,28 @@ git push origin v0.1.0-preview3 --- -## 文档体系(文档驱动开发) +## 文档体系 -本仓库实行**文档驱动开发**:先文档后代码,任何非琐碎变更先满足文档前置条件(决策表见 [docs/DOCUMENTATION.md §11](docs/DOCUMENTATION.md#11-文档驱动开发流程))再进入实现。文档分为 7 种类型,完整规范见 [docs/DOCUMENTATION.md](docs/DOCUMENTATION.md)。Agent 与人类开发者均须遵守。 +完整约定见 [docs/DOCUMENTATION.md](docs/DOCUMENTATION.md)。仓内沉淀 ADR + Design Doc + Roadmap;任务与审查用 GitHub Issue / PR / Release。 -| 类型 | 目录 | 用途 | 关键规则 | -|------|------|------|----------| -| **RFC** | `docs/rfc/` | 设计提案与讨论 | 新增模式/诊断/破坏性 API 必须 RFC;模板 `docs/rfc/_template.md`;已实现移入 `archive/` | -| **ADR** | `docs/adr/` | 架构决策记录(不可变) | RFC Accepted → 产出 ADR;编号不复用;正文不修改,仅 Supersede | -| **Spec** | `docs/spec/` | 稳定契约(API 面、诊断 ID、不变量) | 变更需 RFC + ADR;随代码 PR 同步更新 | -| **Design Doc** | `docs/design/` | 实现细节、设计权衡、已知局限 | 随代码 PR 同步更新 | -| **Plan** | `docs/plans/` | 大型任务计划(跨多 PR) | 里程碑对齐单模块 PR 边界;Done/Cancelled 移入 `archive/`;小任务用 Issue 即可 | -| **Review** | `docs/review/` | 评审记录(设计/实现/发版/回顾) | Final 后正文不可变;行动项全部关闭移入 `archive/` | -| **Roadmap** | `docs/ROADMAP.md` | 功能与技术 backlog | 完成项移入「已完成(归档)」章节 | - -**归档统一规则**:归档 = 移动文件 + 更新状态字段 + 更新 README 索引,同一 PR 完成;归档后正文不再修改(仅修失效链接);归档不删除。 +| 载体 | 位置 | 用途 | +|------|------|------| +| **ADR** | `docs/adr/` | 架构决策(不可变;编号不复用;仅 Supersede) | +| **Design Doc** | `docs/design/` | 每域一份:API / 诊断 / 实现与权衡 | +| **Roadmap** | `docs/ROADMAP.md` | 宏观 backlog | +| **Issue / PR / Release** | GitHub | 任务、审查、版本历史 | -### Agent 文档工作流约定 +### Agent 文档工作流 | 场景 | Agent 行为 | |------|-----------| -| 新增诊断 ID | 确认是否有对应 RFC;无则提示需创建 RFC | -| 修改公共 API | 确认是否有对应 RFC + ADR;无则提示需创建 RFC | -| 跨多 PR 的大型任务 | 确认 `docs/plans/` 是否有对应 Plan;无则先建 Plan(经用户确认)再实现 | -| 创建 RFC | 使用 `docs/rfc/_template.md`;frontmatter 从 `Draft` 开始 | -| 创建 ADR | 编号取 `docs/adr/README.md` 中下一个可用编号 | -| 创建 Plan / Review | 使用对应 `_template.md`;Review 评审人注明为 Agent | -| RFC / Plan / Review 状态变更 | 更新 frontmatter `状态` + 日期;归档时移动到对应 `archive/` 并更新 README 索引 | -| Spec 变更 | 确认 RFC 已 Accepted;同步更新 Spec 版本号 | -| CHANGELOG | 在 `[Unreleased]` 下添加条目 | -| 文档目录 | 不在 `docs/` 之外创建文档文件(`.Local/` 除外) | - -### 模式文档结构 +| 新增诊断 ID / 改生成器行为 / 改公开 API | 同一 PR 更新对应 Design Doc;登记 `DiagnosticIds` + descriptors + `AnalyzerReleases`;更新 `AGENTS.md` 诊断表 | +| 破坏性或跨模块架构决策 | 记新 ADR(编号取 `docs/adr/README.md` 下一个可用编号);正文不可改,仅 Supersede | +| 发版 | 按 `docs/PUBLISHING.md`;`CHANGELOG` 迁出版本节;不擅自 bump/tag/publish | +| CHANGELOG | 用户可见变更写入 `[Unreleased]` | +| 文档目录 | 不在 `docs/` 之外新建文档(`.Local/` 除外) | -所有模式文档已拆分为 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)。 +模式文档索引:[docs/design/README.md](docs/design/README.md)。 --- @@ -366,9 +354,9 @@ git push origin v0.1.0-preview3 Agent 行为准则——与「与用户沟通」并行生效: 1. **用户表述不清楚时,立刻询问**:不要基于猜测继续工作。用聚焦的问题(而非开放式提问)澄清意图,提供 2–4 个具体选项供用户选择。 -2. **用户表述不合理时,立刻指出并给出建议**:包括但不限于——违反已有 ADR(如 Core 引用 MSDI、厚重基类体系替代 primitives)、复用或改变已发布 `DP###` 诊断 ID 的语义、未走 RFC/ADR 流程变更 Spec 契约、跳过测试(单元 / 生成器 Verify 快照 / Analyzer)、单 PR 混合多个模块、破坏兼容基线(TFM / Roslyn 4.8.0)、过度设计。指出问题时必须说明**为什么不合理**,并给出合理替代方案。 +2. **用户表述不合理时,立刻指出并给出建议**:包括但不限于——违反已有 ADR(如 Core 引用 MSDI、厚重基类体系替代 primitives)、复用或改变已发布 `DP###` 诊断 ID 的语义、未更新 Design Doc / ADR 就变更已发布契约、跳过测试(单元 / 生成器 Verify 快照 / Analyzer)、单 PR 混合多个模块、破坏兼容基线(TFM / Roslyn 4.8.0)、过度设计。指出问题时必须说明**为什么不合理**,并给出合理替代方案。 3. **不要盲目执行**:即使能「做到」用户要求的事,如果认为方向有误,应先提出异议,等待用户确认后再动手。 -4. **发现矛盾时主动报告**:如果用户的新要求与已有 ADR / RFC / `AGENTS.md` 规则冲突,指出冲突点,由用户决定是否更新规则或调整需求(ADR 变更须走 Supersede 流程,见 [docs/DOCUMENTATION.md](docs/DOCUMENTATION.md))。 +4. **发现矛盾时主动报告**:如果用户的新要求与已有 ADR / `AGENTS.md` 规则冲突,指出冲突点,由用户决定是否更新规则或调整需求(ADR 变更须走 Supersede 流程,见 [docs/DOCUMENTATION.md](docs/DOCUMENTATION.md))。 ## 与用户沟通 diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f9aebb..7c50996 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,9 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- **Composite parallel traversal**: `CompositeTraverser.TraverseParallel` / `TraverseParallelAsync` / `TraverseForestParallel` / `TraverseForestParallelAsync` — parallel tree traversal with `MaxDegreeOfParallelism` and `MaxParallelDepth` options. BFS same-level parallel, DFS child-parallel recursion with sequential fallback beyond depth threshold. `AggregateException` for error aggregation. `ConfigureAwait(false)` for async paths. `#if` split: `Parallel.ForEachAsync` on net8.0, `SemaphoreSlim` + `Task.WhenAll` on netstandard2.0. 17 new tests. Design RFC: [docs/rfc/CompositeParallelTraversal.md](docs/rfc/CompositeParallelTraversal.md). +- **Composite parallel traversal**: `CompositeTraverser.TraverseParallel` / `TraverseParallelAsync` / `TraverseForestParallel` / `TraverseForestParallelAsync` — parallel tree traversal with `MaxDegreeOfParallelism` and `MaxParallelDepth` options. BFS same-level parallel, DFS child-parallel recursion with sequential fallback beyond depth threshold. `AggregateException` for error aggregation. `ConfigureAwait(false)` for async paths. `#if` split: `Parallel.ForEachAsync` on net8.0, `SemaphoreSlim` + `Task.WhenAll` on netstandard2.0. 17 new tests. See [ADR-006](docs/adr/ADR-006-composite-parallel-traversal.md). - **DP062 Phase 2 — generated RegisterDi coverage**: `CaptiveDependencyAnalyzer` now also scans `RegisterDi` calls from DesignPatterns source generators. Extracts `implementationLifetime` (or `lifetime` for single-param overloads) and applies it to all types bearing `[RegisterStrategy]`, `[RegisterFactory]`, `[RegisterEventHandler]`, `[Decorator]`, `[CompositePart]` attributes. 5 new Verify snapshot tests. -- **Singleton lifecycle diagnostics P1 — Autofac and factory delegate coverage**: `CaptiveDependencyAnalyzer` now collects Autofac registrations (`RegisterType` + fluent lifetime chain, `Register(c => ...)`) via symbol-name matching with no Autofac package reference, and reports the new **DP066** (Warning) when a Singleton factory delegate (`AddSingleton(sp => ...)` or Autofac `Register(...).SingleInstance()`) directly resolves a Scoped/Transient service via `GetRequiredService`/`GetService`/`Resolve`. MSDI factory and instance registrations now also feed the lifetime map, so Singletons constructor-depending on factory-registered Scoped/Transient services report DP062. Limitation: only direct resolution calls inside the delegate body are detected. 11 new Verify snapshot tests. RFC: [docs/rfc/SingletonLifecycleDiagnostics.md](docs/rfc/SingletonLifecycleDiagnostics.md), ADR-008. +- **Singleton lifecycle diagnostics P1 — Autofac and factory delegate coverage**: `CaptiveDependencyAnalyzer` now collects Autofac registrations (`RegisterType` + fluent lifetime chain, `Register(c => ...)`) via symbol-name matching with no Autofac package reference, and reports the new **DP066** (Warning) when a Singleton factory delegate (`AddSingleton(sp => ...)` or Autofac `Register(...).SingleInstance()`) directly resolves a Scoped/Transient service via `GetRequiredService`/`GetService`/`Resolve`. MSDI factory and instance registrations now also feed the lifetime map, so Singletons constructor-depending on factory-registered Scoped/Transient services report DP062. Limitation: only direct resolution calls inside the delegate body are detected. 11 new Verify snapshot tests. See [ADR-008](docs/adr/ADR-008-singleton-lifecycle-diagnostics.md). +- **Documentation system simplification**: Removed in-repo RFC / Spec / Plan / Review document types. Spec content merged into Design Docs; task tracking and review use GitHub Issues / PRs. See [docs/DOCUMENTATION.md](docs/DOCUMENTATION.md). ## [0.2.2] - 2026-06-30 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3eb70ac..d624d27 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,18 +63,17 @@ dotnet test tests/DesignPatterns.SourceGenerators.Tests --filter FullyQualifiedN ## 文档 -本仓库文档体系分为 RFC、ADR、Spec、Design Doc、Roadmap 五种类型,完整规范见 [`docs/DOCUMENTATION.md`](docs/DOCUMENTATION.md)。人类开发者和 AI 编码助手均须遵守。 +文档约定见 [`docs/DOCUMENTATION.md`](docs/DOCUMENTATION.md)。仓内保留 ADR、Design Doc、Roadmap;任务与审查用 GitHub Issue / PR。 ### 文档工作流 | 变更类型 | 需要的文档操作 | |----------|----------------| -| 新增模式 / 诊断 ID / 破坏性 API | 创建 RFC → Accepted 后产出 ADR → 实现 PR 中更新 Spec + Design Doc | -| Bug fix | Issue + PR;如影响实现细节则更新 Design Doc | -| 公共 API 变更 | Spec 更新 + CHANGELOG 条目 | -| 诊断 ID 变更 | Spec 更新 + `AnalyzerReleases.Unshipped.md` 更新 | -| 用户可见变更 | 更新 [`CHANGELOG.md`](CHANGELOG.md)(英语,Keep a Changelog;发版时将 `[Unreleased]` 条目迁入版本节) | -| 新模式 | 在 [DesignPatterns.Samples](https://github.com/Skymly/DesignPatterns.Samples) 增加示例项目、更新本仓 `docs/` 与 [`AGENTS.md`](AGENTS.md) | +| 破坏性 API / 跨模块架构 | ADR + 更新对应 Design Doc | +| 公共 API / 诊断 / 实现细节变化 | 更新 Design Doc(+ `AnalyzerReleases` / `AGENTS.md` 诊断表,如适用) | +| Bug fix | Issue + PR;影响实现细节时更新 Design Doc | +| 用户可见变更 | 更新 [`CHANGELOG.md`](CHANGELOG.md)(英语,Keep a Changelog) | +| 新模式 | DesignPatterns.Samples 示例 + 本仓 Design Doc + [`AGENTS.md`](AGENTS.md) | | key 命名 | 遵循 [`docs/FactoryKeyConventions.md`](docs/FactoryKeyConventions.md) | | 架构 backlog | 见 [`docs/ROADMAP.md`](docs/ROADMAP.md) | @@ -82,9 +81,7 @@ dotnet test tests/DesignPatterns.SourceGenerators.Tests --filter FullyQualifiedN | 目录 | 用途 | |------|------| -| [`docs/DOCUMENTATION.md`](docs/DOCUMENTATION.md) | 文档体系标准(类型、生命周期、模板、归档) | -| [`docs/rfc/`](docs/rfc/README.md) | RFC — 设计提案与讨论 | -| [`docs/adr/`](docs/adr/README.md) | ADR — 架构决策记录 | -| [`docs/spec/`](docs/spec/README.md) | Spec — 稳定契约 | -| [`docs/design/`](docs/design/README.md) | Design Doc — 实现细节 | +| [`docs/DOCUMENTATION.md`](docs/DOCUMENTATION.md) | 文档约定 | +| [`docs/adr/`](docs/adr/README.md) | ADR — 架构决策 | +| [`docs/design/`](docs/design/README.md) | Design Doc — 每域设计 | | [`docs/ROADMAP.md`](docs/ROADMAP.md) | 路线图 | diff --git a/README.md b/README.md index 307f1ce..40db675 100644 --- a/README.md +++ b/README.md @@ -88,8 +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/spec/](docs/spec/README.md) | 模式规范(Spec)— 稳定契约:API 面、诊断 ID、不变量 | -| [docs/design/](docs/design/README.md) | 模式设计文档(Design Doc)— 实现细节、设计权衡、已知局限 | +| [docs/design/](docs/design/README.md) | 模式设计文档(Design Doc)— API / 诊断 / 实现与权衡 | +| [docs/adr/](docs/adr/README.md) | 架构决策记录(ADR) | | [docs/ROADMAP.md](docs/ROADMAP.md) | 功能与技术 backlog | | [CONTRIBUTING.md](CONTRIBUTING.md) | 贡献与测试说明 | | [AGENTS.md](AGENTS.md) | AI 编码助手项目上下文 | diff --git a/docs/DOCUMENTATION.md b/docs/DOCUMENTATION.md index 6b853f4..6f82c44 100644 --- a/docs/DOCUMENTATION.md +++ b/docs/DOCUMENTATION.md @@ -1,726 +1,76 @@ -# 文档体系标准 +# 文档约定 -> **权威源**。本文档定义本仓库**文档驱动开发(Documentation-Driven Development)**体系:所有文档的类型、结构、生命周期、归档规则,以及以文档为先导的开发流程。人类开发者和 AI 编码助手(Agent)均须遵守。`AGENTS.md`「文档体系」章节为本文档的精简摘要。 +> **权威源**。定义本仓库文档载体与维护规则。人类开发者与 AI 编码助手均须遵守。`AGENTS.md`「文档体系」为本文摘要。 > -> - **核心原则**:**先文档后代码**——任何非琐碎变更,先确定它需要哪些文档、文档达到要求状态后才动代码(决策表见 [§11](#11-文档驱动开发流程))。 -> - **语言**:内部维护者文档以**中文**为主;面向库使用者的文档在 [DesignPatterns.Docs](https://github.com/Skymly/DesignPatterns.Docs) 仓库。 +> - **语言**:内部维护者文档以**中文**为主;面向库使用者的文档在 [DesignPatterns.Docs](https://github.com/Skymly/DesignPatterns.Docs)。 > - **冲突优先级**:`AGENTS.md` > `docs/DOCUMENTATION.md` > 其他文档。 ---- +## 1. 文档载体 -## 1. 文档类型总览 - -| 类型 | 目录 | 用途 | 稳定性 | 变更门槛 | -|------|------|------|--------|----------| -| **RFC** | `docs/rfc/` | 设计提案与讨论记录 | 提案阶段,频繁迭代 | 自由修改(Review 前) | -| **ADR** | `docs/adr/` | 架构决策记录(不可变卡片) | 已决策,仅追加 | 仅 Supersede,不修改原文 | -| **Spec** | `docs/spec/` | 稳定契约(API 面、诊断 ID、不变量) | 版本化稳定 | 需 RFC + ADR 方可变更 | -| **Design Doc** | `docs/design/` | 实现细节、设计权衡、已知局限 | 随实现演进 | PR 随代码同步更新 | -| **Roadmap** | `docs/ROADMAP.md` | 功能与技术 backlog | 滚动维护 | 维护者评审 | -| **Plan** | `docs/plans/`(大型)/ GitHub Issue(小型) | 任务计划(目标、步骤、验收) | 短生命周期 | 计划内自由更新 | -| **Review** | `docs/review/` | 评审记录(设计 / 实现 / 阶段回顾) | Final 后不可变 | 仅勾选行动项与修复链接 | +| 载体 | 位置 | 用途 | +|------|------|------| +| **ADR** | `docs/adr/` | 架构决策(不可变卡片) | +| **Design Doc** | `docs/design/` | 每域一份:API 面 + 诊断/契约 + 实现细节 + 设计权衡 | +| **Roadmap** | `docs/ROADMAP.md` | 宏观规划与 backlog 排序 | +| **Issue** | GitHub Issues | 需求、Bug、任务追踪 | +| **PR** | GitHub Pull Requests | 变更审查 | +| **Release** | GitHub Releases | 版本历史 | -### 1.1 不作为独立文档类型 +### 不作为独立文档类型 | 内容 | 载体 | |------|------| -| 编码规范、兼容基线、打包、测试 | `AGENTS.md`(权威源) | +| 编码规范、兼容基线、打包、测试 | `AGENTS.md` | | 开发环境、构建、仓库布局 | `docs/DEVELOPMENT.md` | | 发布流程 | `docs/PUBLISHING.md` | -| 变更日志 | `CHANGELOG.md`(Keep a Changelog 格式) | +| 变更日志 | `CHANGELOG.md` | | 贡献流程 | `CONTRIBUTING.md` | ---- - -## 2. RFC — Request for Comments - -### 2.1 用途 - -对**有设计争议或影响面较大**的变更提出设计方案,供讨论与决策。小改动(bug fix、单方法新增)无需 RFC,直接 Issue + PR。 - -### 2.2 何时需要 RFC - -| 场景 | 需要 RFC? | -|------|-----------| -| 新增模式(新源生成器 + 诊断) | ✅ 必须 | -| 新增或变更公共 API(破坏性) | ✅ 必须 | -| 新增诊断 ID(`DP###`) | ✅ 必须 | -| 跨模块架构变更 | ✅ 必须 | -| 单模块内 bug fix | ❌ Issue + PR | -| 单模块内新增非破坏性 API | ❌ Issue + PR(但需在 Design Doc 记录) | -| 文档/测试/重构 | ❌ Issue + PR | -| 工程整改(CI、构建脚本) | ⚠️ 视影响面,由维护者判断 | - -### 2.3 文件命名 - -``` -docs/rfc/.md -``` - -示例:`CompositeParallelTraversal.md`、`HierarchicalStateMachine.md`。 - -### 2.4 Frontmatter 标准 - -每份 RFC **必须**以如下元数据块开头(blockquote 格式,字段固定): - -```markdown -> **状态**:Draft | Review | Accepted | Rejected | Implemented | Superseded -> **类型**:Feature | Pattern | Architecture | Process -> **创建**:YYYY-MM-DD -> **更新**:YYYY-MM-DD -> **作者**:维护者 / 贡献者 -> **关联 Roadmap**:FXX(如有) -> **关联 Issue**:#XXX(如有) -> **衍生 ADR**:ADR-XXX(Accepted 后填写) -``` - -### 2.5 正文章节模板 - -```markdown -# RFC: <标题> - -> (frontmatter) - -## 摘要 -一段话说明提案内容。 - -## 动机 -为什么需要这个变更?现有局限是什么? - -## 非目标 -明确不做什么,避免范围蔓延。 - -## 设计方案 -### 概念模型 -### API 设计 -### 诊断影响 -### 实现方案 - -## 替代方案 -考虑过但否决的方案及原因。 - -## 开放问题 -讨论中尚未决策的问题(Review 阶段)。 - -## 决策记录 -Review 结束后记录最终决策与理由(Accepted 阶段填写)。 - -## 参考 -``` - -### 2.6 生命周期 - -``` -Draft → Review → Accepted → Implemented → (archive) - ↘ Rejected → (archive) -``` - -| 状态 | 含义 | 操作 | -|------|------|------| -| **Draft** | 作者撰写中,未公开征求意见 | 可自由修改 | -| **Review** | 公开征求意见 | 更新 frontmatter `状态`;讨论在 GitHub Issue 或 PR Comments | -| **Accepted** | 决策通过,待实现 | 填写「决策记录」章节;创建衍生 ADR;关联 Roadmap 项标记「进行中」 | -| **Rejected** | 决策否决 | 记录否决理由;移入 `archive/` | -| **Implemented** | 已实现并合并 | 关联 PR 号;移入 `archive/` | -| **Superseded** | 被后续 RFC 取代 | 标注取代者链接;移入 `archive/` | - -### 2.7 归档规则 - -- 状态变为 **Rejected**、**Implemented** 或 **Superseded** 时,文件移入 `docs/rfc/archive/`。 -- 归档文件**不再修改**(除修正链接失效)。 -- `docs/rfc/README.md` 状态板保留归档条目的链接。 - ---- - -## 3. ADR — Architecture Decision Record - -### 3.1 用途 - -记录**最终架构决策**的简短不可变卡片。ADR 不是讨论场所——讨论在 RFC 中完成,ADR 只记录结论。 - -### 3.2 与 RFC 的关系 - -- RFC **Accepted** → 产出一份 ADR(在 RFC frontmatter `衍生 ADR` 字段和 ADR `关联 RFC` 字段双向链接)。 -- 少数情况下,无需完整 RFC 的小决策也可直接写 ADR(如编码风格选择),但需在 ADR 中说明为何跳过 RFC。 - -### 3.3 文件命名 - -``` -docs/adr/ADR--.md -``` - -编号从 `001` 开始,零填充三位,**不复用编号**。示例:`ADR-001-roslyn-source-generators.md`。 - -### 3.4 格式模板 - -```markdown -# ADR-NNN: <标题> - -| 字段 | 值 | -|------|-----| -| **状态** | Accepted | Superseded by ADR-XXX | Deprecated | -| **日期** | YYYY-MM-DD | -| **关联 RFC** | [docs/rfc/XXX.md](../rfc/XXX.md)(或「无 — 直接决策」) | - -## 背景 - -为什么需要做这个决策?当时的约束和问题是什么? - -## 决策 - -最终决定了什么?一句话概括 + 补充说明。 - -## 后果 - -这个决策带来的正面和负面影响。 - -## 参考 - -- 相关 ADR、文档、外部链接 -``` - -### 3.5 不可变原则 - -- ADR 一旦 **Accepted**,正文**不修改**。 -- 若决策被推翻,创建新 ADR 并将旧 ADR 状态改为 `Superseded by ADR-XXX`,旧 ADR 正文仍不修改。 -- 编号**永不复用**。 - ---- - -## 4. Spec — 规范文档 - -### 4.1 用途 - -定义模式的**稳定契约**:公共 API 面、诊断 ID、不变量、兼容基线。Spec 是「接口」级别的文档——描述 **what**,不描述 **how**。 - -### 4.2 与 Design Doc 的关系 - -| 维度 | Spec | Design Doc | -|------|------|------------| -| 回答 | What(契约是什么) | How + Why(怎么实现、为什么这样做) | -| 稳定性 | 高——变更需 RFC + ADR | 中——随实现演进 | -| 读者 | 库使用者 + 维护者 | 维护者 | -| 位置 | `docs/spec/` | `docs/design/` | - -### 4.3 文件命名 - -``` -docs/spec/.md -``` - -与模式名一致。示例:`docs/spec/Strategy.md`。 - -### 4.4 章节模板 - -```markdown -# Spec: <模式名> - -> **版本**:vX.Y(与 NuGet 包版本对齐) -> **关联 Design Doc**:[docs/design/.md](../design/.md) -> **关联 ADR**:ADR-XXX(如有) - -## API 面 - -### 运行时接口 -(接口定义、方法签名、泛型约束) - -### 特性(Attribute) -(特性类、属性、构造函数签名) - -### 生成器产出 -(生成的类名、方法签名、命名空间) - -## 诊断 ID - -| ID | 级别 | 触发条件 | 消息格式 | -|----|------|----------|----------| -| DPXXX | Warning | ... | ... | - -## 不变量 - -1. ... -2. ... - -## 兼容基线 - -- netstandard2.0 / net8.0 -- ... - -## 不在范围内 - -- ... -``` - -### 4.5 变更门槛 - -- **新增 API** 或 **新增诊断 ID**:需 RFC → ADR → Spec 更新。 -- **破坏性变更 API**:需 RFC → ADR → Spec 更新 + CHANGELOG `Breaking` 条目。 -- **文档修正**(措辞、示例):直接 PR,无需 RFC。 - ---- - -## 5. Design Doc — 设计文档 - -### 5.1 用途 - -记录模式的**实现细节**、设计权衡、已知局限、与生态的边界。Design Doc 是「实现」级别的文档——描述 **how** 和 **why**。 - -### 5.2 文件命名 - -``` -docs/design/.md -``` - -与对应 Spec 同名。示例:`docs/design/Strategy.md`。 - -### 5.3 章节模板 - -```markdown -# Design Doc: <模式名> - -> **关联 Spec**:[docs/spec/.md](../spec/.md) -> **关联 RFC**:[docs/rfc/XXX.md](../rfc/XXX.md)(如有) -> **关联 ADR**:ADR-XXX(如有) - -## 概述 - -## 设计目标 - -## 实现概览 - -### 运行时 -(关键类、数据结构、算法) - -### 源生成器 -(增量管线阶段、EquatableArray 结构) - -### 诊断 -(检测逻辑、报告位置) - -## 设计权衡 - -### 选择了 X 而非 Y,因为... -(链接到 ADR) - -## 与生态的边界 - -## 已知局限 - -## 参考 -``` - -### 5.4 变更门槛 +**判断原则**:若信息在 Issue / PR / Release 中已完整表达,不必再写仓内文件。仓内只记跨多个 Issue/PR 的累积知识,以及不应随讨论漂移的决策。 -- 随代码 PR 同步更新,无需独立 RFC。 -- 但若实现变更导致 Spec 契约变更,则需走 RFC 流程。 +## 2. ADR ---- +- **何时写**:破坏性 API、跨模块架构、诊断语义边界、与已有 ADR 冲突需改决策时。 +- **编号**:从 ADR-001 起,三位零填充,**不复用**;下一个编号见 [adr/README.md](adr/README.md)。 +- **正文不可变**:Accepted 后不改决策正文;仅允许修正失效链接。 +- **变更方式**:新决策写新 ADR,旧 ADR 标 `Superseded by ADR-NNN`。 +- **模板**:[adr/_template.md](adr/_template.md)。 -## 6. Roadmap +## 3. Design Doc -### 6.1 用途 +- **何时更新**:公开 API、诊断 ID、生成器行为或实现权衡变化时,在**同一 PR** 更新对应 `docs/design/.md`。 +- **内容**:API 面、诊断表、不变量/兼容基线、实现概览、设计权衡、已知局限。 +- **模板**:[design/_template.md](design/_template.md);索引:[design/README.md](design/README.md)。 -维护 `docs/ROADMAP.md` 作为功能与技术 backlog 的滚动清单。 +## 4. Roadmap -### 6.2 生命周期 +- 维护宏观排序与探索候选;完成项移入「已完成(归档)」。 +- 具体任务与验收在 **GitHub Issue** 跟踪,不在仓内另建 Plan 文档。 -``` -候选 → 排期 → 进行中 → 已完成(归档) - ↘ 暂缓 / 明确不做 -``` - -- **候选**:在 Roadmap 中登记,未排期。 -- **排期**:分配到 FXX 阶段,准备启动。 -- **进行中**:创建 RFC(如需要)+ Issue + 分支。 -- **已完成**:移入 Roadmap「已完成(归档)」章节。 -- **暂缓 / 明确不做**:移入对应章节,记录理由。 - -### 6.3 与其他文档的联动 - -| Roadmap 状态 | 关联文档 | -|--------------|----------| -| 排期 → 进行中 | 创建 RFC(如需要)→ Accepted → 产出 ADR | -| 进行中 → 已完成 | Spec 更新 + Design Doc 更新 + CHANGELOG 条目 + RFC 归档 | - ---- - -## 7. Plan — 任务计划 - -### 7.1 载体(双轨) - -| 规模 | 载体 | 判断标准 | -|------|------|----------| -| **小型任务** | GitHub Issue | 单 PR 可完成(bug fix、单方法新增、文档修正) | -| **大型任务** | `docs/plans/.md` + 主 Issue | 跨多 PR、跨多阶段、或由 RFC 衍生的实现计划 | - -大型任务的 Plan 文档是**执行的唯一真相源**:目标、里程碑拆解、验收标准、进度状态都记录在文档中;主 Issue 仅作 GitHub 侧跟踪入口,链接到 Plan 文档。 - -### 7.2 文件命名 - -``` -docs/plans/.md -``` - -与关联 RFC 同名(如有)。示例:`docs/plans/HierarchicalStateMachine.md`。 - -### 7.3 Frontmatter 标准 - -```markdown -> **状态**:Active | Done | Cancelled -> **创建**:YYYY-MM-DD -> **更新**:YYYY-MM-DD -> **关联 RFC**:[docs/rfc/XXX.md](../rfc/XXX.md)(如有) -> **关联 Issue**:#XXX -> **关联 Roadmap**:FXX(如有) -``` - -### 7.4 正文章节模板 - -```markdown -# Plan: <标题> - -> (frontmatter) - -## 目标 -一段话说明要交付什么。 - -## 非目标 -本计划不覆盖的内容。 - -## 里程碑拆解 - -| 阶段 | 内容 | 模块(AGENTS.md 边界) | 状态 | PR | -|------|------|------------------------|------|-----| -| P1 | ... | Runtime | [ ] | — | -| P2 | ... | SourceGenerators | [ ] | — | - -## 验收标准 -- [ ] ... - -## 风险与依赖 - -## 变更记录 -计划执行中的重大调整(范围增减、顺序变更)在此追加,不删除原文。 -``` +## 5. 同步 checklist -### 7.5 生命周期与归档 +变更公共 API / 诊断 / 生成代码时,按需同步: -``` -Active → Done → archive/ - ↘ Cancelled → archive/(记录取消原因) -``` - -- 所有里程碑完成且验收标准全部满足 → 状态改 `Done`,移入 `docs/plans/archive/`。 -- 中途取消 → 状态改 `Cancelled`,在「变更记录」中写明原因后归档。 -- 小型任务的 Issue 不归档(GitHub close 即完成)。 - -### 7.6 何时需要 Issue - -- 所有 PR 必须关联一个 Issue(bug fix 可使用 `bug_report` 模板,功能使用 `feature_request` 模板)。 -- 大型特性(跨多 PR)使用一个主 Issue + `docs/plans/` 计划文档。 - ---- - -## 8. Review — 评审记录 - -### 8.1 用途 +1. 本仓 `docs/design/.md` +2. `DesignPatterns.Diagnostics/DiagnosticIds.cs` + descriptors + `AnalyzerReleases.*.md` +3. `AGENTS.md` 诊断表(若新增 `DP###`) +4. `CHANGELOG.md` `[Unreleased]` +5. 用户向文档:[DesignPatterns.Docs](https://github.com/Skymly/DesignPatterns.Docs)(**独立 PR**) +6. 示例:[DesignPatterns.Samples](https://github.com/Skymly/DesignPatterns.Samples)(若行为可见变化) -记录**结构化评审结论**,使评审意见可追溯、行动项可跟踪。区别于 PR 内的行内 code review(即时、随 PR 关闭而结束),Review 文档记录**跨 PR 或里程碑级**的评审。 - -### 8.2 何时需要 Review 文档 - -| 场景 | 需要 Review 文档? | -|------|--------------------| -| RFC 进入 Review 阶段的设计评审 | ✅(评审结论落文档,RFC「决策记录」引用) | -| 大型 Plan 完成后的实现回顾 | ✅ | -| 发版前的 API 面 / 打包审查 | ✅ | -| 定期技术债 / 代码质量审查 | ✅ | -| 单 PR 的常规 code review | ❌ PR Comments 即可 | - -### 8.3 文件命名 - -``` -docs/review/-.md -``` - -示例:`docs/review/2026-07-08-composite-schema-validation-design.md`。 - -### 8.4 Frontmatter 标准 - -```markdown -> **状态**:Draft | Final -> **类型**:Design | Implementation | Release | Retrospective -> **日期**:YYYY-MM-DD -> **评审对象**:RFC / Plan / PR 范围 / 版本号 -> **评审人**:维护者 / Agent(注明) -``` - -### 8.5 正文章节模板 - -```markdown -# Review: <标题> - -> (frontmatter) - -## 评审范围 -评审了什么(文档、代码范围、版本)。 - -## 结论 -通过 | 有条件通过 | 不通过,一句话概括。 - -## 发现 - -| # | 级别 | 发现 | 建议 | -|---|------|------|------| -| 1 | Blocker / Major / Minor / Nit | ... | ... | - -## 行动项 - -- [ ] #1 → Issue #XXX / PR #XXX -- [ ] #2 → ... - -## 参考 -``` - -### 8.6 生命周期与归档 - -``` -Draft → Final → (行动项全部关闭后)archive/ -``` - -- **Final 后正文不可变**:新发现另起新 Review;仅允许勾选行动项 checkbox、补充 Issue/PR 链接。 -- 行动项全部关闭后移入 `docs/review/archive/`。 -- 级别定义:**Blocker**(不修复不得合并/发版)、**Major**(须建 Issue 跟踪)、**Minor**(建议修复)、**Nit**(可忽略)。 - ---- - -## 9. 归档机制(统一规则) - -各类型文档共用同一套归档纪律: - -| 类型 | 归档目录 | 归档触发 | 归档后可改动 | -|------|----------|----------|--------------| -| RFC | `docs/rfc/archive/` | Implemented / Rejected / Superseded | 仅修失效链接 | -| Plan | `docs/plans/archive/` | Done / Cancelled | 仅修失效链接 | -| Review | `docs/review/archive/` | Final 且行动项全部关闭 | 仅修失效链接 | -| ADR | 不移动(原地 Supersede) | — | 仅状态字段改 `Superseded by ADR-XXX` | -| Spec / Design Doc | 不归档(活文档) | 模式被移除时随代码删除 | 随实现演进 | -| Roadmap 条目 | `ROADMAP.md`「已完成(归档)」章节 | 功能落地 | 仅追加 | - -通用规则: - -1. **归档 = 移动文件 + 更新状态字段 + 更新对应 README 索引**,三者必须在同一 PR 完成。 -2. 归档文件**正文不再修改**(唯一例外:修正失效链接)。 -3. 归档不删除——历史决策与讨论过程是资产;需要检索时从各目录 README 的索引进入。 -4. 引用归档文件时使用 `archive/` 路径;归档时须全仓搜索旧路径并修正引用。 - ---- - -## 10. 目录结构 +## 6. 目录结构 ``` docs/ -├── DOCUMENTATION.md # 本文件 — 文档体系标准 -├── README.md # 文档索引 -├── DEVELOPMENT.md # 开发手册 -├── ROADMAP.md # 路线图 -├── PUBLISHING.md # 发布流程 -├── rfc/ -│ ├── README.md # RFC 索引 + 状态板 -│ ├── _template.md # RFC 模板 -│ ├── .md # 进行中的 RFC -│ └── archive/ -│ └── .md # 已实现/已否决的 RFC +├── DOCUMENTATION.md +├── README.md +├── ROADMAP.md ├── adr/ -│ ├── README.md # ADR 索引 -│ └── ADR-NNN-.md # 架构决策记录 -├── spec/ -│ ├── README.md # Spec 索引 -│ ├── _template.md # Spec 模板 -│ └── <PatternName>.md # 模式规范 -├── design/ -│ ├── README.md # Design Doc 索引 -│ └── <PatternName>.md # 模式设计文档 -├── plans/ -│ ├── README.md # Plan 状态板 -│ ├── _template.md # Plan 模板 -│ ├── <ActivePlan>.md # 进行中的大型任务计划 -│ └── archive/ -│ └── <DonePlan>.md # 已完成/已取消的计划 -├── review/ -│ ├── README.md # Review 索引 -│ ├── _template.md # Review 模板 -│ ├── <YYYY-MM-DD>-<topic>.md # 行动项未关闭的评审 -│ └── archive/ -│ └── <ClosedReview>.md # 行动项全部关闭的评审 -└── (横切约定文档保留在 docs/ 根目录) - ├── FactoryKeyConventions.md - ├── AppSettings.md - ├── Autofac.md - ├── Configuration.md - └── PluginAssemblies.md -``` - -### 10.1 现有模式文档的迁移(已完成) - -所有 7 个模式文档已从 `docs/<PatternName>.md` 拆分迁移至 `docs/spec/` + `docs/design/`: - -| 原文件 | 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/` 根目录,不迁移。 -- 旧文件已删除。 - ---- - -## 11. 文档驱动开发流程 - -**先文档后代码**:动手写代码前,先按下表判定变更所需的文档前置条件;前置文档未达到要求状态,不进入实现阶段。人类开发者与 Agent 一体遵守。 - -### 11.1 变更类型 → 文档前置条件决策表 - -| 变更类型 | RFC | ADR | Plan | Review | 实现 PR 须同步 | -|----------|-----|-----|------|--------|----------------| -| 新增模式(生成器 + 诊断) | ✅ 必须 Accepted | ✅ RFC 衍生 | ✅ `docs/plans/` | ✅ 设计评审 | Spec 新建 + Design Doc 新建 + CHANGELOG | -| 破坏性公共 API 变更 | ✅ 必须 Accepted | ✅ | 视规模 | ✅ 设计评审 | Spec + CHANGELOG `Breaking` | -| 新增诊断 ID | ✅ 必须 Accepted | ✅ | 视规模 | 建议 | Spec + `AnalyzerReleases.Unshipped.md` + CHANGELOG | -| 非破坏性 API 新增(单模块) | ❌ | ❌ | ❌(Issue 即可) | ❌ | Design Doc + CHANGELOG | -| Bug fix | ❌ | ❌ | ❌(Issue 即可) | ❌ | CHANGELOG(如用户可见) | -| 重构(无行为变更) | ❌ | 视架构影响 | ❌ | ❌ | Design Doc(如实现结构变化) | -| 发版 | ❌ | ❌ | ❌ | ✅ Release 审查 | CHANGELOG 版本化 | - -### 11.2 新功能/新模式完整流程 - -``` -1. Roadmap 候选 → 排期 -2. 创建 RFC(Draft)→ 公开 Review -3. 设计评审 → Review 文档(Final)→ RFC Accepted → 产出 ADR → Roadmap 标记「进行中」 -4. 创建 Plan(docs/plans/,跨多 PR 时)+ 主 Issue -5. 按 Plan 里程碑实现 → 每个 PR 随代码更新 Spec + Design Doc -6. 全部合并 → RFC 标记 Implemented → 移入 rfc/archive/ -7. Plan 标记 Done → 移入 plans/archive/;可选实现回顾 Review -8. CHANGELOG 条目 → Roadmap 标记「已完成」 -``` - -### 11.3 Bug Fix 流程 - +│ ├── README.md +│ ├── _template.md +│ └── ADR-NNN-*.md +└── design/ + ├── README.md + ├── _template.md + └── <Domain>.md ``` -1. Issue(bug_report) -2. PR(修复 + 测试 + Design Doc 更新如需要) -3. 合并 → CHANGELOG 条目 -``` - -### 11.4 文档变更流程 - -``` -1. Issue(说明文档变更内容与原因) -2. PR(文档修改) -3. 合并 -``` - -### 11.5 发版流程(文档侧) - -``` -1. Release Review(docs/review/,检查 API 面 / CHANGELOG / 打包) -2. Blocker 行动项清零 -3. 按 docs/PUBLISHING.md 执行发版 -4. CHANGELOG [Unreleased] → 版本化章节 -``` - -### 11.6 Agent 工作流约定 - -AI 编码助手(Agent)在本仓库工作时,除遵守 `AGENTS.md` 的全部规则外,还须: - -| 场景 | Agent 行为 | -|------|-----------| -| 新增诊断 ID | 必须先确认是否有对应 RFC;无则提示需创建 RFC | -| 修改公共 API | 必须先确认是否有对应 RFC + ADR;无则提示需创建 RFC | -| 接到跨多 PR 的大型任务 | 先确认 `docs/plans/` 是否有对应 Plan;无则先建 Plan(经用户确认)再实现 | -| 创建 RFC | 使用 `docs/rfc/_template.md` 模板;frontmatter 从 `Draft` 开始 | -| 创建 ADR | 使用 `docs/adr/` 目录;编号取 `docs/adr/README.md` 中下一个可用编号 | -| 创建 Plan | 使用 `docs/plans/_template.md`;里程碑对齐 AGENTS.md 单模块 PR 边界 | -| 创建 Review | 使用 `docs/review/_template.md`;评审人注明为 Agent | -| RFC / Plan / Review 状态变更 | 更新 frontmatter `状态` + 日期;归档时移动文件到对应 `archive/` 并更新 README 索引 | -| Spec 变更 | 确认关联 RFC 已 Accepted;同步更新 Spec 版本号 | -| Design Doc 变更 | 随代码 PR 同步更新 | -| ROADMAP 变更 | 完成项移入「已完成(归档)」;新增项放入对应阶段 | -| CHANGELOG | 在 `[Unreleased]` 下添加条目 | -| 文档目录 | 不在 `docs/` 之外创建文档文件(`.Local/` 除外,见 AGENTS.md) | - -### 11.7 人类开发者工作流约定 - -人类开发者除遵守 `CONTRIBUTING.md` 的全部规则外,还须: - -| 场景 | 开发者行为 | -|------|-----------| -| 发起 RFC | 在 `docs/rfc/` 创建文件,提交 PR 标记 `Draft` → `Review` | -| 评审 RFC | 在 RFC PR 的 Comments 中讨论;重大设计评审落 `docs/review/` 文档;决策后更新状态为 `Accepted`/`Rejected` | -| 创建 ADR | RFC Accepted 后,在 `docs/adr/` 创建 ADR 文件,双向链接 | -| 启动大型任务 | 创建 `docs/plans/` 计划文档 + 主 Issue | -| 归档 RFC / Plan / Review | 达到归档条件后移入对应 `archive/`,更新 README 索引 | -| Spec 变更评审 | 确认 RFC + ADR 齐备后批准 Spec 更新 | -| Roadmap 维护 | 定期评审 Roadmap,更新状态 | - ---- - -## 12. 文档与 Git 的关系 - -| 文档类型 | 提交方式 | -|----------|----------| -| RFC | 独立 PR(`docs: RFC <name>`)或随实现 PR | -| ADR | 随 RFC PR 或独立 PR(`docs: ADR-NNN <title>`) | -| Spec | 随实现 PR(与代码变更同一 PR) | -| Design Doc | 随实现 PR(与代码变更同一 PR) | -| Plan | 独立 PR(`docs: plan <name>`);状态更新可随实现 PR | -| Review | 独立 PR(`docs: review <topic>`) | -| Roadmap | 随实现 PR 或独立 PR | -| CHANGELOG | 随实现 PR | - -### 12.1 PR 模板中的文档 checklist - -PR 模板包含文档变更 checklist,提交者须勾选: - -- [ ] 新增/变更公共 API → Spec 已更新 -- [ ] 新增/变更诊断 ID → Spec 已更新 + `AnalyzerReleases.Unshipped.md` 已更新 -- [ ] 新增/变更实现细节 → Design Doc 已更新 -- [ ] 新增 RFC → 使用 `_template.md` 模板 -- [ ] RFC / Plan / Review 状态变更 → frontmatter 已更新 + 归档操作已完成 -- [ ] CHANGELOG `[Unreleased]` 已添加条目 - ---- - -## 13. 文档质量检查清单 - -提交文档相关 PR 前,检查: - -- [ ] 文件位置正确(RFC 在 `docs/rfc/`,ADR 在 `docs/adr/`,Spec 在 `docs/spec/`,Design Doc 在 `docs/design/`,Plan 在 `docs/plans/`,Review 在 `docs/review/`) -- [ ] Frontmatter 格式符合标准(字段完整、日期格式 `YYYY-MM-DD`) -- [ ] 章节结构符合模板 -- [ ] 交叉链接有效(RFC ↔ ADR ↔ Spec ↔ Design Doc ↔ Plan ↔ Review 双向链接) -- [ ] `docs/README.md` 索引已更新 -- [ ] `docs/rfc/README.md` 状态板已更新(RFC 状态变更时) -- [ ] `docs/adr/README.md` 索引已更新(新增 ADR 时) -- [ ] `docs/plans/README.md` 状态板已更新(Plan 状态变更时) -- [ ] `docs/review/README.md` 索引已更新(Review 状态变更时) -- [ ] 语言:内部文档中文为主,技术术语可用英文 -- [ ] 无 AI/LLM 工具名称(遵守 `AGENTS.md` 隐私规则) -- [ ] 无私有工作区路径(遵守 `AGENTS.md` 隐私规则) - ---- - -## 14. 参考 - -- [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) -- [Semantic Versioning](https://semver.org/spec/v2.0.0.html) -- [RFC Process (Rust)](https://github.com/rust-lang/rfcs) — RFC 生命周期参考 -- [ADR (Michael Nygard)](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions) — ADR 概念起源 diff --git a/docs/README.md b/docs/README.md index 1834dd3..cd7d27d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,53 +2,37 @@ 本目录面向**维护者与贡献者**(中文为主)。面向库使用者的英文/中文指南见 [DesignPatterns.Docs](https://github.com/Skymly/DesignPatterns.Docs)(VitePress 站点:[skymly.github.io/DesignPatterns.Docs](https://skymly.github.io/DesignPatterns.Docs/))。 -> **文档体系标准**:[DOCUMENTATION.md](DOCUMENTATION.md) — 定义所有文档的类型、结构、生命周期与归档规则。人类开发者和 AI 编码助手均须遵守。 +> **文档约定**:[DOCUMENTATION.md](DOCUMENTATION.md) — ADR / Design Doc / Roadmap;任务与审查用 GitHub Issue / PR。 ## 入门 | 文档 | 说明 | |------|------| -| [DOCUMENTATION.md](DOCUMENTATION.md) | **文档体系标准**(类型、生命周期、模板、归档、工作流) | +| [DOCUMENTATION.md](DOCUMENTATION.md) | 文档约定 | | [DEVELOPMENT.md](DEVELOPMENT.md) | 环境、构建、测试、架构约定 | -| [CONTRIBUTING.md](../CONTRIBUTING.md) | 贡献流程、测试与 Roslyn 组件结构 | -| [PUBLISHING.md](PUBLISHING.md) | NuGet 预览/正式发版流程 | +| [CONTRIBUTING.md](../CONTRIBUTING.md) | 贡献流程 | +| [PUBLISHING.md](PUBLISHING.md) | NuGet 发版流程 | | [ROADMAP.md](ROADMAP.md) | 功能与技术 backlog | | [../AGENTS.md](../AGENTS.md) | AI 编码助手上下文(诊断 ID 权威登记) | -## 设计提案与决策 +## 决策与设计 | 目录 | 说明 | |------|------| -| [rfc/](rfc/README.md) | RFC — 设计提案与讨论记录([状态板](rfc/README.md)) | -| [adr/](adr/README.md) | ADR — 架构决策记录([索引](adr/README.md)) | - -## 计划与评审 - -| 目录 | 说明 | -|------|------| -| [plans/](plans/README.md) | Plan — 大型任务计划(跨多 PR;[状态板](plans/README.md)) | -| [review/](review/README.md) | Review — 评审记录(设计/实现/发版/回顾;[索引](review/README.md)) | - -## 规范与设计文档 - -| 目录 | 说明 | -|------|------| -| [spec/](spec/README.md) | Spec — 模式稳定契约(API 面、诊断 ID、不变量) | -| [design/](design/README.md) | Design Doc — 实现细节、设计权衡、已知局限 | - -> 所有模式文档已从 `docs/<PatternName>.md` 拆分迁移至 `spec/` + `design/`,见 [spec/README.md](spec/README.md)。 +| [adr/](adr/README.md) | ADR — 架构决策记录 | +| [design/](design/README.md) | Design Doc — 每域 API / 诊断 / 实现 | ## 模式索引 -| 模式 | 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) | +| 模式 | Design Doc | +|------|------------| +| Strategy | [design/Strategy.md](design/Strategy.md) | +| Chain of Responsibility | [design/ChainOfResponsibility.md](design/ChainOfResponsibility.md) | +| Composite | [design/Composite.md](design/Composite.md) | +| Factory Registry | [design/FactoryRegistry.md](design/FactoryRegistry.md) | +| Decorator | [design/Decorator.md](design/Decorator.md) | +| Event Aggregator | [design/EventAggregator.md](design/EventAggregator.md) | +| State Transition Table | [design/StateTransitionTable.md](design/StateTransitionTable.md) | ## 横切约定 @@ -75,4 +59,4 @@ | 维护者 / 深度 API | 本目录 `docs/` | 中文设计说明 | | 跨工具 AI 约束 | 根目录 `AGENTS.md` | 中文 | -修改诊断 ID、CodeFix 或公共 API 时,同步更新:`DiagnosticIds.cs`、`DesignPatterns.Docs` 的 [diagnostics](https://github.com/Skymly/DesignPatterns.Docs/blob/main/docs/diagnostics.md) 页,以及(若影响 key 约定)本目录 [FactoryKeyConventions.md](FactoryKeyConventions.md)。 +修改诊断 ID、CodeFix 或公共 API 时,同步更新:`DiagnosticIds.cs`、对应 Design Doc、`DesignPatterns.Docs` 的 [diagnostics](https://github.com/Skymly/DesignPatterns.Docs/blob/main/docs/diagnostics.md) 页,以及(若影响 key 约定)本目录 [FactoryKeyConventions.md](FactoryKeyConventions.md)。 diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index ef634ab..10fe97e 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -20,7 +20,7 @@ | Event Aggregator | `{Event}EventHandlerRegistry` | | State | `{StateEnum}TransitionTable`、partial `{Holder}` 便捷方法 | -新增生成器必须沿用此命名风格(详见 [Decorator.md](design/Decorator.md))。诊断 ID 续接现有区段,下一个可用 ID 为 **DP067**,DP067–DP071 已由 [Singleton 生命周期诊断 RFC](rfc/SingletonLifecycleDiagnostics.md) 预留(DP066 为 Singleton 工厂委托 captive dependency,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 为 **DP067**,DP067–DP071 已由 [ADR-008](adr/ADR-008-singleton-lifecycle-diagnostics.md) 预留(DP066 为 Singleton 工厂委托 captive dependency,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+ 增强项,登记后不提前占用,实现时按序领取): @@ -99,20 +99,20 @@ 候选池(仅登记,未排期):Observer / 轻量 pub-sub 扩展、Builder 生成器、ObjectPool(探索源生成池化策略)、Resilience primitive(探索编译期策略组合)、Command 路由(探索与 MediatR 的差异点)。**范围包含 GoF 但不局限于 GoF**:并发模式、反应式模式、函数式模式等候选同样欢迎。**未通过准入前不实现。** -长期探索候选(高潜力、高复杂度,需 RFC 评审): +长期探索候选(高潜力、高复杂度,需 Issue + ADR 评审): | 候选 | 说明 | 探索价值 | |------|------|----------| | ~~State 层级状态机~~ | ~~`[StateMachine(..., Hierarchical = true)]` 支持嵌套状态 + 通配转换,生成器展平为快表~~ — **已在 v3 实现**(v3.1 运行时 `IStateHierarchy`、v3.2 `[StateParent]` + 展平 + DP056–DP059、v3.3 LCA + action 链合成、v3.4 DI + 示例 + 文档) | ⭐⭐⭐ 与 Stateless 重叠但展示「编译期展平层级」技术 | | ~~Composite 并行遍历~~ | ~~`TraverseParallel` / `TraverseParallelAsync` / `TraverseForestParallel` / `TraverseForestParallelAsync` + `MaxDegreeOfParallelism` + `MaxParallelDepth`~~ — **Phase 1 已实现**(BFS 同层并行 / DFS 子节点并行递归 + 深度回退 / `AggregateException` / `ConfigureAwait(false)` / `#if` TFM 分裂) | ⭐⭐ 大树场景实用;AOT 友好并行调度 | -| Composite 懒加载 | `[CompositePart(..., LazyChildren = true)]` + `AssembleAsync` + `ICompositeLazyNode` | ⭐⭐ 大树按需展开;Phase 3 独立 RFC | +| Composite 懒加载 | `[CompositePart(..., LazyChildren = true)]` + `AssembleAsync` + `ICompositeLazyNode` | ⭐⭐ 大树按需展开;独立 Issue + ADR | | ~~Composite 树 schema 校验~~ | ~~编译期校验 max depth / parent-child 类型兼容性 / 节点计数~~ — **已实现**(`[CompositeSchema(MaxDepth, MaxNodes)]` 契约级约束 + `[CompositePart(AllowedChildTypes)]` 节点级约束 + DP063–DP065) | ⭐⭐ 结构错误编译期捕获 | | Decorator 组合 / 嵌套 | `DecoratorStackBuilder.Compose(otherStack)`,Analyzer 校验栈间类型兼容 | ⭐⭐ 可复用装饰器组合 | | MSDI keyed services(.NET 8+) | 生成 keyed registration 代码(`#if NET8_0_OR_GREATER`),与 Autofac keyed 对称 | ⭐⭐ 补齐 MSDI keyed 缺口 | | DI 健康检查 + 生命周期校验 | 生成 `IHealthCheck` 校验注册表键可解析;Analyzer 警告无效 lifetime 组合(Singleton registry + Transient impl) | ⭐⭐ 生产场景刚需 | -| Singleton 生命周期诊断 | Analyzer 检测 singleton 捕获 scoped/transient 引用;async 初始化支持 | ⭐⭐ 防 DI 反模式 | [~] [RFC](rfc/SingletonLifecycleDiagnostics.md) / [Plan](plans/SingletonLifecycleDiagnostics.md) | +| Singleton 生命周期诊断 | Analyzer 检测 singleton 捕获 scoped/transient 引用;async 初始化支持。[~] P1 已完成(DP066);P2/P3 见 [ADR-008](adr/ADR-008-singleton-lifecycle-diagnostics.md) | ⭐⭐ 防 DI 反模式 | -State 转换表 v1 已于 0.1.0-preview4 发布;v2(guard 委托、DI 集成、DP036 字面量边校验)已实现,见 [StateTransitionTable.md](design/StateTransitionTable.md) 与 [rfc/StateTransitionTable.md](rfc/StateTransitionTable.md)。EventAggregator 联动示例仍为候选。 +State 转换表 v1 已于 0.1.0-preview4 发布;v2(guard 委托、DI 集成、DP036 字面量边校验)已实现,见 [StateTransitionTable.md](design/StateTransitionTable.md)。EventAggregator 联动示例仍为候选。 --- @@ -205,6 +205,7 @@ State 转换表 v1 已于 0.1.0-preview4 发布;v2(guard 委托、DI 集成 - F4+ Singleton captive dependency 诊断:`CaptiveDependencyAnalyzer`(DP062 Warning)— 编译期扫描 MSDI 注册调用构建 Type→Lifetime 映射,检查 Singleton 实现构造函数参数是否依赖 Scoped/Transient 服务。 - F4+ DP062 Phase 2:扩展覆盖生成器 `RegisterDi` 调用 — 从 `[RegisterStrategy]`/`[RegisterFactory]`/`[RegisterEventHandler]`/`[Decorator]`/`[CompositePart]` 特性提取实现类型,应用 `implementationLifetime`。 - F4+ DP062 过度注册修复:按模式分组特性标注类型(`RegistrationCategory`),`RegisterDi` containing type 名匹配正确类别,避免跨模式 lifetime 污染。 -- F5 Composite 并行遍历 Phase 1:`TraverseParallel` / `TraverseParallelAsync` / `TraverseForestParallel` / `TraverseForestParallelAsync` + `MaxDegreeOfParallelism` + `MaxParallelDepth` + `AggregateException` + `ConfigureAwait(false)` + `#if` TFM 分裂。设计 RFC:[docs/rfc/CompositeParallelTraversal.md](rfc/CompositeParallelTraversal.md)。 +- F5 Composite 并行遍历 Phase 1:`TraverseParallel` / `TraverseParallelAsync` / `TraverseForestParallel` / `TraverseForestParallelAsync` + `MaxDegreeOfParallelism` + `MaxParallelDepth` + `AggregateException` + `ConfigureAwait(false)` + `#if` TFM 分裂。见 [ADR-006](adr/ADR-006-composite-parallel-traversal.md)。 - 发版 `0.2.2`:含 DP062 Singleton captive dependency 诊断(tag `v0.2.2`,稳定版)。 -- F5+ Composite 树 schema 校验:`[CompositeSchema(MaxDepth, MaxNodes)]` 契约级约束 + `[CompositePart(AllowedChildTypes)]` 节点级约束 + DP063(max depth exceeded Warning)/ DP064(child type not allowed Error)/ DP065(node count exceeded Warning)。设计 RFC:[docs/rfc/CompositeTreeSchemaValidation.md](rfc/CompositeTreeSchemaValidation.md)。 +- F5+ Composite 树 schema 校验:`[CompositeSchema(MaxDepth, MaxNodes)]` 契约级约束 + `[CompositePart(AllowedChildTypes)]` 节点级约束 + DP063(max depth exceeded Warning)/ DP064(child type not allowed Error)/ DP065(node count exceeded Warning)。见 [ADR-007](adr/ADR-007-composite-tree-schema-validation.md)。 +- 文档体系精简:移除 RFC / Spec / Plan / Review 仓内类型;Spec 合入 Design Doc;任务与审查改用 GitHub Issue / PR。见 [DOCUMENTATION.md](DOCUMENTATION.md)。 diff --git a/docs/adr/ADR-001-primitives-over-frameworks.md b/docs/adr/ADR-001-primitives-over-frameworks.md index bfe556b..4569f5b 100644 --- a/docs/adr/ADR-001-primitives-over-frameworks.md +++ b/docs/adr/ADR-001-primitives-over-frameworks.md @@ -4,7 +4,7 @@ |------|-----| | **状态** | Accepted | | **日期** | 2026-06-14 | -| **关联 RFC** | 无 — 项目创立时的核心设计原则 | +| **关联 Issue** | —(项目创立时直接决策) | ## 背景 diff --git a/docs/adr/ADR-002-roslyn-incremental-source-generators.md b/docs/adr/ADR-002-roslyn-incremental-source-generators.md index e9b073a..825ac5b 100644 --- a/docs/adr/ADR-002-roslyn-incremental-source-generators.md +++ b/docs/adr/ADR-002-roslyn-incremental-source-generators.md @@ -4,7 +4,7 @@ |------|-----| | **状态** | Accepted | | **日期** | 2026-06-14 | -| **关联 RFC** | 无 — 项目创立时的技术选型 | +| **关联 Issue** | —(项目创立时直接决策) | ## 背景 diff --git a/docs/adr/ADR-003-dual-tfm-netstandard20-net80.md b/docs/adr/ADR-003-dual-tfm-netstandard20-net80.md index a995668..621be58 100644 --- a/docs/adr/ADR-003-dual-tfm-netstandard20-net80.md +++ b/docs/adr/ADR-003-dual-tfm-netstandard20-net80.md @@ -4,7 +4,7 @@ |------|-----| | **状态** | Accepted | | **日期** | 2026-06-14 | -| **关联 RFC** | 无 — 项目创立时的兼容性决策 | +| **关联 Issue** | —(项目创立时直接决策) | ## 背景 diff --git a/docs/adr/ADR-004-core-does-not-reference-msdi.md b/docs/adr/ADR-004-core-does-not-reference-msdi.md index 4ad677e..e1eaad4 100644 --- a/docs/adr/ADR-004-core-does-not-reference-msdi.md +++ b/docs/adr/ADR-004-core-does-not-reference-msdi.md @@ -4,7 +4,7 @@ |------|-----| | **状态** | Accepted | | **日期** | 2026-06-14 | -| **关联 RFC** | 无 — 项目创立时的架构边界决策 | +| **关联 Issue** | —(项目创立时直接决策) | ## 背景 diff --git a/docs/adr/ADR-005-state-transition-table.md b/docs/adr/ADR-005-state-transition-table.md index 1cff716..6dd711c 100644 --- a/docs/adr/ADR-005-state-transition-table.md +++ b/docs/adr/ADR-005-state-transition-table.md @@ -4,7 +4,7 @@ |------|-----| | **状态** | Accepted | | **日期** | 2026-06-14 | -| **关联 RFC** | [docs/rfc/archive/StateTransitionTable.md](../rfc/archive/StateTransitionTable.md)、[docs/rfc/archive/HierarchicalStateMachine.md](../rfc/archive/HierarchicalStateMachine.md) | +| **关联 Issue** | —(历史 RFC 已随文档体系精简移除) | ## 背景 @@ -34,5 +34,4 @@ DesignPatterns 缺少有限状态机 primitive。现有库(Stateless、Akka.NE ## 参考 -- [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 e18689c..ba315e5 100644 --- a/docs/adr/ADR-006-composite-parallel-traversal.md +++ b/docs/adr/ADR-006-composite-parallel-traversal.md @@ -4,7 +4,7 @@ |------|-----| | **状态** | Accepted | | **日期** | 2026-06-30 | -| **关联 RFC** | [docs/rfc/archive/CompositeParallelTraversal.md](../rfc/archive/CompositeParallelTraversal.md) | +| **关联 Issue** | —(历史 RFC 已随文档体系精简移除) | ## 背景 @@ -34,5 +34,4 @@ Composite 模式已有串行遍历(`Traverse` / `TraverseAsync`),但大树 ## 参考 -- [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 00de761..29797ca 100644 --- a/docs/adr/ADR-007-composite-tree-schema-validation.md +++ b/docs/adr/ADR-007-composite-tree-schema-validation.md @@ -4,7 +4,7 @@ |------|-----| | **状态** | Accepted | | **日期** | 2026-07-07 | -| **关联 RFC** | [docs/rfc/archive/CompositeTreeSchemaValidation.md](../rfc/archive/CompositeTreeSchemaValidation.md) | +| **关联 Issue** | —(历史 RFC 已随文档体系精简移除) | ## 背景 @@ -34,5 +34,4 @@ ## 参考 -- [docs/spec/Composite.md](../spec/Composite.md) - [docs/design/Composite.md](../design/Composite.md) diff --git a/docs/adr/ADR-008-singleton-lifecycle-diagnostics.md b/docs/adr/ADR-008-singleton-lifecycle-diagnostics.md index 72f4c3b..8f02af0 100644 --- a/docs/adr/ADR-008-singleton-lifecycle-diagnostics.md +++ b/docs/adr/ADR-008-singleton-lifecycle-diagnostics.md @@ -4,11 +4,11 @@ |------|-----| | **状态** | Accepted | | **日期** | 2026-07-08 | -| **关联 RFC** | [docs/rfc/SingletonLifecycleDiagnostics.md](../rfc/SingletonLifecycleDiagnostics.md) | +| **关联 Issue** | —(历史 RFC / Plan / Review 已随文档体系精简移除;决策以本文为准) | ## 背景 -DP062 已覆盖 MSDI 构造函数注入的 Singleton captive dependency,但 Autofac 注册与 `AddSingleton` 工厂委托不在检测范围内;`[GenerateSingleton]` 缺少 async 初始化路径,与 DI 容器单例混用时无警告;DEVELOPMENT.md 声明的「静态可变单例提示」尚无对应 Analyzer。设计评审([2026-07-08](../review/2026-07-08-singleton-lifecycle-diagnostics-design.md))确认了三阶段扩展方案并修正两处初稿设计错误。 +DP062 已覆盖 MSDI 构造函数注入的 Singleton captive dependency,但 Autofac 注册与 `AddSingleton` 工厂委托不在检测范围内;`[GenerateSingleton]` 缺少 async 初始化路径,与 DI 容器单例混用时无警告;DEVELOPMENT.md 声明的「静态可变单例提示」尚无对应 Analyzer。设计评审确认了三阶段扩展方案并修正两处初稿设计错误(async 不可用属性 getter、不存在的 MSDI 字段注入)。 ## 决策 @@ -38,5 +38,4 @@ DP062 已覆盖 MSDI 构造函数注入的 Singleton captive dependency,但 Au - [ADR-001 Primitives over frameworks](ADR-001-primitives-over-frameworks.md) - [ADR-004 Core does not reference MSDI](ADR-004-core-does-not-reference-msdi.md) -- [设计评审记录](../review/2026-07-08-singleton-lifecycle-diagnostics-design.md) -- [实现计划](../plans/SingletonLifecycleDiagnostics.md) +- [docs/ROADMAP.md](../ROADMAP.md)(Singleton 生命周期诊断条目) diff --git a/docs/adr/README.md b/docs/adr/README.md index a7af4ee..ab2f1a3 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -1,23 +1,23 @@ # ADR 索引 -架构决策记录(Architecture Decision Record)。ADR 是不可变卡片,记录最终决策;讨论在 RFC 中完成。 +架构决策记录(Architecture Decision Record)。ADR 是不可变卡片,记录最终决策;讨论与任务跟踪在 GitHub Issue / PR 中完成。 -- **格式与生命周期**:见 [DOCUMENTATION.md](../DOCUMENTATION.md#3-adr--architecture-decision-record) +- **格式与规则**:见 [DOCUMENTATION.md](../DOCUMENTATION.md#2-adr) - **模板**:[_template.md](_template.md) - **编号规则**:从 ADR-001 开始,零填充三位,**不复用编号** ## 决策列表 -| ADR | 标题 | 状态 | 日期 | 关联 RFC | -|-----|------|------|------|----------| +| ADR | 标题 | 状态 | 日期 | 关联 Issue | +|-----|------|------|------|------------| | [ADR-001](ADR-001-primitives-over-frameworks.md) | Primitives over frameworks | Accepted | 2026-06-14 | — | | [ADR-002](ADR-002-roslyn-incremental-source-generators.md) | Roslyn incremental source generators | Accepted | 2026-06-14 | — | | [ADR-003](ADR-003-dual-tfm-netstandard20-net80.md) | Dual TFM — netstandard2.0 + net8.0 | Accepted | 2026-06-14 | — | | [ADR-004](ADR-004-core-does-not-reference-msdi.md) | Core does not reference MSDI | Accepted | 2026-06-14 | — | -| [ADR-005](ADR-005-state-transition-table.md) | State transition table | Accepted | 2026-06-14 | StateTransitionTable, HierarchicalStateMachine | -| [ADR-006](ADR-006-composite-parallel-traversal.md) | Composite parallel traversal | Accepted | 2026-06-30 | CompositeParallelTraversal | -| [ADR-007](ADR-007-composite-tree-schema-validation.md) | Composite tree schema validation | Accepted | 2026-07-07 | CompositeTreeSchemaValidation | -| [ADR-008](ADR-008-singleton-lifecycle-diagnostics.md) | Singleton lifecycle diagnostics | Accepted | 2026-07-08 | SingletonLifecycleDiagnostics | +| [ADR-005](ADR-005-state-transition-table.md) | State transition table | Accepted | 2026-06-14 | — | +| [ADR-006](ADR-006-composite-parallel-traversal.md) | Composite parallel traversal | Accepted | 2026-06-30 | — | +| [ADR-007](ADR-007-composite-tree-schema-validation.md) | Composite tree schema validation | Accepted | 2026-07-07 | — | +| [ADR-008](ADR-008-singleton-lifecycle-diagnostics.md) | Singleton lifecycle diagnostics | Accepted | 2026-07-08 | — | ## 下一个可用编号 diff --git a/docs/adr/_template.md b/docs/adr/_template.md index 60b4331..fc3e75e 100644 --- a/docs/adr/_template.md +++ b/docs/adr/_template.md @@ -4,7 +4,7 @@ |------|-----| | **状态** | Accepted | | **日期** | YYYY-MM-DD | -| **关联 RFC** | [docs/rfc/XXX.md](../rfc/XXX.md)(或「无 — 直接决策」) | +| **关联 Issue** | #XXX(或「无 — 直接决策」) | ## 背景 @@ -20,4 +20,4 @@ ## 参考 -- 相关 ADR、文档、外部链接 +- 相关 ADR、Design Doc、外部链接 diff --git a/docs/design/ChainOfResponsibility.md b/docs/design/ChainOfResponsibility.md index 424e94f..75d04bc 100644 --- a/docs/design/ChainOfResponsibility.md +++ b/docs/design/ChainOfResponsibility.md @@ -1,7 +1,6 @@ # Design Doc: Chain of Responsibility -> **关联 Spec**:[docs/spec/ChainOfResponsibility.md](../spec/ChainOfResponsibility.md) -> **关联 RFC**:[docs/rfc/XXX.md](../rfc/XXX.md)(如有) +> **版本**:v0.2.2(与 NuGet 包版本对齐) > **关联 ADR**:ADR-XXX(如有) ## 概述 @@ -15,6 +14,93 @@ 3. **异步一等**:`ValueTask` + `CancellationToken` 4. 不依赖 DI;handler 由调用方手动组装 +## API 面 + +### 运行时接口 + +运行时类型位于 `DesignPatterns/Behavioral/`。 + +| 类型 | 说明 | +|------|------| +| `HandlerDelegate<TContext>` | 调用链中下一阶段的委托 | +| `IHandler<TContext>` | 单个 handler:`InvokeAsync(context, next, ct)` | +| `HandlerPipelineBuilder<TContext>` | `Use(handler)` / `Use(delegate)`,`Build()` | +| `HandlerPipeline<TContext>` | 不可变管道,`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 中显示名为 `"<delegate>"`。 + +`HandlerPipelineTrace.WasShortCircuited` 为「是否存在任一步为 `ShortCircuited`」;终端 handler 不调用 `next` 时也会为 `true`,不宜单独作为「业务短路」判据。 + +### 特性(Attribute) + +```csharp +// 泛型(推荐,C# 11+ / net8.0) +[HandlerOrder<RequestContext>(10)] +public sealed class LoggingHandler : IHandler<RequestContext> { } + +// 非泛型(netstandard2.0) +[HandlerOrder(10, typeof(RequestContext))] +public sealed class LoggingHandler : IHandler<RequestContext> { } +``` + +数值越小越先执行(与手动 `Use` 注册顺序一致)。 + +同一 handler 类可标注多个 `[HandlerOrder<...>]`(`AllowMultiple = true`),分别加入不同 `TContext` 的生成管道,例如共享日志 handler 同时实现 `IHandler<RequestContext>` 与 `IHandler<AuditContext>`。同一 context 下重复的 Order 仍报 **DP005**。 + +### 生成器产出 + +对每种 `TContext` 生成 `{Context}HandlerPipeline`: + +```csharp +public static partial class RequestContextHandlerPipeline +{ + public static HandlerPipeline<RequestContext> Instance { get; } +} +``` + +引用 `DesignPatterns.Extensions.DependencyInjection` 后,`{Context}HandlerPipeline` 额外生成: + +```csharp +RequestContextHandlerPipeline.RegisterDi(services); +var pipeline = provider.GetRequiredService<HandlerPipeline<RequestContext>>(); +``` + +`Create(IServiceProvider sp)` 按 `[HandlerOrder]` 顺序 `GetRequiredService` 各 handler。Singleton 管道在首次构建时固定 handler 实例;需要每次解析新管道时使用 `registryLifetime: ServiceLifetime.Transient`。 + +手动注册:`services.AddHandlerPipeline<TContext>(builder => ...)`(扩展包)。 + +## 诊断 + +| ID | 级别 | 触发条件 | 消息格式 | +|----|------|----------|----------| +| DP005 | Error | 同一 context 下重复的 Order | ... | +| DP008 | Error | 标注 `[HandlerOrder]` 但未实现 `IHandler<TContext>` | ... | +| DP009 | Error | 标注 `[HandlerOrder]` 的 handler 缺少 public 无参构造 | ... | +| DP024 | Info | Analyzer:实现了 `IHandler<TContext>` 但未加 `[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) + ## 实现概览 ### 运行时 @@ -51,7 +137,7 @@ if (trace.Steps.Any(s => s.Status == HandlerPipelineStepStatus.NotReached)) **`{Context}HandlerPipeline` 生成**:对每种 `TContext` 生成 `public static partial class {Context}HandlerPipeline`,暴露 `Instance`(`HandlerPipeline<TContext>`);引用 DI 扩展包后额外生成 `RegisterDi(services)` 与 `Create(IServiceProvider sp)`。 -### 诊断 +### 诊断检测细节 | ID | 说明 | |----|------| diff --git a/docs/design/Composite.md b/docs/design/Composite.md index 4781c78..85c39c2 100644 --- a/docs/design/Composite.md +++ b/docs/design/Composite.md @@ -1,6 +1,6 @@ # Design Doc: Composite -> **关联 Spec**:[docs/spec/Composite.md](../spec/Composite.md) +> **版本**:v0.2.2(与 NuGet 包版本对齐) > **关联 ADR**:[ADR-006](../adr/ADR-006-composite-parallel-traversal.md)、[ADR-007](../adr/ADR-007-composite-tree-schema-validation.md) ## 概述 @@ -14,6 +14,140 @@ Composite 模式将对象组织成树形结构,以统一方式对待单个对 3. **Catalog 装配**:`[CompositePart]` 生成 flat catalog,`BuildRoot()` 组装单根树,`BuildForest()` 组装多根森林 4. 不依赖 DI;树由编译期定义、运行时一次性装配 +## API 面 + +### 运行时接口 + +命名空间:`DesignPatterns.Structural` + +| 类型 | 签名 | 说明 | +|------|------|------| +| `ICompositeNode<TSelf>` | `IReadOnlyList<TSelf> Children { get; }` where `TSelf : ICompositeNode<TSelf>` | 节点契约:通过 `Children` 区分叶子与分支,空列表为叶子 | +| `ICompositeBuildable<TNode>` | `void SetChildren(IReadOnlyList<TNode> children)` where `TNode : ICompositeNode<TNode>` | 装配时接收子节点;实现应在装配后冻结 children,后续调用视为无效 | +| `CompositeTraverser` | `static void Traverse<TNode>(TNode root, Action<TNode, int, int> visitor, CompositeTraversalOptions<TNode>? options = null)` | 同步遍历单根树(visitor: node, depth, siblingIndex) | +| | `static void TraverseForest<TNode>(IReadOnlyList<TNode> roots, Action<TNode, int, int> visitor, CompositeTraversalOptions<TNode>? options = null)` | 同步遍历森林(多根树) | +| | `static ValueTask TraverseAsync<TNode>(TNode root, Func<TNode, int, int, CancellationToken, ValueTask> visitor, CompositeTraversalOptions<TNode>? options = null, CancellationToken cancellationToken = default)` | 异步遍历单根树 | +| | `static ValueTask TraverseForestAsync<TNode>(IReadOnlyList<TNode> roots, Func<TNode, int, int, CancellationToken, ValueTask> visitor, CompositeTraversalOptions<TNode>? options = null, CancellationToken cancellationToken = default)` | 异步遍历森林 | +| | `static void TraverseParallel<TNode>(TNode root, Action<TNode, int, int> visitor, CompositeTraversalOptions<TNode>? options = null)` | 并行遍历单根树(访问顺序非确定) | +| | `static void TraverseForestParallel<TNode>(IReadOnlyList<TNode> roots, Action<TNode, int, int> visitor, CompositeTraversalOptions<TNode>? options = null)` | 并行遍历森林(根间串行,子树内并行) | +| | `static ValueTask TraverseParallelAsync<TNode>(TNode root, Func<TNode, int, int, CancellationToken, ValueTask> visitor, CompositeTraversalOptions<TNode>? options = null, CancellationToken cancellationToken = default)` | 异步并行遍历单根树 | +| | `static ValueTask TraverseForestParallelAsync<TNode>(IReadOnlyList<TNode> roots, Func<TNode, int, int, CancellationToken, ValueTask> visitor, CompositeTraversalOptions<TNode>? options = null, CancellationToken cancellationToken = default)` | 异步并行遍历森林 | +| `CompositeTraversalOptions<TNode>` | `CompositeTraversalOrder Order { get; set; }`(默认 `DepthFirstPreOrder`) | 遍历顺序 | +| | `int? MaxDepth { get; set; }`(`null` = 无限制) | 最大访问深度(0 = 仅根) | +| | `bool VisitLeavesOnly { get; set; }` | 仅访问叶子节点 | +| | `Func<TNode, bool>? ShouldSkipSubtree { get; set; }` | 返回 `true` 时跳过该子树 | +| | `int? MaxDegreeOfParallelism { get; set; }`(`null` = `Environment.ProcessorCount`) | 并行度上限 | +| | `int MaxParallelDepth { get; set; }`(默认 32) | 并行递归深度上限,超过后回退串行 | +| `CompositeTreeBuilder<TNode>` | `CompositeTreeBuilder<TNode> Leaf(TNode node)` | 添加叶子节点 | +| | `CompositeTreeBuilder<TNode> Branch(TNode node, Action<CompositeTreeBuilder<TNode>> configure)` | 添加分支节点(嵌套 builder 配置子节点) | +| | `TNode Build()` | 构建单根树(要求恰好一个顶层节点) | +| `CompositeCatalogEntry<TNode>` | `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<TNode>(IReadOnlyList<CompositeCatalogEntry<TNode>> entries)` where `TNode : class, ICompositeNode<TNode>` | 从 catalog 装配单根树(`Activator.CreateInstance`) | +| | `static TNode Assemble<TNode>(IReadOnlyList<CompositeCatalogEntry<TNode>> entries, IServiceProvider serviceProvider)` | 从 catalog 装配单根树(DI 解析节点) | +| | `static IReadOnlyList<TNode> AssembleForest<TNode>(IReadOnlyList<CompositeCatalogEntry<TNode>> entries)` | 从 catalog 装配多根森林(`Activator.CreateInstance`) | +| | `static IReadOnlyList<TNode> AssembleForest<TNode>(IReadOnlyList<CompositeCatalogEntry<TNode>> 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<TContract>`(泛型,C# 11+ / net8.0) + +```csharp +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] +public sealed class CompositePartAttribute<TContract> : 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 | 级别 | 触发条件 | 消息格式 | +|----|------|----------|----------| +| 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<TContract>` | `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<TContract>` 要求**:catalog 装配的节点类型必须实现 `ICompositeBuildable<TContract>`,`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<TContract>` 仅在 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` 分裂) + ## 实现概览 ### 运行时 @@ -117,7 +251,7 @@ public sealed class SettingsMenu : IMenuNode, ICompositeBuildable<IMenuNode> { } 所有 schema 约束均为 opt-in — 未标注 `[CompositeSchema]` 的契约行为不变。校验在编译期完成,运行时零开销。 -### 诊断 +### 诊断检测细节 诊断检测逻辑与报告位置: @@ -186,7 +320,6 @@ Composite 关注树形结构与统一遍历;Chain 关注线性有序执行与 - 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()` 抛错) diff --git a/docs/design/Decorator.md b/docs/design/Decorator.md index 388ea75..e9b1d5f 100644 --- a/docs/design/Decorator.md +++ b/docs/design/Decorator.md @@ -1,7 +1,6 @@ # Design Doc: Decorator -> **关联 Spec**:[docs/spec/Decorator.md](../spec/Decorator.md) -> **关联 RFC**:[docs/rfc/XXX.md](../rfc/XXX.md)(如有) +> **版本**:v0.2.2(与 NuGet 包版本对齐) > **关联 ADR**:ADR-XXX(如有) ## 概述 @@ -15,6 +14,103 @@ Decorator(装饰器)在**不修改核心类型**的前提下,为同一契 3. **零 DI 依赖**:手动组装;DI 扩展留 P3 4. **编译期胶水**:生成 `{Contract}DecoratorStack.Build(core)` 与 `{Contract}DecoratorOrder` 常量,不生成装饰方法体 +## API 面 + +### 运行时接口 + +运行时类型位于 `DesignPatterns/Structural/`。 + +| 类型 | 说明 | +|------|------| +| `IDecorator<TService>` | `TService Decorate(TService inner)` | +| `IDecoratorOf<TService>` | 可选标记接口(生成器不强制) | +| `DecoratorStackBuilder<TService>` | `Add<TDecorator>()` / `Add(instance)` / `Add(..., Func<bool>)` / `Build(core)` | + +`DecoratorStackBuilder<TService>` 方法签名: + +| 方法 | 说明 | +|------|------| +| `Add<TDecorator>()` where `TDecorator : TService, IDecorator<TService>, new()` | 注册一个装饰器类型(无参构造) | +| `Add(TService decorator)` | 注册一个装饰器实例(须实现 `IDecorator<TService>`) | +| `Add<TDecorator>(Func<bool> predicate)` | 注册一个带运行时谓词的装饰器类型;谓词在 `Build` 时求值,为 `false` 时跳过该装饰器 | +| `Add(TService decorator, Func<bool> predicate)` | 注册一个带运行时谓词的装饰器实例 | +| `Build(TService core)` | 按 `Add` 顺序(`Order` 越小越靠外)包装 `core`,返回最外层 `TService` | + +### 特性(Attribute) + +```csharp +// 泛型(推荐,C# 11+ / net8.0) +[Decorator<IPaymentService>(10)] +public sealed class LoggingPaymentDecorator : IPaymentService, IDecorator<IPaymentService> { ... } + +// 非泛型(netstandard2.0) +[Decorator(10, typeof(IPaymentService))] +public sealed class LoggingPaymentDecorator : IPaymentService, IDecorator<IPaymentService> { ... } +``` + +`[Decorator]` / `[Decorator<TService>]` 构造函数签名: + +| 特性 | 构造函数 | 说明 | +|------|----------|------| +| `DecoratorAttribute<TService>` | `(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<IPaymentService>(PaymentServiceDecoratorOrder.LoggingPaymentDecorator)] +public sealed class LoggingPaymentDecorator : IPaymentService, IDecorator<IPaymentService> { ... } +``` + +## 诊断 + +| 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<TService>` | `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<bool>`)仅适用于手动 `DecoratorStackBuilder` 组装。 + +### 兼容基线 + +- netstandard2.0 + net8.0(两者均须可用并随包分发) +- Roslyn 组件 4.8.0(`Microsoft.CodeAnalysis.CSharp` / Workspaces;Analyzers 3.3.4) + ## 实现概览 ### 运行时 @@ -66,7 +162,7 @@ var service = new DecoratorStackBuilder<IPaymentService>() public sealed class LoggingPaymentDecorator : IPaymentService, IDecorator<IPaymentService> { ... } ``` -### 诊断 +### 诊断检测细节 | ID | 说明 | |----|------| @@ -122,7 +218,5 @@ Decorator 聚焦「同一契约的横切增强」,不替代 ASP.NET 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 index f7d71f4..8920898 100644 --- a/docs/design/EventAggregator.md +++ b/docs/design/EventAggregator.md @@ -1,7 +1,6 @@ # Design Doc: Event Aggregator -> **关联 Spec**:[docs/spec/EventAggregator.md](../spec/EventAggregator.md) -> **关联 RFC**:[docs/rfc/XXX.md](../rfc/XXX.md)(如有) +> **版本**:v0.2.2 > **关联 ADR**:ADR-XXX(如有) ## 概述 @@ -15,6 +14,128 @@ Event Aggregator 提供进程内、轻量的 **发布/订阅(pub/sub)** 机 3. 线程安全:订阅表在锁下修改,发布时对处理器列表做快照 4. 不侵入 Core 的 DI 依赖;可选 `AddEventAggregator` 扩展 +## API 面 + +### 运行时接口 + +| 类型 | 职责 | +|------|------| +| `IEventHandler<TEvent>` | 处理指定事件类型 | +| `IEventAggregator` | 订阅、取消订阅、发布 | +| `EventAggregator` | 默认实现 | + +#### 事件处理器 + +```csharp +public interface IEventHandler<in TEvent> +{ + ValueTask HandleAsync(TEvent evt, CancellationToken cancellationToken = default); +} +``` + +任意类实现该接口即可作为处理器;**不要求**继承基类或注册特性。 + +#### 聚合器 + +```csharp +public interface IEventAggregator +{ + void Subscribe<TEvent>(IEventHandler<TEvent> handler); + void Unsubscribe<TEvent>(IEventHandler<TEvent> handler); + ValueTask PublishAsync<TEvent>(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<FooEvent>]` | 泛型 | `#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<TEvent>` | handler 有公共无参构造 | +| `RegisterDi(IServiceCollection, ServiceLifetime)` | DI:注册 handler 实现到容器 | `DesignPatterns_EnableDiIntegration=true` | +| `SubscribeAll(IEventAggregator, IServiceProvider)` | DI:从容器解析 handler + `Subscribe<TEvent>` | `DesignPatterns_EnableDiIntegration=true` | + +**静态路径**仅包含有公共无参构造的 handler;**DI 路径**包含全部有效 handler。这与其他生成器的无参构造约束一致(DP007/DP009/DP014/DP019/DP022 同源)。 + +#### 静态路径(无 DI 依赖) + +```csharp +public record OrderPlacedEvent(string OrderId); + +[RegisterEventHandler<OrderPlacedEvent>] +public sealed class LogOrderHandler : IEventHandler<OrderPlacedEvent> +{ + 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<IEventAggregator>(); +OrderPlacedEventHandlerRegistry.SubscribeAll(aggregator, provider); +``` + +`RegisterDi` 默认 `implementationLifetime: ServiceLifetime.Transient`(每次解析新实例,因 handler 通常无状态)。 + +## 诊断 + +| ID | 级别 | 触发条件 | 消息格式 | +|----|------|----------|----------| +| DP044 | Info | 实现 `IEventHandler<T>` 但未标注 `[RegisterEventHandler]` | 提示未注册的 EventHandler | +| DP045 | Error | 同一 handler 类对同一 event type 重复标注 `[RegisterEventHandler]` | 报告重复标注 | +| DP046 | Error | 标注 `[RegisterEventHandler<T>]` 但类未实现 `IEventHandler<T>` | 报告契约不匹配 | + +## 不变量 / 兼容基线 + +1. 同一事件类型的处理器按 **订阅顺序** 依次调用。 +2. `PublishAsync` 在发布前对处理器列表做 **快照**,随后在锁外调用,避免在 `HandleAsync` 执行期间持有锁。 +3. 同一 `handler` 实例可重复 `Subscribe`(会多次出现在列表中)。 +4. `Unsubscribe` 移除**第一次**匹配项。 +5. 无处理器时 `PublishAsync` 立即完成(空订阅)。 + +### 兼容基线 + +- netstandard2.0 / net8.0(运行时核心,两者均须可用并随包分发) +- DI 集成:独立包 `DesignPatterns.Extensions.DependencyInjection`,`services.AddEventAggregator()` 注册 `IEventAggregator` → `EventAggregator`(默认 Singleton) + ## 实现概览 ### 运行时 @@ -54,7 +175,7 @@ services.AddEventAggregator(); // 默认 Singleton - **DI 路径**:`RegisterDi(IServiceCollection, ServiceLifetime)` 注册 handler 实现到容器;`SubscribeAll(IEventAggregator, IServiceProvider)` 从容器解析 handler + `Subscribe<TEvent>`。条件为 `DesignPatterns.EnableDiIntegration=true`。 - 静态路径仅包含有公共无参构造的 handler;DI 路径包含全部有效 handler。这与其他生成器的无参构造约束一致(DP007/DP009/DP014/DP019/DP022 同源)。 -### 诊断 +### 诊断检测细节 | ID | 归属 | 语义 | |----|------|------| diff --git a/docs/design/FactoryRegistry.md b/docs/design/FactoryRegistry.md index 139ec4d..aa2a8c7 100644 --- a/docs/design/FactoryRegistry.md +++ b/docs/design/FactoryRegistry.md @@ -1,7 +1,6 @@ # Design Doc: Factory Registry -> **关联 Spec**:[docs/spec/FactoryRegistry.md](../spec/FactoryRegistry.md) -> **关联 RFC**:[docs/rfc/XXX.md](../rfc/XXX.md)(如有) +> **版本**:v0.2.2(与 NuGet 包版本对齐) > **关联 ADR**:ADR-XXX(如有) ## 概述 @@ -18,6 +17,227 @@ Factory Registry 按 key 解析**工厂实现**,每次 `Create` 调用对应 f 2. 显式失败:`FactoryNotFoundException` 带 key 信息 3. 不侵入 Core 的 DI 依赖;生命周期由 factory 委托或 DI 扩展包负责 +## API 面 + +### 运行时接口 + +命名空间:`DesignPatterns.Creational` + +#### 注册表接口 + +```csharp +namespace DesignPatterns.Creational; + +/// <summary> +/// 按 key 解析工厂实现的只读注册表。每次 Create 调用对应 factory 委托得到新产品实例。 +/// </summary> +public interface IFactoryRegistry<TKey, TProduct> : IReadOnlyRegistry<TKey, TProduct> + where TKey : notnull +{ + bool TryCreate(TKey key, [MaybeNullWhen(false)] out TProduct product); + TProduct Create(TKey key); // 找不到抛 FactoryNotFoundException + IReadOnlyCollection<TKey> Keys { get; } +} +``` + +> **注意**:`IFactoryRegistry` 继承 `IReadOnlyRegistry<TKey, TProduct>`,与 `IStrategyRegistry` 共享该抽象。 + +#### 不可变实现 + +```csharp +public sealed class FactoryRegistry<TKey, TProduct> : IFactoryRegistry<TKey, TProduct> + where TKey : notnull +``` + +net8.0+ 上内部使用 `FrozenDictionary` 优化查找。 + +#### Builder(手动注册,无生成器时) + +```csharp +public sealed class FactoryRegistryBuilder<TKey, TProduct> + where TKey : notnull +{ + // 每次 Create(key) 调用该 factory + public FactoryRegistryBuilder<TKey, TProduct> Register(TKey key, Func<TProduct> factory); + + // factory 接收 key + public FactoryRegistryBuilder<TKey, TProduct> Register(TKey key, Func<TKey, TProduct> factory); + + public IFactoryRegistry<TKey, TProduct> 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>(TKey key) where TKey : notnull; +} +``` + +`ForKey` 生成消息:`"No factory registered for key '{key}'."` + +#### 异步工厂(可选) + +```csharp +public interface IAsyncFactory<TProduct> +{ + ValueTask<TProduct> CreateAsync(CancellationToken cancellationToken = default); +} + +public interface IAsyncFactoryRegistry<TKey, TProduct> : IReadOnlyRegistry<TKey, TProduct> + where TKey : notnull +{ + ValueTask<(bool Success, TProduct? Product)> TryCreateAsync( + TKey key, CancellationToken cancellationToken = default); + ValueTask<TProduct> 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<TContract> : 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<TProduct>`,生成器额外输出 `IAsyncFactoryRegistry`。`false` 时异步检测基于 `IAsyncFactory<TProduct>` 实现自动判断 | +| `PoolSize` | `int` | `0` | 启用对象池的最大池大小(per key)。`0` 禁用池化;正值使生成器输出 `IPooledFactoryRegistry`。仅异步工厂有效 | + +#### 用法示例 + +```csharp +// 泛型 Attribute(C# 11+) +[RegisterFactory<IProductFactory>("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<string, IProductFactory> 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<string, IProductFactory> 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<string, IProduct>(builder => { ... })`(扩展包),与生成器互不冲突。 + +## 诊断 + +| 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<TProduct>` | `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<TKey, TValue>` 抽象(提供 `Keys` 和 `TryGet`)。 +4. **标记的类须实现指定的 `TContract`**(DP021)。 +5. **标记的类须有 public 无参构造**:生成器使用 `new()` 实例化(DP022),或启用 DI 集成由容器解析。 +6. **key 唯一性**:同一 `TContract` 下 key 不可重复(DP020 在编译期强制)。 + +### 兼容基线 + +- 运行时 TFM:`netstandard2.0` + `net8.0`(两者均须可用并随包分发)。 +- 泛型 Attribute(`RegisterFactoryAttribute<TContract>`)需要 C# 11+ / `net7.0+`;`netstandard2.0` 目标下用 `#if NET7_0_OR_GREATER` 条件编译。 +- 非泛型 `RegisterFactoryAttribute` 始终可用,功能等价。 +- net8.0 上 `FactoryRegistry<TKey, TProduct>` 内部使用 `FrozenDictionary` 优化查找。 + ## 实现概览 ### 运行时 @@ -86,7 +306,7 @@ public static partial class ProductFactoryRegistry **池化路径**:当 `PoolSize > 0` 且工厂为异步时,生成器输出 `IPooledFactoryRegistry`,按 key 维护对象池。 -### 诊断 +### 诊断检测细节 | ID | 严重性 | 来源 | 触发条件 | |----|--------|------|----------| @@ -153,8 +373,6 @@ services.AddFactoryRegistry<string, IProduct>(builder => { ... }); // 手动 Bui | 未注册实现 | DP006 (Info) | DP023 (Info) | | DI 默认 lifetime | Singleton | Transient | -详见 [Strategy.md](../spec/Strategy.md)。 - ## 已知局限 - **不做抽象工厂族**:不提供多个产品类型族(Abstract Factory)的完整框架 @@ -165,5 +383,4 @@ services.AddFactoryRegistry<string, IProduct>(builder => { ... }); // 手动 Bui ## 参考 - 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 9f9543e..30d9c97 100644 --- a/docs/design/README.md +++ b/docs/design/README.md @@ -1,23 +1,18 @@ # Design Doc 索引 -设计文档(Design Document)— 记录模式的实现细节、设计权衡、已知局限。 +设计文档(Design Document)是各模式的单一事实来源,记录公开 API、诊断、不变量,以及实现细节、设计权衡和已知局限。 -- **格式与变更门槛**:见 [DOCUMENTATION.md](../DOCUMENTATION.md#5-design-doc--设计文档) +- **格式**:见 [DOCUMENTATION.md](../DOCUMENTATION.md) - **模板**:[_template.md](_template.md) -- **与 Spec 的关系**:[Spec](../spec/) 描述 **what**(契约),Design Doc 描述 **how** + **why**(实现) ## 已有 Design Doc -| 模式 | 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) | - -## 迁移状态 - -所有 7 个模式文档已从 `docs/<PatternName>.md` 拆分迁移至 `docs/spec/` + `docs/design/`。旧文件已删除。 +| 模式 | Design Doc | 关联 ADR | +|------|------------|----------| +| Strategy | [Strategy.md](Strategy.md) | — | +| Chain of Responsibility | [ChainOfResponsibility.md](ChainOfResponsibility.md) | — | +| Composite | [Composite.md](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) | — | +| Decorator | [Decorator.md](Decorator.md) | — | +| Event Aggregator | [EventAggregator.md](EventAggregator.md) | — | +| State Transition Table | [StateTransitionTable.md](StateTransitionTable.md) | [ADR-005](../adr/ADR-005-state-transition-table.md) | diff --git a/docs/design/StateTransitionTable.md b/docs/design/StateTransitionTable.md index b5d9ed5..b142e32 100644 --- a/docs/design/StateTransitionTable.md +++ b/docs/design/StateTransitionTable.md @@ -1,8 +1,7 @@ # Design Doc: State 转换表 -> **关联 Spec**:[docs/spec/StateTransitionTable.md](../spec/StateTransitionTable.md) +> **版本**:v0.2.2(与 NuGet 包版本对齐) > **关联 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) ## 概述 @@ -24,6 +23,388 @@ State 转换表为 **(当前状态, 触发器) → 下一状态** 的有限图 5. 层次状态机(v3)通过编译期展平实现,运行时无层级开销 6. 同时支持同步与异步路径(entry/exit action 仅 async 路径触发) +## API 面 + +### 运行时接口 + +命名空间:`DesignPatterns.Behavioral` + +#### 转换表 + +```csharp +namespace DesignPatterns.Behavioral; + +/// <summary> +/// 只读转换表:(当前状态, 触发器) → 下一状态。 +/// </summary> +public interface ITransitionTable<TState, TTrigger> + where TState : struct, Enum + where TTrigger : struct, Enum +{ + bool TryTransition(TState from, TTrigger trigger, out TState to); + bool CanTransitionFrom(TState from, TTrigger trigger); + IReadOnlyCollection<TTrigger> GetAllowedTriggers(TState from); + TState InitialState { get; } +} +``` + +- `TryTransition`:命中边且 guard 通过时返回 `true` 并设置 `to`;否则返回 `false`。 +- `GetAllowedTriggers`:返回从 `from` 出发的所有合法触发器。 +- `CanTransitionFrom`:仅接收 state(无 trigger),判断是否存在以 `from` 为起点的边。 + +`TransitionTable<TState, TTrigger>` 为不可变实现;net8.0 上内部使用 `FrozenDictionary` 优化查找。 + +#### Builder(手动注册,无生成器时) + +```csharp +public sealed class TransitionTableBuilder<TState, TTrigger> + where TState : struct, Enum + where TTrigger : struct, Enum +{ + public TransitionTableBuilder<TState, TTrigger> WithInitial(TState initial); + public TransitionTableBuilder<TState, TTrigger> Add( + TState from, TTrigger trigger, TState to, + Func<TState, TTrigger, bool>? guard = null, + Action<TState, TState, TTrigger>? onEnterSync = null, + Action<TState, TState, TTrigger>? onExitSync = null, + Func<TState, TState, TTrigger, CancellationToken, ValueTask>? onEnterAsync = null, + Func<TState, TState, TTrigger, CancellationToken, ValueTask>? onExitAsync = null); + public ITransitionTable<TState, TTrigger> 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<OrderStatus, OrderTrigger>() + .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<TState, TTrigger>( + this ITransitionTable<TState, TTrigger> table, TState from, TTrigger trigger) + where TState : struct, Enum + where TTrigger : struct, Enum; +} +``` + +#### Guard 委托 + +`TransitionTableBuilder.Add` 提供 `guard` 参数;`TransitionEdge<TState, TTrigger>.Guard` 存储委托。`TransitionTable.TryTransition` 在命中边后调用 guard;guard 返回 `false` 时该次转换视为不存在。 + +```csharp +var table = new TransitionTableBuilder<OrderStatus, OrderTrigger>() + .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<OrderStatus, OrderTrigger>() + .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<TState>` 不暴露执行进度。 + +#### IStateMachine 实例包装器 + +```csharp +public interface IStateMachine<TState, TTrigger> + where TState : struct, Enum + where TTrigger : struct, Enum +{ + TState CurrentState { get; } + bool TryTransition(TTrigger trigger, out TState to); + ValueTask<TransitionResult<TState>> TryTransitionAsync(TTrigger trigger, CancellationToken ct = default); +} + +public sealed class StateMachine<TState, TTrigger> : IStateMachine<TState, TTrigger> + where TState : struct, Enum + where TTrigger : struct, Enum +{ + public StateMachine(ITransitionTable<TState, TTrigger> table); + public TState CurrentState { get; } // 初始 == table.InitialState + public bool TryTransition(TTrigger trigger, out TState to); + public ValueTask<TransitionResult<TState>> TryTransitionAsync(TTrigger trigger, CancellationToken ct = default); +} +``` + +`TryTransition` 委托给底层 table;成功时更新 `CurrentState`。`TryTransitionAsync` 同理,并触发 entry/exit action。 + +```csharp +var machine = new StateMachine<OrderStatus, OrderTrigger>(table); +// machine.CurrentState == table.InitialState + +if (machine.TryTransition(OrderTrigger.Submit, out var next)) +{ + // machine.CurrentState == OrderStatus.Submitted +} +``` + +> **线程安全**:`StateMachine<TState,TTrigger>` 非线程安全,设计为单线程使用。多线程场景应在调用方同步,或每线程使用独立实例。 + +#### IStateHierarchy(v3 层次状态) + +```csharp +public interface IStateHierarchy<TState> + where TState : struct, Enum +{ + TState? GetParent(TState state); + bool IsInState(TState state, TState ancestor); + IReadOnlyList<TState> 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<OrderStatus, OrderTrigger> + , IStateHierarchy<OrderStatus> // 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<OrderTrigger> GetAllowedTriggers(OrderStatus from); + // v3 层次模式时额外实现 IStateHierarchy<OrderStatus> + public OrderStatus? GetParent(OrderStatus state); + public bool IsInState(OrderStatus state, OrderStatus ancestor); + public IReadOnlyList<OrderStatus> 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<OrderStatus, OrderTrigger>), + _ => Instance, + lifetime)); + // v3 层次模式时额外注册 IStateHierarchy<OrderStatus> + return services; +} +``` + +调用方式: + +```csharp +OrderStatusTransitionTable.RegisterDi(services); +// 或链式 +services.AddOtherStuff().RegisterDi<OrderStatus, OrderTrigger>(); +``` + +启用开关:MSBuild 属性 `DesignPatterns_EnableDiIntegration`(引用 DI 扩展包时自动为 `true`)。 + +#### 手动注册扩展 + +`DesignPatterns.Extensions.DependencyInjection.DesignPatternsServiceCollectionExtensions` 提供 `AddTransitionTable` 扩展方法,用于注册预构建的表实例: + +```csharp +services.AddTransitionTable(manualTable); +services.AddTransitionTable(OrderStatusTransitionTable.Instance, ServiceLifetime.Singleton); +``` + +使用 `TryAdd` 语义,重复注册不会覆盖。 + +v3 层次模式下使用 `AddStateHierarchy<TState, TTrigger>` 扩展方法(从容器解析 table 并转型)和 `AddStateMachine<TState, TTrigger>` 扩展方法。 + +## 诊断 + +| 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<TState, TTrigger>` **非线程安全**,设计为单线程使用。多线程场景应在调用方同步,或每线程使用独立实例。 +5. 执行顺序(`TryTransitionAsync`):guard → OnExit(sync → async)→ OnEnter(sync → async)→ 返回结果。 +6. Entry/Exit action 仅通过 async 路径(`TryTransitionAsync`)触发;同步 `TryTransition` 不调用 action。 +7. 部分执行语义:若 OnExit 成功后 OnEnter 抛异常,异常传播给调用方,OnExit 副作用已发生;`TransitionResult<TState>` 不暴露执行进度。 +8. `TransitionTable<TState, TTrigger>` 不可变;`Build()` 后不可修改。 +9. DI 注册使用 `TryAdd` 语义,重复注册不会覆盖。 + +### 兼容基线 + +- netstandard2.0 + net8.0(两者均须可用并随包分发) +- Roslyn 组件 4.8.0(`Microsoft.CodeAnalysis.CSharp` / Workspaces;Analyzers 3.3.4) +- 生成器实现:`IIncrementalGenerator` + `ForAttributeWithMetadataName` + ## 实现概览 ### 运行时 @@ -52,13 +433,13 @@ State 转换表为 **(当前状态, 触发器) → 下一状态** 的有限图 - **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): +- **LCA 算法 + entry/exit action 链合成**(v3.3):当层次模式下一条展平后的边跨越多个层级时,源生成器按 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<TState,TState,TTrigger>` 或 `Func<...,ValueTask>` - **边界场景(RFC §8.4)**: + **边界场景**: | 场景 | Exit 链 | Enter 链 | |------|---------|----------| @@ -69,7 +450,7 @@ State 转换表为 **(当前状态, 触发器) → 下一状态** 的有限图 - **DI 集成生成**:当消费项目引用 `DesignPatterns.Extensions.DependencyInjection`(自动 Import `build/DesignPatterns.Extensions.DependencyInjection.targets`,设置 `DesignPatterns_EnableDiIntegration=true`)时,生成器在转换表类上额外输出 `RegisterDi` 方法。v3 层次模式下 `RegisterDi` 自动注册 `ITransitionTable` + `IStateHierarchy`。 -### 诊断 +### 诊断检测细节 #### 生成器诊断 @@ -187,9 +568,6 @@ Entry/exit action 选择 **部分执行**语义——若 OnExit 成功后 OnEnte ## 参考 - [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 扩展 diff --git a/docs/design/Strategy.md b/docs/design/Strategy.md index c549eea..90a0167 100644 --- a/docs/design/Strategy.md +++ b/docs/design/Strategy.md @@ -1,6 +1,6 @@ # Design Doc: Strategy -> **关联 Spec**:[docs/spec/Strategy.md](../spec/Strategy.md) +> **版本**:v0.2.2(与 NuGet 包版本对齐) ## 概述 @@ -17,6 +17,288 @@ Strategy 模式允许在运行时按条件选择算法/行为实现,避免大 3. 不侵入实现类的继承链 4. 同时支持同步与异步策略 +## API 面 + +### 运行时接口 + +命名空间:`DesignPatterns.Behavioral` + +#### 策略接口(可选标记接口) + +```csharp +namespace DesignPatterns.Behavioral; + +/// <summary> +/// 标记性策略接口。不强制实现,但配合源生成器时提供更好的类型约束。 +/// </summary> +public interface IStrategy<in TInput, out TOutput> +{ + TOutput Execute(TInput input); +} + +/// <summary> +/// 异步策略接口。 +/// </summary> +public interface IAsyncStrategy<in TInput, TOutput> +{ + ValueTask<TOutput> ExecuteAsync(TInput input, CancellationToken ct = default); +} +``` + +> **注意**:`[RegisterStrategy]` 不要求实现 `IStrategy<,>`。任何接口/基类均可作为策略契约。 +> +> `IStrategy<,>` / `IAsyncStrategy<,>` 为**可选标记接口**,便于表达同步/异步算法形状;注册表与生成器不依赖它们。用法见 `tests/DesignPatterns.Tests/Behavioral/StrategyMarkerInterfaceTests.cs`。 + +#### 注册表 + +```csharp +namespace DesignPatterns.Behavioral; + +/// <summary> +/// 按 key 解析策略实现的只读注册表。 +/// </summary> +public interface IStrategyRegistry<TKey, TStrategy> + where TKey : notnull +{ + bool TryGet(TKey key, [MaybeNullWhen(false)] out TStrategy strategy); + TStrategy Get(TKey key); // 找不到抛 StrategyNotFoundException + bool TryGetWithGuard(TKey key, [MaybeNullWhen(false)] out TStrategy strategy); + IReadOnlyCollection<TKey> Keys { get; } +} +``` + +> **注意**:`TStrategy` 为不变(invariant),非协变 `out`——`TryGetWithGuard` 的 `out TStrategy` 参数阻止协变。 + +提供不可变实现 `StrategyRegistry<TKey, TStrategy>`,net8.0 上内部使用 `FrozenDictionary` 优化查找。 + +#### Builder(手动注册,无生成器时) + +```csharp +public sealed class StrategyRegistryBuilder<TKey, TStrategy> where TKey : notnull +{ + public StrategyRegistryBuilder<TKey, TStrategy> Register(TKey key, TStrategy strategy); + public StrategyRegistryBuilder<TKey, TStrategy> Register(TKey key, Func<TStrategy> factory); + public IStrategyRegistry<TKey, TStrategy> Build(); +} +``` + +#### 异步策略解析 + +`IAsyncStrategy<TInput, TOutput>` 与 `[RegisterStrategy]` 使用**同一套** Keys / Registry / `RegisterDi` 路径;契约可以是继承 `IAsyncStrategy<,>` 的专用接口,无需额外 attribute 或并行注册表类型。 + +`StrategyRegistryExtensions` 提供按 key 解析并执行的便利方法: + +```csharp +// 注册表值为 IAsyncStrategy<TInput, TOutput> +var result = await registry.ExecuteAsync(PaymentAsyncStrategyKeys.Stripe, amount); + +// 注册表值为继承 IAsyncStrategy<,> 的契约(需显式指定 TContract / TOutput / TInput) +var length = await registry.ExecuteAsync<ITextProcessor, int, string>( + TextProcessorKeys.Length, "hello"); + +// 或等价写法 +var length = await registry.Get(TextProcessorKeys.Length).ExecuteAsync("hello"); +``` + +`TryExecuteAsync` 与 `ExecuteAsync` 对称,未命中 key 时返回 `false` 而不抛异常。 + +DI 场景:`{Contract}Registry.RegisterDi(services)` 后从容器解析 `IStrategyRegistry<string, TContract>`,再 `await registry.ExecuteAsync<...>(...)` 或 `Get(key).ExecuteAsync(...)`。 + +#### Guard 谓词 + +`TryGetWithGuard` 在解析策略时额外评估注册的 guard 谓词。guard 返回 `false` 时该策略视为未注册(返回 `false`)。 + +```csharp +var builder = new StrategyRegistryBuilder<string, IPaymentStrategy>() + .Register("alipay", new AlipayPayment(), guard: key => isEnabled("alipay")); +var registry = builder.Build(); + +// guard 通过时返回策略;guard 返回 false 时返回 false +if (registry.TryGetWithGuard("alipay", out var strategy)) { ... } +``` + +> **设计约束**:guard 签名为 `Func<TKey, bool>`(仅接收 key),非 `Func<TInput, bool>`。注册表层面不知道 `TInput`(`TStrategy` 不要求实现 `IStrategy<TInput, TOutput>`),因此无法基于输入判断。基于输入的动态路由是业务逻辑,不在此库范围。 + +源生成器支持:`[RegisterStrategy<TContract>("key", Guard = nameof(CanEnable))]`,生成器校验 guard 方法签名(DP047-DP049)。 + +### 特性(Attribute) + +命名空间:`DesignPatterns.Behavioral` + +#### 泛型版本(C# 11 / .NET 7+) + +```csharp +/// <summary> +/// 标记一个类为某策略接口的实现,并注册到编译期生成的策略注册表中。 +/// </summary> +/// <typeparam name="TContract">策略契约接口。</typeparam> +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] +public sealed class RegisterStrategyAttribute<TContract> : Attribute +{ + /// <summary> + /// 用于解析此策略的 key。 + /// </summary> + public string Key { get; } + + /// <summary> + /// 可选:实现类上的 static guard 方法名。设置后该方法须有签名 + /// <c>static bool Method(TKey key)</c>。guard 返回 false 时该策略视为未注册。 + /// </summary> + public string? Guard { get; set; } + + public RegisterStrategyAttribute(string key) + { + Key = key; + } +} +``` + +#### 非泛型版本(netstandard2.0 / C# 7.3) + +```csharp +/// <summary> +/// 非泛型版本,用于不支持泛型 Attribute 的目标框架。 +/// </summary> +[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] +public sealed class RegisterStrategyAttribute : Attribute +{ + public string Key { get; } + + /// <summary> + /// 策略契约接口类型。 + /// </summary> + public Type For { get; } + + /// <summary> + /// 可选:实现类上的 static guard 方法名。签名须为 <c>static bool Method(TKey key)</c>。 + /// </summary> + public string? Guard { get; set; } + + public RegisterStrategyAttribute(string key, Type @for) + { + Key = key; + For = @for; + } +} +``` + +#### 用法示例 + +```csharp +// 泛型 Attribute(推荐,C# 11+) +[RegisterStrategy<IPaymentStrategy>("alipay")] +public class AlipayPayment : IPaymentStrategy { ... } + +[RegisterStrategy<IPaymentStrategy>("wechat")] +public class WechatPayment : IPaymentStrategy { ... } + +// 非泛型(netstandard2.0 / C# 7.3) +[RegisterStrategy("alipay", typeof(IPaymentStrategy))] +public class AlipayPayment : IPaymentStrategy { ... } +``` + +### 生成器产出 + +对于每个 `TContract`,生成器输出: + +#### 1. 强类型 Key 常量 + +```csharp +// PaymentStrategyKeys.g.cs +public static partial class PaymentStrategyKeys +{ + public const string Alipay = "alipay"; + public const string Wechat = "wechat"; +} +``` + +命名规则:`{接口名去掉前缀I和后缀Strategy}Keys`,可通过特性参数 override。 + +#### 2. 静态注册表(无 DI 场景) + +```csharp +// PaymentStrategyRegistry.g.cs +public static partial class PaymentStrategyRegistry +{ + private static readonly IStrategyRegistry<string, IPaymentStrategy> _instance = + new StrategyRegistry<string, IPaymentStrategy>( + new Dictionary<string, IPaymentStrategy> + { + ["alipay"] = new AlipayPayment(), + ["wechat"] = new WechatPayment(), + }); + + public static IStrategyRegistry<string, IPaymentStrategy> Instance => _instance; +} +``` + +#### 3. DI 集成(引用 `DesignPatterns.Extensions.DependencyInjection` 时) + +引用扩展包会自动 Import `build/DesignPatterns.Extensions.DependencyInjection.targets`,设置 `DesignPatterns_EnableDiIntegration=true`,生成器额外输出: + +```csharp +public static partial class PaymentStrategyRegistry +{ + // Instance 仍保留(new() 静态实例,无 DI 生命周期) + + public static IStrategyRegistry<string, IPaymentStrategy> Create(IServiceProvider serviceProvider) => + new ServiceProviderStrategyRegistry<string, IPaymentStrategy>(serviceProvider, _diEntries); + + public static IServiceCollection RegisterDi( + IServiceCollection services, + ServiceLifetime implementationLifetime = ServiceLifetime.Singleton, + ServiceLifetime registryLifetime = ServiceLifetime.Singleton); +} +``` + +推荐用法: + +```csharp +var services = new ServiceCollection(); +PaymentStrategyRegistry.RegisterDi(services); +var registry = services.BuildServiceProvider() + .GetRequiredService<IStrategyRegistry<string, IPaymentStrategy>>(); +``` + +| 生命周期组合 | 行为 | +|--------------|------| +| Singleton 实现 + Singleton 注册表 | 默认;`TryGet` 每次解析同一实现实例 | +| Transient 实现 + Singleton 注册表 | `TryGet` 每次从容器取新实例(推荐需要可变策略时) | +| Transient 注册表 | 每次解析注册表时重建 `Create(sp)` | + +手动 Builder 仍可用:`services.AddStrategyRegistry<string, IPaymentStrategy>(...)`(见扩展包),与生成器互不冲突。 + +## 诊断 + +| 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。 + +## 不变量 / 兼容基线 + +1. **key 唯一性**:同一 `TContract` 下 key 不可重复(DP003 在编译期强制)。 +2. **`TStrategy` 不变(invariant)**:`IStrategyRegistry<TKey, TStrategy>` 的 `TStrategy` 非协变 `out`——`TryGetWithGuard` 的 `out TStrategy` 参数阻止协变。 +3. **guard 签名固定为 `Func<TKey, bool>`**:guard 仅接收 key,不接收 `TInput`。注册表层面不知道 `TInput`(`TStrategy` 不要求实现 `IStrategy<TInput, TOutput>`)。 +4. **`[RegisterStrategy]` 不要求实现 `IStrategy<,>`**:任何接口/基类均可作为策略契约。 +5. **标记的类须有 public 无参构造**:生成器使用 `new()` 实例化(DP007)。 +6. **标记的类须实现指定的 `TContract`**(DP004)。 +7. **`IStrategy<,>` / `IAsyncStrategy<,>` 为可选标记接口**:注册表与生成器不依赖它们。 + +### 兼容基线 + +- 运行时 TFM:`netstandard2.0` + `net8.0`(两者均须可用并随包分发)。 +- 泛型 Attribute(`RegisterStrategyAttribute<TContract>`)需要 C# 11+ / `net7.0+`;`netstandard2.0` 目标下用 `#if NET7_0_OR_GREATER` 条件编译。 +- 非泛型 `RegisterStrategyAttribute` 始终可用,功能等价。 +- net8.0 上 `StrategyRegistry<TKey, TStrategy>` 内部使用 `FrozenDictionary` 优化查找。 + ## 实现概览 ### 运行时 @@ -39,7 +321,7 @@ Strategy 模式允许在运行时按条件选择算法/行为实现,避免大 2. **静态注册表**(`{ContractName}Registry`),包含 `Instance` 静态属性(`new()` 静态单例,无 DI 生命周期)。 3. **DI 集成方法**(引用 `DesignPatterns.Extensions.DependencyInjection` 时自动启用):`Create(IServiceProvider)` 返回 `ServiceProviderStrategyRegistry`,`RegisterDi(IServiceCollection, ...)` 注册到容器。 -### 诊断 +### 诊断检测细节 | 检测点 | 逻辑 | 报告位置 | |--------|------|----------| @@ -116,8 +398,6 @@ guard 签名固定为 `Func<TKey, bool>`(仅接收 key),不接收 `TInput` | 共享抽象 | `IStrategyRegistry` 继承 `IReadOnlyRegistry` | `IFactoryRegistry` 继承 `IReadOnlyRegistry` | | DI | `PaymentStrategyRegistry.RegisterDi(services)` | `ProductFactoryRegistry.RegisterDi(services)` | -详见 [FactoryRegistry.md](../spec/FactoryRegistry.md)。 - ## 已知局限 - **不做 DI 生命周期管理**:注册表不替代 DI 容器的生命周期管理;`Instance` 为 `new()` 静态单例,无 DI 生命周期。 @@ -131,7 +411,5 @@ guard 签名固定为 `Func<TKey, bool>`(仅接收 key),不接收 `TInput` - 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/design/_template.md b/docs/design/_template.md index 50527de..402c9ec 100644 --- a/docs/design/_template.md +++ b/docs/design/_template.md @@ -1,32 +1,33 @@ # Design Doc: <模式名> -> **关联 Spec**:[docs/spec/<PatternName>.md](../spec/<PatternName>.md) -> **关联 RFC**:[docs/rfc/XXX.md](../rfc/XXX.md)(如有) > **关联 ADR**:ADR-XXX(如有) +> **关联 Issue**:#XXX(可选) ## 概述 ## 设计目标 -## 实现概览 +## API 面 -### 运行时 +(公开运行时 API、Attribute 与生成器产出) -(关键类、数据结构、算法) +## 诊断 -### 源生成器 +(诊断 ID、级别、触发条件与消息) -(增量管线阶段、EquatableArray 结构) +## 不变量 / 兼容基线 -### 诊断 +(语义约束、目标框架与版本兼容要求) -(检测逻辑、报告位置) +## 实现概览 -## 设计权衡 +### 运行时 + +### 源生成器 -### 选择了 X 而非 Y,因为... +### 诊断检测逻辑 -(链接到 ADR) +## 设计权衡 ## 与生态的边界 diff --git a/docs/plans/README.md b/docs/plans/README.md deleted file mode 100644 index 8b8f36f..0000000 --- a/docs/plans/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Plan 状态板 - -大型任务计划(跨多 PR / 跨多阶段)。小型任务直接使用 GitHub Issue,不建计划文档。 - -- **格式与生命周期**:见 [DOCUMENTATION.md](../DOCUMENTATION.md#7-plan--任务计划) -- **模板**:[_template.md](_template.md) -- **归档**:已完成 / 已取消的 Plan 移入 [archive/](archive/) - -## 活跃 Plan - -| Plan | 标题 | 状态 | 创建日期 | 关联 RFC | -|------|------|----------|----------|----------| -| [SingletonLifecycleDiagnostics.md](SingletonLifecycleDiagnostics.md) | Singleton 生命周期诊断 | Active | 2026-07-08 | [RFC](../rfc/SingletonLifecycleDiagnostics.md) | - -## 归档 Plan - -| Plan | 标题 | 最终状态 | 创建日期 | 关联 RFC | -|------|------|----------|----------|----------| -| — | — | — | — | — | diff --git a/docs/plans/SingletonLifecycleDiagnostics.md b/docs/plans/SingletonLifecycleDiagnostics.md deleted file mode 100644 index c2a4346..0000000 --- a/docs/plans/SingletonLifecycleDiagnostics.md +++ /dev/null @@ -1,54 +0,0 @@ -# Plan: Singleton 生命周期诊断 - -> **状态**:Active -> **创建**:2026-07-08 -> **更新**:2026-07-08 -> **关联 RFC**:[docs/rfc/SingletonLifecycleDiagnostics.md](../rfc/SingletonLifecycleDiagnostics.md) -> **关联 Issue**:(待创建) -> **关联 Roadmap**:F3 — Singleton 生命周期诊断 - -## 目标 - -按 RFC 三阶段交付 Singleton 生命周期编译期诊断:P1 补齐 DP062 缺口 → P2 `[GenerateSingleton]` 增强 → P3 静态可变单例反模式检测。每阶段独立 PR,遵守 AGENTS.md 单模块边界。 - -## 非目标 - -- 不在本 Plan 内发版(发版由维护者单独决策)。 -- 不在首版实现 CodeFix(除非某阶段 Review 结论要求)。 - -## 里程碑拆解 - -| 阶段 | 内容 | 模块(AGENTS.md 边界) | 状态 | PR | -|------|------|------------------------|------|-----| -| **P0** | RFC Draft → 设计评审([Review](../review/2026-07-08-singleton-lifecycle-diagnostics-design.md))→ Accepted → [ADR-008](../adr/ADR-008-singleton-lifecycle-diagnostics.md) | Docs | [x] | (直接提交) | -| **P1** | 扩展 captive dependency:Autofac / RegisterAutofac 注册收集(符号名匹配)、工厂委托 lambda 分析;DP066;CaptiveDependencyAnalyzer 测试 | Analyzers | [x] | (直接提交) | -| **P2a** | `GenerateSingletonAttribute.InitializeAsync` + 生成 `GetInstanceAsync`(`Lazy<Task<T>>`)+ 生成器 DP067 | Runtime + SourceGenerators(拆 2 PR) | [ ] | — | -| **P2b** | DP068 DI 混用警告 + DP069 ThreadSafe 提示 | Analyzers | [ ] | — | -| **P2** | Spec `docs/spec/Singleton.md` + Design Doc + Sample 更新(可选) | Docs | [ ] | — | -| **P3** | DP070 / DP071 静态可变单例启发式检测 | Analyzers | [ ] | — | -| **收尾** | CHANGELOG、`AGENTS.md` 诊断表、DesignPatterns.Docs diagnostics 页、RFC → Implemented 归档 | Docs | [ ] | — | - -## 验收标准 - -- [x] P1:Autofac `SingleInstance` 构造函数 captive + 工厂委托 captive(DP066)有 Verify 测试;DP066 已登记并发布到 `AnalyzerReleases` -- [ ] P2a:`InitializeAsync` 指定后生成 `GetInstanceAsync` 且不生成同步 `Instance`;无效签名报 DP067;netstandard2.0 / net8.0 测试通过 -- [ ] P2b:同时 `[GenerateSingleton]` + `AddSingleton` 报 DP068;`ThreadSafe=false` 场景报 DP069 -- [ ] P3:典型 static mutable singleton 样本报 DP070;与 DI 双重注册报 DP071;误报样本不报 -- [ ] 全仓 `./build.ps1 --target Ci --configuration Release` 零警告 -- [ ] RFC 状态 → Implemented;本 Plan → Done 并归档 - -## 风险与依赖 - -| 风险 | 缓解 | -|------|------| -| P1 工厂委托 lambda 静态分析覆盖不全(间接调用、方法组) | 首版仅分析直接 `GetRequiredService` / `GetService` 调用;限制记入 Design Doc | -| P2a `Lazy<Task<T>>` 初始化失败缓存异常 Task | Design Doc 记录该 `Lazy` 语义;必要时用 `LazyThreadSafetyMode.PublicationOnly` 权衡 | -| P3 启发式误报 | 默认 Info;实现 PR 用 Samples 与测试固件调参 | - -## 变更记录 - -| 日期 | 调整 | 原因 | -|------|------|------| -| 2026-07-08 | 初版 Plan 自 RFC Draft 创建 | 用户选定完整 RFC 三阶段范围 | -| 2026-07-08 | P1 删除字段注入项(DP066 改为工厂委托 captive);P2a 改 `GetInstanceAsync` 设计 | 设计评审 Blocker #1/#2([Review](../review/2026-07-08-singleton-lifecycle-diagnostics-design.md)) | -| 2026-07-08 | P1 完成:Autofac 注册收集 + DP066 工厂委托分析 + 11 个 Verify 测试 | P1 实现落地 | diff --git a/docs/plans/_template.md b/docs/plans/_template.md deleted file mode 100644 index 48a49ba..0000000 --- a/docs/plans/_template.md +++ /dev/null @@ -1,39 +0,0 @@ -# Plan: <标题> - -> **状态**:Active -> **创建**:YYYY-MM-DD -> **更新**:YYYY-MM-DD -> **关联 RFC**:[docs/rfc/XXX.md](../rfc/XXX.md)(如有) -> **关联 Issue**:#XXX -> **关联 Roadmap**:FXX(如有) - -## 目标 - -一段话说明要交付什么。 - -## 非目标 - -本计划不覆盖的内容。 - -## 里程碑拆解 - -<!-- 每个里程碑对应一个 PR,模块须对齐 AGENTS.md 单模块 PR 边界 --> - -| 阶段 | 内容 | 模块(AGENTS.md 边界) | 状态 | PR | -|------|------|------------------------|------|-----| -| P1 | ... | Runtime | [ ] | — | -| P2 | ... | SourceGenerators | [ ] | — | - -## 验收标准 - -- [ ] ... - -## 风险与依赖 - -## 变更记录 - -<!-- 计划执行中的重大调整(范围增减、顺序变更)在此追加,不删除原文 --> - -| 日期 | 调整 | 原因 | -|------|------|------| -| — | — | — | diff --git a/docs/plans/archive/.gitkeep b/docs/plans/archive/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/docs/review/2026-07-08-singleton-lifecycle-diagnostics-design.md b/docs/review/2026-07-08-singleton-lifecycle-diagnostics-design.md deleted file mode 100644 index 89851a3..0000000 --- a/docs/review/2026-07-08-singleton-lifecycle-diagnostics-design.md +++ /dev/null @@ -1,44 +0,0 @@ -# Review: Singleton 生命周期诊断 — RFC 设计评审 - -> **状态**:Final -> **类型**:Design -> **日期**:2026-07-08 -> **评审对象**:[docs/rfc/SingletonLifecycleDiagnostics.md](../rfc/SingletonLifecycleDiagnostics.md)(Draft 初版) -> **评审人**:Agent(对照代码库核查) - -## 评审范围 - -RFC Draft 初版全部章节:P1 DP062 缺口补齐、P2 `[GenerateSingleton]` 增强、P3 静态可变单例检测、诊断 ID 分配(DP066–DP071)、开放问题 1–4。核查依据:`CaptiveDependencyAnalyzer.cs`(DP062 现实现)、`DesignPatterns.Analyzers.csproj`(依赖结构)、`SingletonSyntaxFactory.cs` / `GenerateSingletonAttribute.cs`(生成器现状)。 - -## 结论 - -**有条件通过** — 修正 2 个 Blocker、1 个 Major 后可进入 Accepted;诊断 ID 分配与三阶段结构合理,无需推翻。 - -## 发现 - -| # | 级别 | 发现 | 建议 | -|---|------|------|------| -| 1 | Blocker | P2a 设计「`Instance` getter 在首次访问时 await 初始化」不可实现——C# 属性 getter 不能 `await`;若同步阻塞(`.GetAwaiter().GetResult()`)则违反「异步一等」原则且有死锁风险 | 指定 `InitializeAsync` 时**不生成**同步 `Instance`,改为生成 `static ValueTask<T> GetInstanceAsync(CancellationToken)`,内部用 `Lazy<Task<T>>`(netstandard2.0 可用);两种形态互斥,语义清晰 | -| 2 | Blocker | P1「字段/属性注入」以 `[FromServices]` 为触发条件不成立——`[FromServices]` 是 ASP.NET Core MVC 参数绑定特性,MSDI 本身**没有**字段/属性注入;按原设计 DP066 永远不会在纯 MSDI 代码中触发 | 首版删除字段/属性注入项;Autofac `PropertiesAutowired()` 属性注入列为「不在范围(后续评估)」。DP066 改指**工厂委托 captive dependency**(`AddSingleton<T>(sp => ...)` lambda 内解析 Scoped/Transient),该项原本悬而未决,正好补位 | -| 3 | Major | 开放问题 2(Analyzer 是否引用 Autofac 程序集)实际已有先例可循——现有 DP060/DP062 对 MSDI **未加包引用**,通过 `ToDisplayString()` 符号全名匹配(`CaptiveDependencyAnalyzer.cs:293`);Analyzer 程序集 `IncludeBuildOutput=false`,不应携带第三方依赖 | Autofac 检测同样用**符号全名字符串匹配**(`Autofac.ContainerBuilder`、`SingleInstance` 等),不新增 PackageReference;开放问题 2 关闭 | -| 4 | Minor | DP069 触发条件「含 async 成员或实现 IDisposable」与线程安全风险相关性弱——`IDisposable` 与 `LazyThreadSafetyMode.None` 并无直接冲突 | 触发条件改为「`ThreadSafe = false` 且类型含**非 readonly 实例字段**(可变状态)」,直指竞态本质;保持 Info 级别 | -| 5 | Minor | 开放问题 1(`Lazy<Task<T>>` vs 自定义 `AsyncLazy`)可直接决策——`Lazy<Task<T>>` 在 netstandard2.0 完全可用,且本库「primitives 而非框架」原则不支持为单一场景新增公共 primitive | 采用 `Lazy<Task<T>>`(生成代码内部实现细节,不暴露公共类型);开放问题 1 关闭 | -| 6 | Nit | DP070 启发式仅覆盖 static 字段,遗漏 `public static T Instance { get; set; }` 可变属性形态 | 启发式补充「public static 带 setter 的引用类型属性」 | -| 7 | Nit | 开放问题 4(CodeFix)建议直接定稿 | 首版仅诊断,不做 CodeFix;后续按需求迭代(与 DP010–012 处理方式一致) | - -## 行动项 - -- [x] #1 → RFC P2a 改为 `GetInstanceAsync` 设计(本次评审随附修改) -- [x] #2 → RFC P1 重构:删除字段注入、DP066 重定义为工厂委托 captive(本次评审随附修改) -- [x] #3 → RFC 非目标与开放问题更新:Autofac 符号名匹配定稿(本次评审随附修改) -- [x] #4 → RFC DP069 触发条件修正(本次评审随附修改) -- [x] #5 → RFC 开放问题 1 关闭(本次评审随附修改) -- [x] #6 → RFC DP070 启发式补充(本次评审随附修改) -- [x] #7 → RFC 开放问题 4 关闭(本次评审随附修改) - -## 参考 - -- [CaptiveDependencyAnalyzer.cs](../../DesignPatterns.Analyzers/CaptiveDependencyAnalyzer.cs) — DP062 符号匹配实现 -- [DesignPatterns.Analyzers.csproj](../../DesignPatterns.Analyzers/DesignPatterns.Analyzers.csproj) — Analyzer 依赖结构(无 MSDI/Autofac 包引用) -- [ADR-001 Primitives over frameworks](../adr/ADR-001-primitives-over-frameworks.md) -- [ADR-004 Core does not reference MSDI](../adr/ADR-004-core-does-not-reference-msdi.md) diff --git a/docs/review/README.md b/docs/review/README.md deleted file mode 100644 index 4cc613d..0000000 --- a/docs/review/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Review 索引 - -评审记录 — 设计评审、实现回顾、发版审查、技术债审查。单 PR 的常规 code review 在 PR Comments 中进行,不建文档。 - -- **格式与生命周期**:见 [DOCUMENTATION.md](../DOCUMENTATION.md#8-review--评审记录) -- **模板**:[_template.md](_template.md) -- **归档**:Final 且行动项全部关闭的 Review 移入 [archive/](archive/) - -## 行动项未关闭 - -| Review | 类型 | 结论 | 日期 | -|--------|------|------|------| -| [2026-07-08-singleton-lifecycle-diagnostics-design.md](2026-07-08-singleton-lifecycle-diagnostics-design.md) | Design | 有条件通过(修订已随评审完成,待 RFC Accepted 后归档) | 2026-07-08 | - -## 归档 Review - -| Review | 类型 | 结论 | 日期 | -|--------|------|------|------| -| — | — | — | — | diff --git a/docs/review/_template.md b/docs/review/_template.md deleted file mode 100644 index b595f20..0000000 --- a/docs/review/_template.md +++ /dev/null @@ -1,31 +0,0 @@ -# Review: <标题> - -> **状态**:Draft -> **类型**:Design | Implementation | Release | Retrospective -> **日期**:YYYY-MM-DD -> **评审对象**:RFC / Plan / PR 范围 / 版本号 -> **评审人**:维护者 / Agent(注明) - -## 评审范围 - -评审了什么(文档、代码范围、版本)。 - -## 结论 - -通过 | 有条件通过 | 不通过,一句话概括。 - -## 发现 - -<!-- 级别:Blocker(不修复不得合并/发版)、Major(须建 Issue 跟踪)、Minor(建议修复)、Nit(可忽略) --> - -| # | 级别 | 发现 | 建议 | -|---|------|------|------| -| 1 | Blocker / Major / Minor / Nit | ... | ... | - -## 行动项 - -<!-- Final 后正文不可变;仅允许勾选 checkbox、补充 Issue/PR 链接 --> - -- [ ] #1 → Issue #XXX / PR #XXX - -## 参考 diff --git a/docs/review/archive/.gitkeep b/docs/review/archive/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/docs/rfc/README.md b/docs/rfc/README.md deleted file mode 100644 index 55ae7b5..0000000 --- a/docs/rfc/README.md +++ /dev/null @@ -1,22 +0,0 @@ -# RFC 状态板 - -Request for Comments — 设计提案与讨论记录。 - -- **格式与生命周期**:见 [DOCUMENTATION.md](../DOCUMENTATION.md#2-rfc--request-for-comments) -- **模板**:[_template.md](_template.md) -- **归档**:已实现 / 已否决 / 已取代的 RFC 移入 [archive/](archive/) - -## 活跃 RFC - -| RFC | 标题 | 状态 | 创建日期 | 关联 Plan | -|-----|------|------|----------|-----------| -| [SingletonLifecycleDiagnostics.md](SingletonLifecycleDiagnostics.md) | Singleton 生命周期诊断 | Accepted | 2026-07-08 | [Plan](../plans/SingletonLifecycleDiagnostics.md) | - -## 归档 RFC - -| RFC | 标题 | 最终状态 | 创建日期 | 衍生 ADR | -|-----|------|----------|----------|----------| -| [StateTransitionTable.md](archive/StateTransitionTable.md) | State 转换表 | Implemented | 2026-06-14 | ADR-005 | -| [HierarchicalStateMachine.md](archive/HierarchicalStateMachine.md) | State 层级状态机 | Implemented | 2026-06-28 | ADR-005 | -| [CompositeParallelTraversal.md](archive/CompositeParallelTraversal.md) | Composite 并行遍历 | Implemented | 2026-06-30 | ADR-006 | -| [CompositeTreeSchemaValidation.md](archive/CompositeTreeSchemaValidation.md) | Composite 树 schema 校验 | Implemented | 2026-07-07 | ADR-007 | diff --git a/docs/rfc/SingletonLifecycleDiagnostics.md b/docs/rfc/SingletonLifecycleDiagnostics.md deleted file mode 100644 index aac0494..0000000 --- a/docs/rfc/SingletonLifecycleDiagnostics.md +++ /dev/null @@ -1,217 +0,0 @@ -# RFC: Singleton 生命周期诊断 - -> **状态**:Accepted -> **类型**:Feature -> **创建**:2026-07-08 -> **更新**:2026-07-08(设计评审修订并 Accepted,见[评审记录](../review/2026-07-08-singleton-lifecycle-diagnostics-design.md)) -> **作者**:维护者 -> **关联 Roadmap**:F3(长期探索候选 — Singleton 生命周期诊断) -> **关联 Issue**:(待创建) -> **衍生 ADR**:[ADR-008](../adr/ADR-008-singleton-lifecycle-diagnostics.md) - -## 摘要 - -在现有 **DP062**(Singleton 构造函数捕获 Scoped/Transient 服务)基础上,分三阶段扩展 Singleton 生命周期相关编译期诊断:补齐 DI 注册覆盖缺口、增强 `[GenerateSingleton]` 生命周期安全、检测静态可变单例反模式。目标是在不引入厚重框架的前提下,最大化「Analyzer + 源生成器」协同的 DI 反模式防护价值。 - -## 动机 - -| 现状 | 局限 | -|------|------| -| **DP062** 已覆盖 MSDI `AddSingleton` / `TryAdd` 及生成器 `RegisterDi` 的**构造函数注入** captive dependency | 未覆盖 Autofac `RegisterAutofac` 注册、`AddSingleton` 工厂委托注册 | -| **`[GenerateSingleton]`** 生成 `Lazy<T>` + `Instance`,与 DI 生命周期**显式无关**(见特性 XML 文档) | 无 async 初始化路径;与 DI 混用时无警告;`ThreadSafe = false` 无场景提示 | -| **DEVELOPMENT.md** 已声明「诊断可提示静态可变单例,推荐 DI 生命周期」 | 尚无对应 Analyzer | - -生产场景中 Singleton 生命周期错误(captive dependency、静态可变状态、编译期单例与容器单例混用)是高频 DI 反模式;本 RFC 将 ROADMAP 候选「Singleton 生命周期诊断」落地为可排期的三阶段计划。 - -## 非目标 - -- 不实现完整 DI 容器或替换 MSDI / Autofac。 -- 不将 `[GenerateSingleton]` 与 DI 生命周期**合并**为统一单例模型(二者语义保持分离)。 -- 不检测所有可能的运行时 captive dependency(如 `IServiceProvider` 手动 resolve、反射注入)。 -- **Analyzer 不引用 Autofac 包**——沿用 DP060/DP062 对 MSDI 的做法:符号全名字符串匹配(`ToDisplayString()`),Analyzer 程序集保持零第三方依赖(`IncludeBuildOutput=false`)。 -- 不检测字段/属性注入——MSDI 无此机制;Autofac `PropertiesAutowired()` 属性注入列为后续评估项,首版不做。 -- 不重复 **DP060**(`RegisterDi` registry lifetime > implementation lifetime)或 **DP061**(wasteful lifetime mismatch)的语义。 - ---- - -## 设计方案 - -### 概念模型 - -``` -┌─────────────────────────────────────────────────────────────┐ -│ DI 容器注册(MSDI / Autofac / RegisterDi / RegisterAutofac)│ -│ → 构建 Type → Lifetime 映射 │ -│ → 对 Singleton 实现检查依赖来源(ctor / 工厂委托 lambda) │ -└─────────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ [GenerateSingleton](编译期 Lazy<T>,非 DI) │ -│ → 可选 async 初始化钩子 │ -│ → 与 DI 注册混用警告 │ -│ → ThreadSafe=false 场景提示 │ -└─────────────────────────────────────────────────────────────┘ - │ - ▼ -┌─────────────────────────────────────────────────────────────┐ -│ 静态可变单例反模式(无特性、无 DI) │ -│ → 检测 public static 可变字段 / 典型 ServiceLocator 形态 │ -└─────────────────────────────────────────────────────────────┘ -``` - -### 阶段 P1 — 补齐 DP062 缺口(Analyzer 模块) - -**目标**:扩展 `CaptiveDependencyAnalyzer`(或同模块姊妹 Analyzer),不修改 DP062 已发布语义。 - -| 增强项 | 说明 | 诊断 | -|--------|------|------| -| **Autofac 注册收集** | 识别 `RegisterType<T>().SingleInstance()`、`Register(_ => ...).SingleInstance()`、`RegisterAutofac` 生成方法内的 `SingleInstance()` / `InstancePerDependency()` 等;映射 implementation type → lifetime。检测采用**符号全名匹配**(如 `Autofac.ContainerBuilder`),不引入 Autofac 包引用 | 复用 **DP062**(同一 captive dependency 语义,构造函数注入) | -| **工厂委托注册** | `AddSingleton<T>(sp => new T(...))` / `AddSingleton(sp => ...)` 中静态分析 lambda 体内的 `GetRequiredService<X>()` / `GetService<X>()` 调用;若 `X` 在 lifetime map 中为 Scoped/Transient → 报告 | **DP066**(新,独立 ID:修复建议与构造函数场景不同) | -| **多构造函数** | 文档化限制:当前取首个 public 实例构造函数;评估是否改为「最长 public 构造函数」对齐 `ActivatorUtilities` | 无新 ID;Design Doc 记录 | - -**不变量**: - -- DP062 消息格式与严重性(Warning)不变。 -- Analyzer 仍放在 `DesignPatterns.Analyzers`,遵守 ADR-004;对 MSDI / Autofac 均为符号名匹配,无包引用。 - -> 评审备注:初稿的「字段/属性注入(`[FromServices]`)」项经评审移除——MSDI 没有字段/属性注入机制,`[FromServices]` 是 ASP.NET Core MVC 参数绑定特性;见[评审记录 #2](../review/2026-07-08-singleton-lifecycle-diagnostics-design.md)。 - -### 阶段 P2 — `[GenerateSingleton]` 增强(SourceGenerators + Analyzers) - -**目标**:增强编译期单例生成器的生命周期安全,不与 DI Singleton 混淆。 - -#### P2a — Async 初始化(SourceGenerators 模块) - -新增可选特性属性: - -```csharp -public sealed class GenerateSingletonAttribute : Attribute -{ - // 现有 - public bool ThreadSafe { get; set; } = true; - - /// <summary> - /// Optional static method name for async initialization invoked once - /// before the instance is returned from the generated GetInstanceAsync(). - /// When set, the synchronous Instance property is not generated. - /// Signature: static ValueTask|Task MethodName(T instance, CancellationToken ct) - /// </summary> - public string? InitializeAsync { get; set; } -} -``` - -生成器行为(**两种形态互斥**): - -- 未指定 `InitializeAsync`(默认):生成现有 `Lazy<T>` + 同步 `Instance` 属性,行为不变。 -- 指定 `InitializeAsync`:**不生成**同步 `Instance`,改为生成: - -```csharp -private static readonly Lazy<Task<T>> _instance = new(async () => -{ - var instance = new T(); - await T.<InitializeAsync>(instance, CancellationToken.None).ConfigureAwait(false); - return instance; -}, LazyThreadSafetyMode.ExecutionAndPublication); - -/// <summary>Gets the asynchronously initialized singleton instance.</summary> -public static ValueTask<T> GetInstanceAsync() => new(_instance.Value); -``` - -- 生成器校验 `InitializeAsync` 方法存在、static、签名匹配 `static ValueTask|Task Method(T instance, CancellationToken ct)`(**DP067** Error,复用 State guard 签名校验基础设施)。 -- `Lazy<Task<T>>` 为生成代码内部实现,不新增公共 primitive(netstandard2.0 可用;遵守 ADR-001)。 - -> 评审备注:初稿「`Instance` getter 首次访问时 await」不可实现(属性 getter 不能 await,同步阻塞违反异步一等原则);改为互斥的 `GetInstanceAsync` 设计,见[评审记录 #1](../review/2026-07-08-singleton-lifecycle-diagnostics-design.md)。 - -#### P2b — DI 混用与 ThreadSafe 提示(Analyzers 模块) - -| ID | 严重性 | 触发条件 | -|----|--------|----------| -| **DP068** | Warning | 类型同时标注 `[GenerateSingleton]` 且在编译单元内被 MSDI `AddSingleton` / `RegisterDi` / Autofac `SingleInstance` 注册(双重单例,实例不一致风险) | -| **DP069** | Info | `[GenerateSingleton(ThreadSafe = false)]` 且类型含**非 readonly 实例字段**(可变状态 + 无发布屏障 → 竞态风险) | - -**非目标(P2)**:不自动生成 DI 注册代码;不将 `Instance` 注册到容器。 - -### 阶段 P3 — 静态可变单例反模式(Analyzers 模块) - -**目标**:落实 DEVELOPMENT.md「诊断可提示静态可变单例,推荐 DI 生命周期」。 - -| ID | 严重性 | 触发条件 | -|----|--------|----------| -| **DP070** | Info | `public static` 非 readonly 引用类型字段,**或 `public static` 带 setter 的引用类型属性**,且类型名/成员名匹配常见单例命名(`Instance`、`Singleton`、`_instance`)或类为 `sealed` 且仅含 static 访问器 | -| **DP071** | Warning | 上述字段且类型在 DI 容器中也有 Singleton 注册(双重单例形态) | - -**抑制**:`[SuppressMessage]` 或 `#pragma` 标准 Roslyn 抑制;不新增自定义 Suppress 特性。 - -**非目标(P3)**:不检测所有 static mutable 字段(误报控制优先);不强制迁移到 DI。 - ---- - -## 诊断 ID 汇总 - -| ID | 名称 | 严重性 | 归属 | 阶段 | -|----|------|--------|------|------| -| DP062 | CaptiveDependency | Warning | Analyzers | 已有 | -| DP066 | FactoryDelegateCaptiveDependency | Warning | Analyzers | P1 | -| DP067 | GenerateSingletonInvalidInitializer | Error | SourceGenerators | P2a | -| DP068 | GenerateSingletonDiMixing | Warning | Analyzers | P2b | -| DP069 | GenerateSingletonThreadSafetyHint | Info | Analyzers | P2b | -| DP070 | StaticMutableSingleton | Info | Analyzers | P3 | -| DP071 | StaticMutableSingletonWithDi | Warning | Analyzers | P3 | - -> **ID 登记**:实现前须同步 `DiagnosticIds.cs`、`DesignPatternsDiagnosticDescriptors.cs`、`AnalyzerReleases.Unshipped.md`、`AGENTS.md` 诊断表、`docs/spec/`(若影响公共 API)及 DesignPatterns.Docs diagnostics 页。 - ---- - -## API 变更 - -| 变更 | 类型 | 模块 | -|------|------|------| -| `GenerateSingletonAttribute.InitializeAsync` | 非破坏性新增 | Runtime (`DesignPatterns/`) | -| 无其他公共运行时 API 变更 | — | — | - -Spec:若 P2a 落地,须新建或扩展 `docs/spec/Singleton.md`(当前无独立 Spec 文件)。 - ---- - -## 替代方案 - -| 方案 | 否决原因 | -|------|----------| -| 仅文档说明,不新增诊断 | 无法发挥编译期协同探索价值;与项目方针不符 | -| 将 `[GenerateSingleton]` 废弃,统一 DI Singleton | 破坏已有 Sample / 测试;`GenerateSingleton` 与 DI 生命周期语义 intentionally 分离 | -| 单一 mega-Analyzer 覆盖全部阶段 | 违反单模块 PR 边界;难测试、难回滚 | -| 扩展 DP062 语义覆盖工厂委托而不新 ID | 工厂委托与构造函数注入的修复建议不同(改 lambda vs 改构造函数/生命周期);独立 ID 便于 help link 与后续 CodeFix 演进 | - ---- - -## 开放问题 - -1. ~~**P2a Async 初始化**:`Lazy<Task<T>>` vs 自定义 `AsyncLazy` primitive~~ — **已决策**:采用 `Lazy<Task<T>>`(生成代码内部实现,不新增公共 primitive,ADR-001 约束)。见[评审记录 #5](../review/2026-07-08-singleton-lifecycle-diagnostics-design.md)。 -2. ~~**P1 Autofac**:Analyzer 是否引用 Autofac 程序集~~ — **已决策**:符号全名字符串匹配,零包引用(对齐 DP060/DP062 的 MSDI 处理)。见[评审记录 #3](../review/2026-07-08-singleton-lifecycle-diagnostics-design.md)。 -3. **P3 误报率**:`DP070` 启发式规则需在实现 PR 中用真实代码库样本调优(默认 Info 已定,调参留在 P3 实现 PR)。 -4. ~~**CodeFix**~~ — **已决策**:首版仅诊断,不做 CodeFix;后续按需求迭代。见[评审记录 #7](../review/2026-07-08-singleton-lifecycle-diagnostics-design.md)。 - ---- - -## 决策记录 - -**2026-07-08 设计评审**([评审记录](../review/2026-07-08-singleton-lifecycle-diagnostics-design.md),结论:有条件通过): - -1. P2a 改为互斥 `GetInstanceAsync` 设计(原 `Instance` await 方案不可实现)。 -2. P1 移除字段/属性注入项(MSDI 无此机制);DP066 重定义为工厂委托 captive dependency。 -3. Autofac 检测用符号全名匹配,Analyzer 零第三方包引用。 -4. DP069 触发条件改为「非 readonly 实例字段」;DP070 启发式补充可变静态属性。 -5. 首版仅诊断无 CodeFix;`Lazy<Task<T>>` 为内部实现。 - -**2026-07-08 维护者确认 Accepted**,产出 [ADR-008](../adr/ADR-008-singleton-lifecycle-diagnostics.md)。 - ---- - -## 参考 - -- [ADR-004 Core does not reference MSDI](../adr/ADR-004-core-does-not-reference-msdi.md) -- [CaptiveDependencyAnalyzer.cs](../../DesignPatterns.Analyzers/CaptiveDependencyAnalyzer.cs)(DP062 实现) -- [GenerateSingletonAttribute.cs](../../DesignPatterns/Creational/GenerateSingletonAttribute.cs) -- [ROADMAP.md](../ROADMAP.md) — Singleton 生命周期诊断候选 -- [DEVELOPMENT.md](../DEVELOPMENT.md) — Singleton 诊断说明 diff --git a/docs/rfc/_template.md b/docs/rfc/_template.md deleted file mode 100644 index 5a262c8..0000000 --- a/docs/rfc/_template.md +++ /dev/null @@ -1,46 +0,0 @@ -# RFC: <标题> - -> **状态**:Draft -> **类型**:Feature | Pattern | Architecture | Process -> **创建**:YYYY-MM-DD -> **更新**:YYYY-MM-DD -> **作者**:维护者 / 贡献者 -> **关联 Roadmap**:FXX(如有) -> **关联 Issue**:#XXX(如有) -> **衍生 ADR**:ADR-XXX(Accepted 后填写) - -## 摘要 - -一段话说明提案内容。 - -## 动机 - -为什么需要这个变更?现有局限是什么? - -## 非目标 - -明确不做什么,避免范围蔓延。 - -## 设计方案 - -### 概念模型 - -### API 设计 - -### 诊断影响 - -### 实现方案 - -## 替代方案 - -考虑过但否决的方案及原因。 - -## 开放问题 - -讨论中尚未决策的问题(Review 阶段)。 - -## 决策记录 - -Review 结束后记录最终决策与理由(Accepted 阶段填写)。 - -## 参考 diff --git a/docs/rfc/archive/CompositeParallelTraversal.md b/docs/rfc/archive/CompositeParallelTraversal.md deleted file mode 100644 index d141b5b..0000000 --- a/docs/rfc/archive/CompositeParallelTraversal.md +++ /dev/null @@ -1,306 +0,0 @@ -# RFC: Composite 并行遍历 - -> **状态**:Implemented -> **类型**:Feature -> **创建**:2026-06-30 -> **更新**:2026-07-07 -> **作者**:维护者 -> **关联 Roadmap**:F5 -> **关联 Issue**:— -> **衍生 ADR**:[ADR-006](../../adr/ADR-006-composite-parallel-traversal.md) - -## 概述 - -为 Composite 模式新增并行遍历能力(`TraverseParallel` / `TraverseParallelAsync`),支持 BFS 同层并行、DFS 子节点并行递归,配合 `MaxDegreeOfParallelism` 限流与 `MaxParallelDepth` 递归深度保护。 - -懒加载(`ICompositeLazyNode` + `[CompositePart(LazyChildren)]`)作为 Phase 3 独立 RFC,不在本纪要范围。 - -## 设计会议纪要 - -### 参与角色 - -| 角色 | 职责 | -|------|------| -| 运行时库架构师 | API 设计、接口契约、向后兼容性 | -| 并发工程师 | 并发原语、同步机制、性能、死锁/竞态 | -| 风险分析师 / 魔鬼代言人 | 失败模式、竞态条件、误用场景 | -| 真实用户场景分析师 | 实际使用模式、用户心智模型 | - ---- - -## 一、达成共识的议题 - -### 1.1 线程安全责任归属:用户责任 + 文档契约 - -四角色一致同意:库不做运行时防御,线程安全是用户责任。 - -| 维度 | 共识 | -|------|------| -| `Children` 读取 | 组装后视为不可变;并行读取安全(前提:无写入);不做防御性复制 | -| `ShouldSkipSubtree` | 用户责任;不做预评估(O(N) 额外遍历违背并行初衷) | -| visitor 回调 | 纯文档契约;不加 `ISafeVisitor` 标记接口 | -| `IReadOnlyList<T>` | `List<T>` 并行 `foreach` 读取安全(无写入时);.NET 运行时保证 | - -- **架构师**:"库做防御性复制会破坏性能目标、掩盖用户错误。" -- **魔鬼**:"库无法防止用户错误——只能通过文档和 API 设计引导正确使用。" -- **用户分析师**:"模仿 `Parallel.ForEach` 的文档风格——.NET 开发者看到 `TraverseParallel` 会自然联想到线程安全要求。" - -### 1.2 API 形态:独立方法 - -四角色一致同意:使用 `TraverseParallel` / `TraverseParallelAsync` 独立方法,不修改现有 `Traverse` 语义。 - -``` -TraverseParallel(root, visitor, options) // 同步并行 -TraverseParallelAsync(root, asyncVisitor, options, ct) // 异步并行 -TraverseForestParallel(roots, visitor, options) -TraverseForestParallelAsync(roots, asyncVisitor, options, ct) -``` - -**架构师**:"`MaxDegreeOfParallelism` 仅对并行有效,加到 Options 会污染同步遍历的语义。" - -### 1.3 异常传播:`AggregateException` - -四角色一致同意:收集所有异常,抛 `AggregateException`。 - -- **并发工程师**:`ConcurrentQueue<Exception>` 收集 → `AggregateException` 抛出。 -- **架构师**:提供 `ExceptionMode` 选项(Aggregate / FirstException / Suppress)。 -- **魔鬼**:`ContinueOnError` 选项允许部分失败后继续。 - -**合并方案**:Phase 1 默认 `AggregateException`;`ContinueOnError` / `ExceptionMode` 延迟到 Phase 2。 - -### 1.4 async 路径:`ConfigureAwait(false)` - -四角色一致同意:异步并行遍历内部使用 `ConfigureAwait(false)`。 - -- **魔鬼**:"WPF/WinForms 中 `await` 默认捕获 `SynchronizationContext`,多个并行 visitor 同时回到 UI 线程会串行化——完全违背并行初衷。" -- **并发工程师**:`SemaphoreSlim.WaitAsync(ct).ConfigureAwait(false)` + `asyncVisitor(...).ConfigureAwait(false)`。 - -### 1.5 递归深度限制 - -四角色一致同意:限制并行递归深度,超过阈值回退到顺序遍历。 - -- **并发工程师**:`MaxParallelDepth = 32`,超过后顺序递归。 -- **魔鬼**:并行递归 DFS 在循环引用"树"中会创建无限 Task,耗尽线程池——但循环检测默认关闭(开销大)。 - -### 1.6 netstandard2.0 兼容 - -四角色一致同意:`#if` 分支。 - -| TFM | 同步并行 | 异步并行 | -|-----|----------|----------| -| net8.0 | `Parallel.ForEach` | `Parallel.ForEachAsync`(.NET 6+) | -| netstandard2.0 | `Parallel.ForEach` | `SemaphoreSlim` + `Task.WhenAll` | - ---- - -## 二、存在分歧的议题 - -### 2.1 `MaxDegreeOfParallelism = 1` 的行为 - -| 角色 | 立场 | -|------|------| -| 魔鬼 | 抛 `ArgumentException` 或文档警告"不保证顺序" | -| 架构师 | 不特殊处理,用户应直接用 `Traverse` | -| 并发工程师 | 不特殊处理,`Parallel.ForEach` 的 `MaxDop=1` 已有定义 | - -**决议**:不抛异常,文档明确"`MaxDegreeOfParallelism = 1` 不保证顺序;如需顺序请用 `Traverse`"。 - -### 2.2 是否提供线程安全收集器 - -| 角色 | 立场 | -|------|------| -| 用户分析师 | **必须提供** `CollectParallel` API + `CompositeTraversalCollector<T>` | -| 架构师 | 纯文档契约,不加额外 API | -| 魔鬼 | 提供收集器重载,但不强制 | -| 并发工程师 | 不涉及(超出线程安全范围) | - -**用户分析师的关键论据**: - -```csharp -// 用户最自然的写法——会崩溃 -var errors = new List<string>(); -CompositeTraverser.TraverseParallel(root, (node, _, _) => { - if (!node.IsValid()) errors.Add($"{node.Key}: invalid"); -}); -``` - -**决议**:Phase 1 不提供收集器(保持 API 最小化);文档示例展示 `ConcurrentBag<T>` 用法;Phase 2 根据用户反馈再考虑 `CollectParallel`。 - -### 2.3 `ExceptionMode` vs `ContinueOnError` - -| 角色 | 立场 | -|------|------| -| 架构师 | `ParallelExceptionMode` 枚举(Aggregate / First / Suppress) | -| 魔鬼 | `ContinueOnError` 布尔选项 | -| 并发工程师 | 默认 `AggregateException`,不需要选项 | - -**决议**:Phase 1 只实现 `AggregateException`(默认行为)。`ContinueOnError` / `ExceptionMode` 延迟到 Phase 2 根据用户反馈再加。 - -### 2.4 默认并行度 - -| 角色 | 立场 | -|------|------| -| 魔鬼 | `Math.Min(ProcessorCount, 4)` 保守默认 | -| 架构师 | `Environment.ProcessorCount` | -| 并发工程师 | `Environment.ProcessorCount` | -| 用户分析师 | `Environment.ProcessorCount`(与 `Parallel.ForEach` 一致) | - -**魔鬼的关键论据**:Docker 容器中 `ProcessorCount` 可能返回宿主机 CPU 数而非容器限制。 - -**决议**:默认 `Environment.ProcessorCount`(与 .NET 生态一致);文档警告容器化环境需手动设置。 - -### 2.5 循环检测 - -| 角色 | 立场 | -|------|------| -| 魔鬼 | 提供 `DetectCycles` 选项(默认关闭) | -| 其他三角色 | 不需要(树结构假设无循环) | - -**决议**:不实现循环检测。文档明确"Composite 树必须无循环引用"。顺序遍历的栈溢出是快速失败,已足够。 - -### 2.6 测试可重复性 - -| 角色 | 立场 | -|------|------| -| 魔鬼 | `ForceSequentialForTesting` 选项 | -| 架构师 | 不需要选项,测试用 `ConcurrentBag` + count 断言 | -| 并发工程师 | `MaxDegreeOfParallelism = 1` 用于逻辑测试 | - -**决议**:不提供 `ForceSequentialForTesting`。测试策略: -- 逻辑正确性:`ConcurrentBag<T>` + count + 集合断言(不断言顺序) -- 竞态测试:`MaxDegreeOfParallelism = Environment.ProcessorCount` + 多次运行 - ---- - -## 三、最终技术方案 - -### 3.1 `CompositeTraversalOptions` 新增属性 - -```csharp -/// <summary> -/// Maximum degree of parallelism for parallel traversals. -/// null means Environment.ProcessorCount. -/// WARNING: Setting to 1 does NOT guarantee visitation order; -/// use Traverse() for ordered traversal. -/// </summary> -public int? MaxDegreeOfParallelism { get; set; } - -/// <summary> -/// Maximum depth for parallel recursion. Beyond this depth, -/// traversal falls back to sequential. Default is 32. -/// </summary> -public int MaxParallelDepth { get; set; } = 32; -``` - -### 3.2 同步并行实现 - -| 遍历顺序 | 策略 | -|----------|------| -| BFS | 逐层提取 → `Parallel.ForEach` 同层并行 → 收集下一层 | -| DFS PreOrder | 根节点同步访问 → 子节点 `Parallel.ForEach` 并行递归 | -| DFS PostOrder | 子节点 `Parallel.ForEach` 并行递归 → 全部完成后访问根节点 | -| 深度 > MaxParallelDepth | 回退到顺序递归 | - -### 3.3 异步并行实现 - -| TFM | 策略 | -|-----|------| -| net8.0 | `Parallel.ForEachAsync` + `ConfigureAwait(false)` | -| netstandard2.0 | `SemaphoreSlim` + `Task.WhenAll` + `ConfigureAwait(false)` | - -### 3.4 异常处理 - -```csharp -var exceptions = new ConcurrentQueue<Exception>(); - -// 每个 visitor 调用: -try { visitor(node, depth, idx); } -catch (Exception ex) { exceptions.Enqueue(ex); } - -// 遍历结束后: -if (!exceptions.IsEmpty) - throw new AggregateException(exceptions); -``` - -### 3.5 取消传播 - -- 直接传递 `CancellationToken`,不使用 `CreateLinkedTokenSource` -- `Parallel.ForEach` / `SemaphoreSlim.WaitAsync` 自动响应取消 -- 已启动的 visitor 继续完成;未启动的任务取消 - -### 3.6 文档契约(XML doc) - -``` -TraverseParallel: - "The visitor may be invoked concurrently from multiple threads. - Ensure thread safety: use ConcurrentBag<T> for accumulation, - lock/SemaphoreSlim for shared state. Order is non-deterministic; - use Traverse() for ordered traversal." - -MaxDegreeOfParallelism: - "null = Environment.ProcessorCount. Setting to 1 does NOT - guarantee order. In containerized environments, manually set - this to match CPU limits." -``` - ---- - -## 四、风险矩阵与决议 - -| 风险 | 严重度 | 库层面解决 | 决议 | -|------|--------|-----------|------| -| visitor 共享状态崩溃 | 高 | 否(用户责任) | 文档警告 + `ConcurrentBag` 示例 | -| `ShouldSkipSubtree` 竞态 | 中 | 否(用户责任) | 文档警告 | -| `Children` 懒加载非线程安全 | 高 | 否(用户责任) | 文档要求组装后不可变 | -| 死锁 | 中 | 库内部无锁 | 文档警告避免 visitor+skip 共享锁 | -| `MaxDop=1` 误导 | 中 | 文档 | 不抛异常,文档明确 | -| async `SynchronizationContext` | 高 | 是 | `ConfigureAwait(false)` | -| 异常资源泄漏 | 中 | 否(用户责任) | 文档警告 + `using` | -| 循环引用 | 高 | 否 | 文档要求无循环 | -| 容器 `ProcessorCount` | 中 | 部分 | 文档警告 | -| 测试可重复性 | 中 | 否 | 测试策略指南 | - ---- - -## 五、行动计划 - -| Phase | 内容 | 优先级 | -|-------|------|--------| -| **Phase 1** | `TraverseParallel` + `TraverseParallelAsync` + `TraverseForestParallel` + `TraverseForestParallelAsync` + `MaxDegreeOfParallelism` + `MaxParallelDepth` + `AggregateException` + `ConfigureAwait(false)` + 文档 + 测试 | 立即 | -| **Phase 2** | `ContinueOnError` 选项 + `CollectParallel` API + `ExceptionMode` 枚举 | 根据用户反馈 | -| **Phase 3** | 懒加载(`ICompositeLazyNode` + `[CompositePart(LazyChildren)]` + 生成器) | 独立 RFC | - ---- - -## 六、用户场景分析摘要 - -### 适合并行遍历的场景 - -| 场景 | 原因 | -|------|------| -| 配置树校验(CPU 密集) | 各节点校验独立,并行收益明显 | -| 远程 API 调用(I/O 密集 async) | 并发度从 1 提升到 N,延迟降低数量级 | -| 日志/遥测收集 | `ILogger` 实现线程安全 | -| Forest 遍历(多棵独立树) | 最安全的并行场景,无共享状态 | -| DI 容器解析 | `IServiceProvider` 线程安全 | - -### 不适合并行遍历的场景 - -| 场景 | 原因 | -|------|------| -| UI 菜单树渲染 | UI dispatcher 单线程瓶颈,并行收益有限 | -| 写入同一个 `Stream` | `Stream.Write` 非线程安全 | -| 向 `StringBuilder` 追加 | `StringBuilder` 非线程安全 | -| 向 `List<T>.Add` 收集 | `List<T>.Add` 非线程安全(最常见误用) | - -### 用户最常见误用模式 - -```csharp -// ❌ 会崩溃——List<T>.Add 非线程安全 -var results = new List<string>(); -CompositeTraverser.TraverseParallel(root, (node, _, _) => results.Add(node.Name)); - -// ✅ 正确——ConcurrentBag<T> 线程安全 -var results = new ConcurrentBag<string>(); -CompositeTraverser.TraverseParallel(root, (node, _, _) => results.Add(node.Name)); -``` diff --git a/docs/rfc/archive/CompositeTreeSchemaValidation.md b/docs/rfc/archive/CompositeTreeSchemaValidation.md deleted file mode 100644 index 12a0476..0000000 --- a/docs/rfc/archive/CompositeTreeSchemaValidation.md +++ /dev/null @@ -1,292 +0,0 @@ -# RFC: Composite 树 schema 校验 - -> **状态**:Implemented -> **类型**:Feature -> **创建**:2026-07-15 -> **更新**:2026-07-07 -> **作者**:维护者 -> **关联 Roadmap**:F5+ -> **关联 Issue**:#218 -> **衍生 ADR**:[ADR-007](../../adr/ADR-007-composite-tree-schema-validation.md) - -## 概述 - -为 Composite 模式新增编译期树结构 schema 校验:**最大深度**、**父子类型兼容性**、**节点计数**。在源生成器阶段从 flat parent-key 映射计算树结构并报告诊断,无需运行时开销。 - -现有 Composite 诊断(DP010–DP015, DP040–DP041)覆盖键唯一性、父键存在性、循环检测、契约实现、构造函数、DI 注册等基础校验,但**不**校验树的整体结构形态。本 RFC 补齐这一层。 - -## 设计目标 - -| 目标 | 说明 | -|------|------| -| 编译期捕获结构错误 | 深度过大可能导致栈溢出;类型不匹配的父子关系是设计错误;节点过多暗示架构问题 | -| 零运行时开销 | 所有校验在源生成器阶段完成,不增加运行时成本 | -| 向后兼容 | 所有新约束均为 opt-in,不标注则不校验(现有代码行为不变) | -| 探索价值 | 展示「源生成器从 flat 声明重建树拓扑并在编译期校验」的编译期 + 运行时协同模式 | - ---- - -## 一、API 设计 - -### 1.1 `CompositeSchemaAttribute`(契约级约束) - -新增特性,标注在**契约接口/基类**上,声明该契约的全局树结构约束: - -```csharp -[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class, Inherited = false, AllowMultiple = false)] -public sealed class CompositeSchemaAttribute : Attribute -{ - /// <summary>Maximum tree depth (root = depth 1). 0 = no limit. Default = 0.</summary> - public int MaxDepth { get; set; } - - /// <summary>Maximum total node count across all roots. 0 = no limit. Default = 0.</summary> - public int MaxNodes { get; set; } -} -``` - -**用法示例**: - -```csharp -[CompositeSchema(MaxDepth = 10, MaxNodes = 500)] -public interface IMenuNode : ICompositeNode<IMenuNode> { } -``` - -### 1.2 `CompositePartAttribute.AllowedChildTypes`(节点级约束) - -在现有 `CompositePartAttribute` 上新增可选属性,声明该节点允许的子节点实现类型: - -```csharp -// 新增到现有 CompositePartAttribute -public Type[]? AllowedChildTypes { get; set; } -``` - -**用法示例**: - -```csharp -[CompositePart("root", typeof(IMenuNode))] -public partial class MenuRoot : IMenuNode, ICompositeBuildable<IMenuNode> { } - -[CompositePart("panel", typeof(IMenuNode), ParentKey = "root", AllowedChildTypes = new[] { typeof(PanelNode), typeof(LeafNode) })] -public partial class PanelNode : IMenuNode, ICompositeBuildable<IMenuNode> { } - -[CompositePart("leaf", typeof(IMenuNode), ParentKey = "panel")] -// OK: LeafNode 的父节点 panel 允许 LeafNode -public partial class LeafNode : IMenuNode, ICompositeBuildable<IMenuNode> { } - -[CompositePart("section", typeof(IMenuNode), ParentKey = "root", AllowedChildTypes = new[] { typeof(SectionNode) })] -// ERROR: SectionNode 的父节点 root 未在 AllowedChildTypes 中声明 SectionNode -public partial class SectionNode : IMenuNode, ICompositeBuildable<IMenuNode> { } -``` - -**向后兼容**:`AllowedChildTypes = null`(默认)表示不限制——任何实现契约的类型都可作为子节点,与现有行为一致。 - -### 1.3 泛型变体同步 - -`CompositePartAttribute<TContract>` 也新增 `AllowedChildTypes` 属性。`CompositeSchemaAttribute` 不需要泛型变体(它标注在契约类型本身上,无需泛型参数)。 - ---- - -## 二、诊断 ID - -| ID | 名称 | 严重性 | 归属 | 触发条件 | -|----|------|--------|------|----------| -| DP063 | CompositeTreeMaxDepthExceeded | Warning | SourceGenerators | 树的实际深度超过 `[CompositeSchema(MaxDepth)]` 声明值 | -| DP064 | CompositeChildTypeNotAllowed | Error | SourceGenerators | 子节点的实现类型不在父节点的 `AllowedChildTypes` 集合中 | -| DP065 | CompositeNodeCountExceeded | Warning | SourceGenerators | 契约的总节点数超过 `[CompositeSchema(MaxNodes)]` 声明值 | - -### 2.1 诊断消息格式 - -**DP063**: -- Title: "Composite tree max depth exceeded" -- Message: "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." -- Description: "Excessively deep composite trees can cause stack overflows in recursive traversal. Review the tree structure or adjust the MaxDepth constraint." - -**DP064**: -- Title: "Composite child type not allowed by parent" -- Message: "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." -- Description: "Parent-child type compatibility is enforced at compile time when AllowedChildTypes is specified. Ensure child implementation types are declared in the parent's AllowedChildTypes array." - -**DP065**: -- Title: "Composite node count exceeds limit" -- Message: "Composite contract '{0}' has {1} parts, exceeding the maximum of {2} declared by [CompositeSchema]. Consider splitting into multiple contracts or increasing MaxNodes." -- Description: "Excessive node counts may indicate architectural issues or cause performance problems in tree assembly and traversal." - -### 2.2 诊断报告位置 - -- **DP063**:报告在**最深的叶子节点**的 `[CompositePart]` 特性位置(指向造成超限的路径终点) -- **DP064**:报告在**子节点**的 `[CompositePart]` 特性位置(指向不被允许的子节点) -- **DP065**:报告在**契约类型**声明位置(`[CompositeSchema]` 标注的接口/类) - ---- - -## 三、生成器实现 - -### 3.1 数据流 - -现有管线不变。新增校验在 `ReportDiagnostics` 阶段执行(与现有 DP010–DP012 同层): - -``` -ForAttributeWithMetadataName("CompositePartAttribute") ← 现有 -ForAttributeWithMetadataName("CompositePartAttribute`1") ← 现有 - → Transform → CompositeRegistration[] ← 现有 - → Collect + Combine ← 现有 - → ReportDiagnostics ← 新增 schema 校验 - → Emit ← 现有 -``` - -### 3.2 深度计算 - -从 flat parent-key 映射计算树深度(复用现有 `BuildParentMap` 基础设施): - -```csharp -// 伪代码 -static int ComputeDepth(string key, IReadOnlyDictionary<string, string?> parentByKey) -{ - var depth = 1; // root = depth 1 - var children = entries.Where(e => e.ParentKey == key); - foreach (var child in children) - { - depth = Math.Max(depth, 1 + ComputeDepth(child.Key, parentByKey)); - } - return depth; -} -``` - -**优化**:使用 memoization 避免重复计算(节点数 N,O(N) 时间 + O(N) 空间)。 - -**安全**:循环检测已在 DP012 完成,深度计算不会无限递归。 - -### 3.3 父子类型兼容性校验 - -```csharp -// 伪代码 -foreach (var entry in registrations) -{ - if (entry.ParentKey is null) continue; - if (!entryByKey.TryGetValue(entry.ParentKey, out var parent)) continue; // DP011 已处理 - - var allowedTypes = parent.AllowedChildTypes; - if (allowedTypes is null || allowedTypes.Length == 0) continue; // 不限制 - - if (!allowedTypes.Contains(entry.ImplementationType)) - { - context.ReportDiagnostic(DP064, entry.Location, ...); - } -} -``` - -**注意**:`AllowedChildTypes` 存储的是 `Type[]`,在生成器中需要从特性参数提取 `INamedTypeSymbol` 并比较完全限定名。 - -### 3.4 节点计数 - -```csharp -// 伪代码 -var schemaAttr = contractType.GetAttributes() - .FirstOrDefault(a => a.AttributeClass?.Name == "CompositeSchemaAttribute"); - -if (schemaAttr != null) -{ - var maxNodes = ExtractIntArgument(schemaAttr, "MaxNodes"); - if (maxNodes > 0 && registrations.Count > maxNodes) - { - context.ReportDiagnostic(DP065, contractTypeLocation, ...); - } -} -``` - -### 3.5 `CompositeSchemaAttribute` 提取 - -`CompositeSchemaAttribute` 标注在契约接口上,不在 `[CompositePart]` 的目标类上。生成器需要额外提取: - -1. 在 Transform 阶段,从 `INamedTypeSymbol contractType` 的 `GetAttributes()` 查找 `CompositeSchemaAttribute` -2. 提取 `MaxDepth` 和 `MaxNodes` 值 -3. 存入 `CompositeRegistration` 或单独的 schema 模型 - -**模型扩展**:在 `CompositeRegistration` 中新增 `int? SchemaMaxDepth` 和 `int? SchemaMaxNodes` 字段(nullable,null = 未标注 `[CompositeSchema]`)。或者使用单独的 `CompositeSchemaInfo` 模型,按契约分组。 - -选择后者(单独模型),避免每个 `CompositeRegistration` 重复存储: - -```csharp -private sealed record CompositeSchemaInfo( - ContractInfo Contract, - int? MaxDepth, - int? MaxNodes, - LocationInfo? Location); -``` - -在 Combine 阶段,按 Contract 分组提取 schema 信息,与 registrations 一起传入 ReportDiagnostics。 - ---- - -## 四、向后兼容性 - -| 变更 | 影响 | -|------|------| -| 新增 `CompositeSchemaAttribute` | 无影响——未标注时不触发任何新诊断 | -| `CompositePartAttribute.AllowedChildTypes` 新属性 | 无影响——`null` 默认值表示不限制 | -| 新增 DP063/DP064/DP065 | 无影响——仅在 opt-in 约束被声明时才报告 | - -**无破坏性变更**:所有现有 `[CompositePart]` 代码无需修改即可继续工作。 - ---- - -## 五、测试计划 - -### 5.1 生成器单元测试(Verify + 诊断断言) - -| 测试 | 验证点 | -|------|--------| -| `ReportsDp063_MaxDepthExceeded` | `[CompositeSchema(MaxDepth = 2)]` + 3 层树 → DP063 Warning | -| `ReportsDp063_NotExceeded_WhenWithinLimit` | `[CompositeSchema(MaxDepth = 5)]` + 3 层树 → 无 DP063 | -| `ReportsDp064_ChildTypeNotAllowed` | 父节点 `AllowedChildTypes = [A, B]`,子节点类型 C → DP064 Error | -| `ReportsDp064_Allowed_WhenTypeInList` | 父节点 `AllowedChildTypes = [A, B]`,子节点类型 B → 无 DP064 | -| `ReportsDp064_NotChecked_WhenAllowedChildTypesNull` | 父节点未设 `AllowedChildTypes` → 无 DP064(任意类型均可) | -| `ReportsDp065_NodeCountExceeded` | `[CompositeSchema(MaxNodes = 3)]` + 5 个节点 → DP065 Warning | -| `ReportsDp065_NotExceeded_WhenWithinLimit` | `[CompositeSchema(MaxNodes = 10)]` + 5 个节点 → 无 DP065 | -| `NoSchemaAttribute_NoNewDiagnostics` | 无 `[CompositeSchema]` + 深树 + 多节点 → 无 DP063/DP065 | -| `MaxDepth_ForestMode_ComputesPerTree` | 多根森林,最深树超过限制 → DP063 报告最深树 | -| `AllowedChildTypes_GenericAttribute` | 泛型 `[CompositePart<T>]` + `AllowedChildTypes` → DP064 正确触发 | - -### 5.2 运行时测试 - -无需新增运行时测试——所有校验在编译期完成,运行时行为不变。 - -### 5.3 集成测试 - -新增一个使用 `[CompositeSchema]` + `AllowedChildTypes` 的集成测试项目,验证端到端生成 + 组装 + 遍历流程。 - ---- - -## 六、实现范围 - -| 文件 | 变更 | -|------|------| -| `DesignPatterns/Structural/CompositeSchemaAttribute.cs` | **新增** — 契约级特性 | -| `DesignPatterns/Structural/CompositePartAttribute.cs` | **修改** — 新增 `AllowedChildTypes` 属性 | -| `DesignPatterns/Structural/CompositePartAttribute.TContract.cs` | **修改** — 新增 `AllowedChildTypes` 属性 | -| `DesignPatterns.Diagnostics/DiagnosticIds.cs` | **修改** — 新增 DP063/DP064/DP065 常量 | -| `DesignPatterns.Diagnostics/DesignPatternsDiagnosticDescriptors.cs` | **修改** — 新增 3 个描述符 | -| `DesignPatterns.SourceGenerators/Generators/CompositePartGenerator.cs` | **修改** — 提取 schema、深度计算、类型校验、节点计数 | -| `DesignPatterns.SourceGenerators/AnalyzerReleases.Unshipped.md` | **修改** — 新增 DP063/DP064/DP065 条目 | -| `tests/DesignPatterns.SourceGenerators.Tests/Generators/CompositePartGeneratorTests.cs` | **修改** — 新增 10 个测试 | -| `tests/DesignPatterns.Tests/Structural/CompositeSchemaAttributeTests.cs` | **新增** — 特性构造测试 | -| `docs/spec/Composite.md` + `docs/design/Composite.md` | **修改** — 文档补充 schema 校验说明 | -| `docs/ROADMAP.md` | **修改** — 标记完成 | - -**PR 边界**:跨 Runtime + Diagnostics + SourceGenerators 三个模块。按 AGENTS.md 规范,应拆分为: -1. **Runtime PR**:`CompositeSchemaAttribute` + `CompositePartAttribute.AllowedChildTypes` -2. **Diagnostics + SourceGenerators PR**:DP063/DP064/DP065 + 生成器校验逻辑 + 测试 - -或合并为一个 PR(因为特性 + 诊断 + 校验紧密耦合,拆分后第一个 PR 无法独立验证)。选择合并。 - ---- - -## 七、探索价值 - -本 RFC 展示以下编译期 + 运行时协同技术: - -1. **从 flat 声明重建树拓扑**:源生成器从 `ParentKey` 字符串引用重建完整树结构,在编译期计算深度、验证类型兼容性——无需运行时反射或动态分析 -2. **opt-in 约束的渐进式类型安全**:`AllowedChildTypes` 允许用户逐步收紧类型约束,从「任意契约实现」到「特定实现类型集合」,编译期捕获设计错误 -3. **契约级 vs 节点级约束的分层**:`CompositeSchemaAttribute`(契约级全局约束)与 `AllowedChildTypes`(节点级局部约束)正交组合,覆盖不同粒度的校验需求 - -与 Stateless 等运行时库的对比:本方案将结构校验前移到编译期,运行时零开销;Stateless 等库在运行时动态验证状态机配置,有反射和异常成本。 diff --git a/docs/rfc/archive/HierarchicalStateMachine.md b/docs/rfc/archive/HierarchicalStateMachine.md deleted file mode 100644 index 8ac0470..0000000 --- a/docs/rfc/archive/HierarchicalStateMachine.md +++ /dev/null @@ -1,551 +0,0 @@ -# RFC: State 层级状态机(编译期展平) - -> **状态**:Implemented -> **类型**:Pattern -> **创建**:2026-06-28 -> **更新**:2026-07-07 -> **作者**:维护者 -> **关联 Roadmap**:F3 -> **关联 Issue**:— -> **衍生 ADR**:[ADR-005](../../adr/ADR-005-state-transition-table.md)(与 StateTransitionTable RFC 共享 ADR) - ---- - -## 1. 摘要 - -在现有 State 转换表(v2:扁平 enum 图 + guard + entry/exit action + DI)基础上,增加 **层级状态(hierarchical states)** 支持:允许状态声明父状态,子状态自动继承父状态的转换边,父状态的 entry/exit action 在进入/离开子状态时按 UML 状态机语义链式触发。 - -**核心技术路线**:**编译期展平(compile-time flattening)**——源生成器在编译期将层级关系展开为扁平查找表,运行时 API 保持 `Dictionary<(TState, TTrigger), …>` 的 O(1) 查找性能,不引入运行时层级引擎。这与 ROADMAP 中「生成器展平为快表」的描述一致。 - -**不是**完整 UML 状态机框架:不做历史状态(history)、并发状态(orthogonal regions)、持久化快照。与 Stateless 的边界见 §9。 - ---- - -## 2. 动机 - -### 2.1 现有局限 - -当前 State 转换表(v2)是**扁平图**:每个 `(state, trigger)` 至多一条边,无父子关系。当领域模型存在自然层级时(见下例),扁平表需要为每个子状态重复声明相同的转换,导致冗余且易遗漏。 - -```text -订单状态层级: - Active - ├── Submitted - └── Paid - Cancelled - Draft - -问题:Submitted 和 Paid 都应响应 Cancel → Cancelled。 -扁平表需要两条独立边: - [Transition(Submitted, Cancel, Cancelled)] - [Transition(Paid, Cancel, Cancelled)] -层级表只需一条父级边: - [Transition(Active, Cancel, Cancelled)] ← 子状态自动继承 -``` - -### 2.2 为何现在 - -- F1/F2/F2+ 全部完成,0.2.0-preview3 已发布,功能主线收敛。 -- State v2(guard + entry/exit + DI + tracing)已稳定,层级状态是 v1 RFC §2.3 明确列出的「暂不做」项中**探索价值最高**的(⭐⭐⭐)。 -- 生成器管线已模块化(Transform → Validate → Emit),扩展层级元数据收集与展平逻辑的侵入性低。 -- 与 Stateless 重叠但展示「编译期展平层级」技术——这正是本库「编译期 + 运行时协同」探索方针的核心体现。 - -### 2.3 非目标 - -- **历史状态**(shallow/deep history)——UML 超集,运行时需要记住最后活跃子状态,与「编译期展平」路线冲突。 -- **并发状态**(orthogonal regions / parallel states)——需要多活跃状态,当前 `IStateMachine.CurrentState` 是单值。 -- **运行时动态层级修改**——层级在编译期固定;运行时不可增删父子关系。 -- **持久化快照 / 分布式状态 / 超时转换**——与 v1 非目标一致。 -- **`string` / `int` 状态键**——仍仅支持 `enum`。 - ---- - -## 3. 设计原则 - -1. **编译期展平,运行时扁平**:生成器在编译期将层级关系展开为扁平边表;运行时查找逻辑与 v2 完全一致(O(1) Dictionary lookup),不引入层级遍历开销。 -2. **子优先继承**:子状态的直接边覆盖父状态的继承边(most-specific wins)。 -3. **Action 链由生成器合成**:entry/exit action 链在编译期合成为复合委托,运行时 `TransitionEdge` 结构不变(仍为单个 `OnEnter` / `OnExit` 委托,但委托体内部按序调用链)。 -4. **向后兼容**:未声明层级的现有状态机行为不变;层级是 opt-in。 -5. **层级元数据可选暴露**:生成器额外输出 `IStateHierarchy<TState>` 实现,供 `IsInState` / `GetParent` 查询;不影响转换查找路径。 - ---- - -## 4. 概念模型 - -```text - Cancel trigger - [Active] ─────────────────► [Cancelled] - │ - ├── [Submitted] ──Pay──► [Paid] - │ │ - │ Submit - │ │ - │ ▼ - │ (self-loop or edge) - │ - └── [Paid] - - [Draft] ──Submit──► [Submitted] -``` - -- **父状态(parent state)**:一个 enum 成员,声明为其他状态的父。父状态本身可以是叶子(有出边)也可以是纯容器(无自身边,仅提供继承边)。 -- **子状态(child state)**:声明了 `Parent` 的状态。子状态继承父状态的所有转换边(除非自身有同 trigger 的直接边)。 -- **祖先链(ancestor chain)**:从当前状态向上到根的路径:`Submitted → Active → (root)`。 -- **LCA(最低公共祖先)**:转换 `from → to` 时,from 和 to 的最近公共祖先。exit action 从 from 向上执行到 LCA(不含 LCA);entry action 从 LCA 向下执行到 to(不含 LCA)。 - -### 4.1 转换解析规则 - -当 `TryTransition(current, trigger)` 被调用时: - -1. 查找 `(current, trigger)` 的直接边 → 命中则使用 -2. 若未命中,查找 `(parent(current), trigger)` → 命中则使用 -3. 继续向上直到根 → 命中则使用 -4. 全部未命中 → 返回 `false` - -**编译期展平**:生成器在编译期执行上述解析,为每个 `(state, trigger)` 对预计算有效边,写入扁平表。运行时只需一次 Dictionary 查找。 - -### 4.2 Entry/Exit Action 链 - -转换 `Submitted → Cancelled`(LCA = root,即无公共祖先): - -```text -1. Exit Submitted (子 exit action) -2. Exit Active (父 exit action) -3. Enter Cancelled (entry action) -``` - -转换 `Submitted → Paid`(LCA = Active,父未变): - -```text -1. Exit Submitted (子 exit action) -2. Enter Paid (子 entry action) -(Active 的 exit/enter 不触发——父状态未变) -``` - -**编译期合成**:生成器为每条展平后的边计算 action 链,生成复合委托: - -```csharp -// 生成代码(示意) -static void ExitChain_Submitted_To_Cancelled(TState from, TState to, TTrigger trigger) -{ - OnExitSubmitted(from, to, trigger); // 子 exit - OnExitActive(from, to, trigger); // 父 exit -} -static void EnterChain_Submitted_To_Cancelled(TState from, TState to, TTrigger trigger) -{ - OnEnterCancelled(from, to, trigger); -} -``` - ---- - -## 5. 运行时 API(草案) - -### 5.1 层级元数据接口(新增) - -```csharp -namespace DesignPatterns.Behavioral; - -/// <summary> -/// Optional hierarchy metadata for hierarchical state machines. -/// Generated tables implement this interface when Hierarchical = true. -/// </summary> -public interface IStateHierarchy<in TState> - where TState : struct, Enum -{ - /// <summary> - /// Returns the parent state of <paramref name="state"/>, or null if it is a root. - /// </summary> - TState? GetParent(TState state); - - /// <summary> - /// Returns true if <paramref name="current"/> is <paramref name="ancestor"/> - /// or a descendant of <paramref name="ancestor"/>. - /// </summary> - bool IsInState(TState current, TState ancestor); - - /// <summary> - /// Returns the ancestor chain from <paramref name="state"/> up to root (exclusive of root). - /// </summary> - IReadOnlyList<TState> GetAncestors(TState state); -} -``` - -### 5.2 ITransitionTable 扩展 - -`ITransitionTable<TState, TTrigger>` **不**新增成员。层级表的生成实现额外实现 `IStateHierarchy<TState>`: - -```csharp -// 生成产物 -public sealed partial class OrderStatusTransitionTable - : ITransitionTable<OrderStatus, OrderTrigger> - , IStateHierarchy<OrderStatus> // 仅 Hierarchical = true 时 -{ ... } -``` - -消费者可通过模式匹配访问层级元数据: - -```csharp -if (table is IStateHierarchy<OrderStatus> hierarchy) -{ - bool isActive = hierarchy.IsInState(current, OrderStatus.Active); -} -``` - -### 5.3 扩展方法 - -```csharp -namespace DesignPatterns.Behavioral; - -public static class StateHierarchyExtensions -{ - /// <summary> - /// Attempts to cast the table to IStateHierarchy and query IsInState. - /// Returns false if the table is not hierarchical. - /// </summary> - public static bool IsInState<TState, TTrigger>( - this ITransitionTable<TState, TTrigger> table, - TState current, TState ancestor) - where TState : struct, Enum - where TTrigger : struct, Enum - => table is IStateHierarchy<TState> h && h.IsInState(current, ancestor); -} -``` - -### 5.4 手动 Builder 扩展 - -```csharp -public sealed class TransitionTableBuilder<TState, TTrigger> - where TState : struct, Enum - where TTrigger : struct, Enum -{ - // 现有 API 不变 - - /// <summary>Declares a parent-child relationship. Opt-in for hierarchical mode.</summary> - public TransitionTableBuilder<TState, TTrigger> WithParent( - TState child, TState parent); -} -``` - -`Build()` 返回的 `ITransitionTable` 实现在 `WithParent` 被调用后额外实现 `IStateHierarchy<TState>`。手动 Builder 不做展平(用户需自行添加所有有效边);层级元数据仅用于 `IsInState` 查询。 - ---- - -## 6. 编译期 API(草案) - -### 6.1 新增特性 - -```csharp -namespace DesignPatterns.Behavioral; - -/// <summary> -/// Declares a parent state for a child state in a hierarchical state machine. -/// Apply on the holder class, once per child state that has a parent. -/// </summary> -[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] -public sealed class StateParentAttribute : Attribute -{ - public StateParentAttribute(object child, object parent) { ... } -} -``` - -`[StateMachine]` 新增属性: - -```csharp -public sealed class StateMachineAttribute : Attribute -{ - // 现有成员不变 - - /// <summary> - /// Enables hierarchical state machine mode. - /// When true, [StateParent] attributes are collected and transitions are flattened. - /// Default: false (backward compatible). - /// </summary> - public bool Hierarchical { get; set; } -} -``` - -### 6.2 用法示例 - -```csharp -public enum OrderStatus -{ - Draft, - Active, // composite/parent state - Submitted, - Paid, - Cancelled, -} - -public enum OrderTrigger -{ - Submit, - Pay, - Cancel, -} - -[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)] - -// 父级继承边:Submitted 和 Paid 都继承此边 -[Transition(OrderStatus.Active, OrderTrigger.Cancel, OrderStatus.Cancelled)] - -// 父级 entry/exit action(子状态进入/离开时链式触发) -[Transition(OrderStatus.Active, OrderTrigger.Cancel, OrderStatus.Cancelled, - OnExit = nameof(OnExitActive))] -public static partial class OrderStatusMachine; - -static partial void OnExitActive(OrderStatus from, OrderStatus to, OrderTrigger trigger) - => Console.WriteLine($"Exiting Active (was in {from})"); -``` - -### 6.3 生成产物 - -| 生成类型 | 说明 | -|----------|------| -| `OrderStatusTransitionTable` | 扁平表(展平后)+ `IStateHierarchy<OrderStatus>` 实现 | -| `OrderStatusMachine`(partial 扩展) | 便捷方法(与 v2 一致) | -| `OrderStatusStateMachine` | `IStateMachine<OrderStatus, OrderTrigger>` 包装器 | - -展平后的表等效于: - -```text -(Draft, Submit) → Submitted -(Submitted, Pay) → Paid -(Submitted, Cancel) → Cancelled ← 从 Active 继承 -(Paid, Cancel) → Cancelled ← 从 Active 继承 -``` - -### 6.4 生成器实现要点 - -在现有 `StateTransitionGenerator` 管线上扩展: - -1. **Transform 阶段**: - - 当 `Hierarchical = true` 时,额外收集 `[StateParent]` 特性 - - 构建 `Dictionary<TState, TState>` parent map - - 检测循环(DP056) - -2. **Validate 阶段**(新增诊断): - - DP056:层级循环(A → B → A) - - DP057:`[StateParent]` 的 child 或 parent 不是声明的状态 enum 成员 - - DP058:`[StateParent]` 的 parent 等于 child(自引用) - - DP059:父状态从未作为 `from` 出现且无子状态(Info,孤立父状态) - -3. **Flatten 阶段**(新增): - - 对每个 `(state, trigger)` 对,沿祖先链向上查找有效边 - - 子状态直接边优先(覆盖父级边) - - 为每条展平边计算 entry/exit action 链(LCA 算法) - - 生成复合委托包装 action 链 - -4. **Emit 阶段**: - - 输出扁平表(与 v2 格式一致,但边数可能更多) - - 额外输出 `IStateHierarchy<TState>` 实现(`GetParent` / `IsInState` / `GetAncestors`) - - 生成 `StateParentMap` 静态字段供层级查询 - ---- - -## 7. 诊断 ID(DP056–DP059) - -下一可用 ID:**DP056**。 - -| ID | 级别 | 归属 | 触发条件 | -|----|------|------|----------| -| **DP056** | Error | 生成器 | 层级循环:状态 A 的父链经 B、C…回到 A | -| **DP057** | Error | 生成器 | `[StateParent]` 的 child 或 parent 不是 `[StateMachine]` 声明的状态 enum 成员 | -| **DP058** | Error | 生成器 | `[StateParent]` 的 parent 等于 child(自引用) | -| **DP059** | Info | 生成器 | 父状态从未作为 `from` 出现且无子状态声明它为父(孤立父状态) | - -**CodeFix**:暂不提供(层级关系需人工判断)。 - ---- - -## 8. Entry/Exit Action 链算法 - -### 8.1 LCA 计算 - -```text -function LCA(a, b): - ancestors_a = [a] + GetAncestors(a) - ancestors_b = [b] + GetAncestors(b) - return first common element in ancestors_a ∩ ancestors_b - (or null if no common ancestor = root) -``` - -### 8.2 Exit 链(from → LCA,不含 LCA) - -```text -function ExitChain(from, lca): - chain = [] - current = from - while current != lca and current != null: - if HasOnExit(current): chain.append(GetOnExit(current)) - current = GetParent(current) - return chain // 执行顺序:from → ... → child-of-lca -``` - -### 8.3 Enter 链(LCA → to,不含 LCA,逆序执行) - -```text -function EnterChain(to, lca): - chain = [] - current = to - while current != lca and current != null: - if HasOnEnter(current): chain.prepend(GetOnEnter(current)) - current = GetParent(current) - return chain // 执行顺序:child-of-lca → ... → to -``` - -### 8.4 同级转换(LCA = from 或 LCA = to) - -- `from == to`(自环):exit(from) → enter(from) -- `LCA == from`(进入后代):仅 enter 链(from 的 exit 不触发) -- `LCA == to`(从后代回到祖先):仅 exit 链(to 的 enter 不触发) - ---- - -## 9. 与 Stateless 的边界 - -| | 本库层级状态机 | [Stateless](https://github.com/dotnet-state-machine/stateless) | -|--|-------------------|----------------------------------------------------------------| -| 层级模型 | 编译期声明 + 展平 | 运行时 fluent API(`SubstateOf`) | -| 查找性能 | O(1) 扁平表(展平后) | O(depth) 运行时向上查找 | -| 层级修改 | 编译期固定 | 运行时可配置 | -| 历史状态 | 不支持 | 支持(`OnEntry` from history) | -| 并发状态 | 不支持 | 支持(`DefineParallelState`) | -| Action 链 | 编译期合成委托 | 运行时动态调用 | -| 诊断 | 编译期 DP056–DP059 | 无编译期诊断 | -| 适用 | 层级固定、追求零运行时开销 | 需要运行时灵活性 / 历史状态 / 并发 | - -**指导**:层级关系在编译期已知且不需运行时修改 → 本库;需要 `SubstateOf` 动态配置 / 历史状态 / 并发区域 → Stateless。 - ---- - -## 10. 交付计划 - -| 阶段 | 内容 | 依赖 | -|------|------|------| -| **v3.1** | 运行时 `IStateHierarchy<TState>` + `TransitionTableBuilder.WithParent` + `IsInState` 扩展方法 | 无 | -| **v3.2** | 生成器:`[StateParent]` 收集 + 展平算法 + DP056–DP059 诊断 | v3.1 | -| **v3.3** | 生成器:entry/exit action 链合成 + LCA 算法 | v3.2 | -| **v3.4** | DI 集成(`IStateHierarchy` 注册与解析) + Sample + Docs | v3.3 | - -每个阶段独立 PR,遵守单模块 PR 边界。 - ---- - -## 11. 开放问题(已决策) - -> 以下 5 个开放问题经 4 位评审委员(运行时性能 / API 设计 / 生成器实现 / 领域建模)并行评审后达成共识,转为最终决策。 - -### Q1:父状态能否也是活跃状态?→ **决策:允许** - -**问题**:`Active` 既是 `Submitted`/`Paid` 的父,能否也是 `TryTransition` 的 `from`(即直接处于 `Active` 状态而非其子状态)? - -**决策**:允许。父状态本身可以是叶子状态。`IsInState(Active, Active)` 返回 `true`。展平时 `(Active, trigger)` 的直接边与子状态继承的边独立存在。 - -**评审共识**(4/4 一致): -- 运行时:零开销,无需特殊处理,展平算法统一处理所有状态 -- API 设计:与 enum 模型一致,无需引入「container-only」概念 -- 生成器:最简路径,`StateMachineModel` 结构不变 -- 领域建模:符合 UML 状态图语义,Stateless 同样允许;拒绝则需人造「默认子状态」增加复杂度 - -### Q2:多重继承?→ **决策:不支持** - -**问题**:一个子状态能否有多个父状态? - -**决策**:不支持。单继承(每个状态至多一个 parent)。多重继承引入 LCA 歧义和复杂度,不符合「primitive」定位。需要多重继承的场景用 Stateless。 - -**评审共识**(4/4 一致): -- 运行时:单继承 = `Dictionary<TState, TState>` O(depth) 遍历;多继承 = DAG 图遍历 + 菱形歧义 -- API 设计:`GetParent(TState) → TState?` 简洁;多继承需 `GetParents → IReadOnlyList` + 冲突解决规则 -- 生成器:单继承展平算法线性;多继承需拓扑排序 + C3 线性化,生成代码体积爆炸 -- 领域建模:UML 和 Stateless 均用单继承树结构;真实领域模型几乎总是树形 - -### Q3:通配转换(wildcard transition)?→ **决策:v3 不做** - -**问题**:ROADMAP 提及「通配转换」。是否需要一个特殊的「任意状态」枚举值或特性来声明全局 fallback 边? - -**决策**:v3 不做。父级继承边已覆盖大部分「通配」语义(在父状态上声明边即对所有子状态生效)。如果后续有需求,可评估 `[Transition(Any, trigger, to)]` 语法。 - -**评审共识**(4/4 一致): -- 运行时:通配需运行时 fallback 逻辑,破坏 O(1) 查找;父级继承通过编译期展平保持 O(1) -- API 设计:显式优于隐式;父级声明使继承关系清晰可追溯 -- 生成器:通配需特殊语法解析 + 优先级排序 + 歧义诊断,管线复杂度中等 -- 领域建模:UML 无通配转换;Stateless 不支持;父级继承是「Ultimate Hook」模式的正确表达 - -### Q4:`GetAllowedTriggers` 是否包含继承的触发器?→ **决策:包含** - -**问题**:当 `Submitted` 没有直接 `Cancel` 边但 `Active` 有时,`GetAllowedTriggers(Submitted)` 是否返回 `Cancel`? - -**决策**:包含。展平后 `Submitted` 的有效边包含继承的 `Cancel`,`GetAllowedTriggers` 返回所有有效触发器。 - -**评审共识**(4/4 一致): -- 运行时:与编译期展平一致,`_triggersByState` 字典预构建,零运行时遍历 -- API 设计:方法名是 `GetAllowedTriggers` 非 `GetDirectlyDeclaredTriggers`;语义正确性要求返回有效触发器 -- 生成器:直接查询展平表,无需额外逻辑——实际上比排除继承触发器更简单 -- 领域建模:UML 状态「is in」所有超态;Stateless `PermittedTriggers` 包含继承触发器;UI 按钮启用/禁用依赖此行为 - -### Q5:`TransitionTrace` 如何表示 action 链?→ **决策:状态列表 + count + 失败定位** - -**问题**:现有 `TransitionTrace` 有 `OnExitCompleted` / `OnEnterCompleted` 两个 bool。层级 action 链可能有多个 exit/enter action。 - -**决策**:综合 4 位委员意见,采用状态列表为主 + count 快速路径 + 简化失败定位: - -```csharp -public readonly struct TransitionTrace<TState> - where TState : struct, Enum -{ - // 现有字段保留(向后兼容) - public bool Succeeded { get; } - public TState NextState { get; } - public bool OnExitCompleted { get; } // = ExitActions 全部完成 - public bool OnEnterCompleted { get; } // = EnterActions 全部完成 - public Exception? Exception { get; } - - // 新增:性能梯度(bool → count → 状态列表 → 失败定位) - public int ExitActionCount { get; } // 零分配快速路径 - public int EnterActionCount { get; } - public IReadOnlyList<TState> StatesExited { get; } // 语义清晰:哪些状态被 exit - public IReadOnlyList<TState> StatesEntered { get; } // 语义清晰:哪些状态被 enter - public string? FailedActionName { get; } // 定位失败点(简化版) - public TState? FailedActionState { get; } // 失败发生在哪个状态 -} -``` - -**设计理由**: -- **状态列表(enum 值)而非方法名(string)**:用户关心的是层级路径(哪些状态被 exit/enter),而非内部方法名;enum 值零字符串分配,AOT 友好 -- **Count 字段**:提供零分配快速路径,无需访问列表即可判断是否有 action -- **失败定位**:`FailedActionName` + `FailedActionState` 精确指出哪个 action 在哪个状态失败,无需完整 `ActionTrace` 结构体的开销 -- **现有 bool 字段保留**:`OnExitCompleted` / `OnEnterCompleted` 作为聚合 bool 保持向后兼容 - -**评审分歧与综合**: -- A(运行时):建议加 count 字段作为零分配路径 → **采纳** -- B(API 设计):建议结构化 `ActionTrace`(含 `Completed` + `Exception?`)→ **简化采纳**:用 `FailedActionName` + `FailedActionState` 定位失败点,避免完整结构体开销 -- C(生成器):建议用状态列表(`TState`)而非方法名(`string`)→ **采纳**:语义更清晰,性能更好 -- D(领域建模):接受原案(方法名列表)→ **升级**:状态列表比方法名更有语义价值 - ---- - -## 12. 向后兼容性 - -| 现有 API | 影响 | -|----------|------| -| `ITransitionTable<TState, TTrigger>` | **不变**(无新成员) | -| `TransitionTableBuilder<TState, TTrigger>` | 新增 `WithParent` 方法(additive) | -| `IStateMachine<TState, TTrigger>` | **不变** | -| `[StateMachine]` | 新增 `Hierarchical` 属性(默认 `false`) | -| `[Transition]` | **不变** | -| 现有扁平状态机 | **零影响**(`Hierarchical` 默认 `false`,生成器行为不变) | -| `TransitionTrace<TState>` | 可能扩展(Q5),additive | -| `TransitionEdge<TState, TTrigger>` | **不变**(action 链由生成器合成为单个委托) | - -**破坏性变更**:无。层级模式完全 opt-in。 diff --git a/docs/rfc/archive/StateTransitionTable.md b/docs/rfc/archive/StateTransitionTable.md deleted file mode 100644 index 8d1d71f..0000000 --- a/docs/rfc/archive/StateTransitionTable.md +++ /dev/null @@ -1,373 +0,0 @@ -# RFC: State 转换表(有限状态转换 primitive) - -> **状态**:Implemented -> **类型**:Pattern -> **创建**:2026-06-14 -> **更新**:2026-07-07 -> **作者**:维护者 / 贡献者 -> **关联 Roadmap**:F3 -> **关联 Issue**:— -> **衍生 ADR**:[ADR-005](../../adr/ADR-005-state-transition-table.md) - ---- - -## 1. 摘要 - -为 DesignPatterns 增加 **有限状态转换表(finite state transition table)** primitive:用 **enum 状态 + enum 触发器** 描述合法边,编译期生成冻结查找表与强类型 API,编译期诊断拦截重复边、非法枚举成员与明显结构问题。 - -**不是**通用状态机框架(层次状态、历史、持久化、重试、entry/exit 动作 DSL)。定位与 Strategy/Factory 同级:**声明式标注 + 生成胶水 + 可选手动 Builder**。 - ---- - -## 2. 动机 - -### 2.1 库内缺口 - -| 已有能力 | 表达力 | -|----------|--------| -| Strategy / Factory | key → 实现(无「当前态」) | -| Chain / Decorator | 线性顺序(非分支图) | -| Composite | 树结构(非状态图) | -| EventAggregator | 事件广播(无状态合法性) | - -许多领域问题本质是 **(当前状态, 触发器) → 下一状态** 的有限图:订单生命周期、插件启停、连接状态、向导步骤、任务阶段。手写 `switch` 或 `Dictionary<(State, Trigger), State>` 易漏边、难测试、重构 enum 时无编译期保护。 - -### 2.2 为何现在 - -- F1/F2 主线已收敛,F3 准入评审通过本 RFC 后可排期。 -- 与现有生成器模式(partial holder、`ForAttributeWithMetadataName`、`*Keys` 常量风格)一致,可复用注册表类基础设施思路。 -- 不重复 MediatR(命令路由)或 Polly(弹性);与 **Stateless** 等库边界清晰(见 §8)。 - -### 2.3 非目标(v1 明确不做) - -- 层次 / 并发 / 历史状态(UML 状态机超集) -- 持久化快照、分布式状态、超时转换 -- ~~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 自动发现转换 - ---- - -## 3. 设计原则 - -1. **表驱动,非继承**:状态行为留在 consumer 类型中;库只提供 **合法性表 + TryTransition**。 -2. **编译期优先**:非法边、重复边、引用未知 enum 成员 → **Error**;孤立状态 → **Info**(可配置关闭)。 -3. **组合优于继承**:不引入 `StateBase` / `AbstractState` 体系。 -4. **与生态共存**:表可嵌入领域服务;需要完整状态机 DSL 时继续用 Stateless 等,本库不竞争。 -5. **分阶段交付**:v1 纯表;v2 guard + DI;v3 再评 string trigger / 与 EventAggregator 联动示例。 - ---- - -## 4. 概念模型 - -```text - trigger T1 trigger T2 - [S0] ──────────────► [S1] ──────────────► [S2] - (唯一边) (唯一边) -``` - -- **状态 `TState`**:`enum`(consumer 定义) -- **触发器 `TTrigger`**:**独立** `enum`(consumer 定义;v1 不与 `TState` 共用同一 enum) -- **边**:至多一条 `(TState from, TTrigger trigger) → TState to`;**允许自环**(`from == to`) -- **初始态**:机器元数据指定一个 `TState` 常量 -- **终态**:v1 **不**建模终态 attribute 或专用规则;无出边的状态自然表示终态 - ---- - -## 5. 运行时 API(草案) - -命名空间:`DesignPatterns.Behavioral`(与 Strategy、Chain、EventAggregator 同级)。 - -### 5.1 核心接口 - -```csharp -namespace DesignPatterns.Behavioral; - -/// <summary> -/// Immutable transition table for enum state and trigger types. -/// </summary> -public interface ITransitionTable<TState, TTrigger> - where TState : struct, Enum - where TTrigger : struct, Enum -{ - TState InitialState { get; } - - /// <summary> - /// Returns false when (current, trigger) is not a declared transition. - /// </summary> - bool TryTransition(TState current, TTrigger trigger, out TState next); - - /// <summary> - /// Triggers that have at least one outgoing edge from <paramref name="current"/>. - /// Order is declaration order in source. - /// </summary> - IReadOnlyList<TTrigger> GetAllowedTriggers(TState current); - - /// <summary> - /// Whether any edge leaves <paramref name="current"/>. - /// </summary> - bool CanTransitionFrom(TState current); -} -``` - -### 5.2 异常类型(可选) - -```csharp -public sealed class InvalidTransitionException<TState, TTrigger> : Exception - where TState : struct, Enum - where TTrigger : struct, Enum; -``` - -提供扩展方法 `Transition(...)` 在失败时抛上述异常;与 `StrategyRegistry.Get` 对称。 - -### 5.3 手动 Builder(无生成器时) - -```csharp -public sealed class TransitionTableBuilder<TState, TTrigger> - where TState : struct, Enum - where TTrigger : struct, Enum -{ - public TransitionTableBuilder<TState, TTrigger> WithInitial(TState initial); - public TransitionTableBuilder<TState, TTrigger> Add(TState from, TTrigger trigger, TState to); - public ITransitionTable<TState, TTrigger> Build(); -} -``` - -- 重复 `(from, trigger)` → `ArgumentException`(与 Factory Builder 一致) -- net8.0 实现内部可用 `FrozenDictionary<(TState, TTrigger), TState>`;netstandard2.0 用 `Dictionary` - -### 5.4 与 EventAggregator / Strategy 的组合(consumer 侧) - -```csharp -if (table.TryTransition(order.Status, OrderTrigger.Pay, out var next)) -{ - order.Status = next; - await aggregator.PublishAsync(new OrderPaidEvent(order.Id), ct); -} -``` - -库 **不** 内建发布事件或调用 Strategy;RFC 仅在 Sample 演示组合。 - ---- - -## 6. 编译期 API(草案) - -### 6.1 特性 - -```csharp -namespace DesignPatterns.Behavioral; - -/// <summary> -/// Marks a partial static holder for generated transition table metadata. -/// Apply exactly once per state machine. -/// </summary> -[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] -public sealed class StateMachineAttribute : Attribute -{ - public StateMachineAttribute(Type stateType, Type triggerType) { ... } - - /// <summary>Initial state enum member.</summary> - public object Initial { get; set; } = null!; - - /// <summary>Optional machine name for generated type names; default = state enum name sans leading 'E'.</summary> - public string? Name { get; set; } -} - -/// <summary> -/// Declares one directed edge. Repeat on the same partial holder class. -/// </summary> -[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] -public sealed class TransitionAttribute : Attribute -{ - public TransitionAttribute(object from, object trigger, object to) { ... } -} -``` - -**约束(生成器 Error)** - -- Holder 必须为 `static partial class`(**一 holder 一机**;v1 不支持同一 holder 多台状态机) -- `StateMachineAttribute.StateType` / `TriggerType` 须为 **两个独立** enum -- `TransitionAttribute` 的 `from` / `trigger` / `to` 须为该 enum 的**命名常量**(`OrderStatus.Draft`),非任意 int cast -- 同一 holder 上 `(from, trigger)` 唯一 -- `from == to` 的自环边合法 - -### 6.2 用法示例 - -```csharp -public enum OrderStatus { Draft, Submitted, Paid, Cancelled } - -public enum OrderTrigger { Submit, Pay, Cancel } - -[StateMachine(typeof(OrderStatus), typeof(OrderTrigger), Initial = OrderStatus.Draft)] -[Transition(OrderStatus.Draft, OrderTrigger.Submit, OrderStatus.Submitted)] -[Transition(OrderStatus.Submitted, OrderTrigger.Pay, OrderStatus.Paid)] -[Transition(OrderStatus.Draft, OrderTrigger.Cancel, OrderStatus.Cancelled)] -public static partial class OrderStatusMachine; -``` - -`[Transition]` 与 `[StateMachine]` 一样标注在 **holder 类声明**上(`AllowMultiple`),不是方法或嵌套类型。 - -### 6.3 生成产物命名 - -对 `OrderStatus` + holder `OrderStatusMachine`: - -| 生成类型 | 说明 | -|----------|------| -| `OrderStatusTransitionTable` | `ITransitionTable<OrderStatus, OrderTrigger>` 实现 + `static Instance` | -| `OrderStatusMachine`(同名 partial 扩展) | 便捷:`public static bool TryTransition(...)` 转发到 `TransitionTable.Instance` | - -v1 **不**生成 `{State}Triggers` 或 `{State}Keys` 常量类——触发器与状态均直接使用 consumer 定义的 enum 成员。 - -命名规则写入 [ROADMAP.md](../../ROADMAP.md)「命名规则」表: - -| 模式 | 生成类型示例 | -|------|----------------| -| State | `{StateEnum}TransitionTable`、partial `{Holder}` 便捷方法 | - -### 6.4 生成器实现要点 - -- 新增 `StateTransitionGenerator`:`IIncrementalGenerator` + `ForAttributeWithMetadataName` -- 收集同一 partial holder 上所有 `TransitionAttribute`(需 syntax 或 semantic 聚合;参考 `HandlerOrder` 多特性模式) -- 输出单文件 partial:`TransitionTable` + 扩展方法 -- 注册 `[Generator]` / `AnalyzerReleases.Unshipped.md` - ---- - -## 7. 诊断 ID(预留 DP026–DP032) - -下一可用 ID:**DP026**([AGENTS.md](../../../AGENTS.md))。实现前在 `DiagnosticIds.cs` 正式登记。 - -| ID | 级别 | 归属 | 触发条件 | -|----|------|------|----------| -| **DP026** | Error | 生成器 | 重复 `(from, trigger)` | -| **DP027** | Error | 生成器 | `Transition` 中 `from`/`to` 不是 `StateMachine` 声明的状态 enum 成员 | -| **DP028** | Error | 生成器 | `trigger` 不是声明的触发器 enum 成员 | -| **DP029** | Error | 生成器 | `Initial` 不是状态 enum 成员 | -| **DP030** | Error | 生成器 | `[StateMachine]` holder 不是 `static partial class` | -| **DP031** | Info | 生成器 | 状态 enum 成员从未作为 `from` 出现(**孤立态**,排除 `Initial` 仅作终态场景) | -| **DP032** | Error | 生成器(v2) | guard 方法在 holder 类上未找到 | -| **DP034** | Error | 生成器(v2) | guard 方法非 static | -| **DP035** | Error | 生成器(v2) | guard 方法签名错误(须 `bool Method(TState, TTrigger)`) | -| **DP036** | Info | Analyzer(v2) | `TryTransition` 字面量 `(state, trigger)` 对未声明(与 DP025 对称) | - -**CodeFix(v1)** - -- DP026:无自动修复(删重复 attribute) -- DP027–DP029:若 typo 接近某 enum 成员,可选「替换为最近成员」CodeFix(复用 CorrectRegistryKey 距离算法) - -**明确不做 CodeFix** - -- DP031 孤立态(需业务判断是否故意终态) - ---- - -## 8. 与 Stateless / 手写表 的边界 - -| | 本库 State 转换表 | [Stateless](https://github.com/dotnet-state-machine/stateless) | -|--|-------------------|----------------------------------------------------------------| -| 模型 | 扁平 enum 图 | 状态机 + guard + 行为 + 子状态 | -| 编译期 | 边表 + DP | 主要为运行时 fluent API | -| 体积 | 单表 + TryTransition | 功能完整 | -| 适用 | 合法边校验、轻量 domain | 复杂工作流、回调、触发器参数 | - -**指导**:边数 < 30、无 guard 的 domain → 本库;需要 `OnEntry` / `PermitReentry` / 子状态 → Stateless。 - ---- - -## 9. 示例场景(Samples 规划) - -| Sample 项目 | 演示 | -|-------------|------| -| `DesignPatterns.Samples.StateMachine` | 订单状态 + `TryTransition` + 非法触发单元测试 | -| (可选)与 EventAggregator | 转换成功后 `PublishAsync`(Sample 内组合,非库 API) | -| (可选)手动 Builder | 与生成表等价的手写 `TransitionTableBuilder` 测试 | - ---- - -## 10. DI 集成(v2,已实现) - -```csharp -// 生成器在转换表类上输出 RegisterDi(引用 DesignPatterns.Extensions.DependencyInjection 时自动启用) -OrderStatusTransitionTable.RegisterDi(services); // Singleton ITransitionTable<,> - -// 或手动注册预构建的表 -services.AddTransitionTable(OrderStatusTransitionTable.Instance); -``` - -启用开关:MSBuild 属性 `DesignPatterns_EnableDiIntegration`(引用 DI 扩展包时自动为 `true`)。详见 [StateTransitionTable.md](../../design/StateTransitionTable.md#v2di-集成)。 - ---- - -## 11. 实现分期 - -| 阶段 | 范围 | 交付 | -|------|------|------| -| **M0 评审** | 本 RFC 定稿 | Issue + 标签 `rfc-approved` | -| **M1 Runtime** | `ITransitionTable`、`TransitionTableBuilder`、异常、单元测试 | PR → Runtime 模块 | [x] | -| **M2 Generator** | `[StateMachine]`、`[Transition]`、`{State}TransitionTable`、DP026–DP031 | PR → SourceGenerators + Diagnostics | [x] | -| **M3 Sample + Docs** | Samples 项目、DesignPatterns.Docs 用户页、`docs/StateTransitionTable.md` | 分 PR | -| **M4 v2 Guard 运行时** | `TransitionEdge.Guard` 委托、`TransitionTableBuilder.Add` guard 重载 | PR #124 | [x] | -| **M4 v2 Guard 生成器** | `[Transition] Guard` 属性、DP032/DP034/DP035 诊断 | PR #125 | [x] | -| **M4 v2 DI 集成** | 生成 `RegisterDi` 方法、`AddTransitionTable` 扩展 | PR #126 | [x] | -| **M4 v2 字面量边校验** | DP036 `StateTransitionLiteralEdgeAnalyzer` | PR #127 | [x] | - -**模块边界**(强制):M1 / M2 / M3 各为独立 PR,符合 [AGENTS.md](../../../AGENTS.md) 跨模块规则。 - ---- - -## 12. 测试策略 - -| 层级 | 内容 | -|------|------| -| 单元 | `TryTransition` 命中/未命中、`GetAllowedTriggers` 顺序、Builder 重复边 | -| 生成器 Verify | 表源码快照、DP026–DP030 消息与位置 | -| 集成 | 生成 `TransitionTable` → 运行时行为与手写 Builder 一致 | -| Analyzer(v2) | DP032 字面量边校验 | - ---- - -## 13. v1 设计决策(已确认) - -| # | 决策 | 结论 | -|---|------|------| -| Q1 | 触发器是否必须独立 enum? | **是** — `TState` 与 `TTrigger` 必须为两个独立 enum | -| Q2 | 是否生成 `{State}Triggers` const 类? | **否** — v1 不生成;直接使用 consumer 的 trigger enum | -| Q3 | DP031 孤立态默认级别 | **Info** | -| Q4 | `Transition` 是否允许 `from == to`(自环)? | **允许** | -| Q5 | 终态是否要在表中有出边 / 是否建模终态? | **不强制、不建模** — 无出边即自然终态 | -| Q6 | 一个 holder 是否只允许一台状态机? | **是** — 一 holder 一机 | - -后续若需 string trigger 或 trigger 常量类,单独开 v2 RFC 修订,不在 v1 范围。 - ---- - -## 14. 评审检查清单 - -- [x] 与「不做全量 GoF / 不做 MediatR」无冲突 -- [x] 命名与现有 `{Contract}Keys` / `{Contract}Registry` 可区分(v1 不生成 Keys/Triggers 常量) -- [x] DP026–DP031 区间无与既有 ID 重叠 -- [x] netstandard2.0 + net8.0 双 TFM 表实现可行 -- [x] v1 设计决策(§13 Q1–Q6)已确认 -- [ ] 至少一个 Skymly 产品场景愿意在 preview 包上试用(AnyTool 插件生命周期 / AgentDesk 任务态等) -- [ ] M1→M2→M3 分期与独立 PR 启动 - ---- - -## 15. 参考 - -- [Strategy.md](../../spec/Strategy.md) — 注册表 + 生成器模式参考 -- [FactoryKeyConventions.md](../../FactoryKeyConventions.md) — 键约定(State v1 不用 string key) -- [EventAggregator.md](../../spec/EventAggregator.md) — 组合用 pub/sub -- [ROADMAP.md](../../ROADMAP.md) F3 准入标准 -- Stateless 文档 — 生态边界对照 - ---- - -## 变更记录 - -| 日期 | 说明 | -|------|------| -| 2026-06-14 | 初稿(Draft) | -| 2026-06-15 | v1 设计决策确认(§13 Q1–Q6) | -| 2026-06-22 | v2 已实现:guard 委托(PR #124/#125)、DI 集成(PR #126)、DP036 字面量边校验(PR #127) | diff --git a/docs/spec/ChainOfResponsibility.md b/docs/spec/ChainOfResponsibility.md deleted file mode 100644 index 56aa126..0000000 --- a/docs/spec/ChainOfResponsibility.md +++ /dev/null @@ -1,96 +0,0 @@ -# Spec: Chain of Responsibility - -> **版本**:v0.2.2(与 NuGet 包版本对齐) -> **关联 Design Doc**:[docs/design/ChainOfResponsibility.md](../design/ChainOfResponsibility.md) -> **关联 ADR**:ADR-XXX(如有) - -## API 面 - -### 运行时接口 - -运行时类型位于 `DesignPatterns/Behavioral/`。 - -| 类型 | 说明 | -|------|------| -| `HandlerDelegate<TContext>` | 调用链中下一阶段的委托 | -| `IHandler<TContext>` | 单个 handler:`InvokeAsync(context, next, ct)` | -| `HandlerPipelineBuilder<TContext>` | `Use(handler)` / `Use(delegate)`,`Build()` | -| `HandlerPipeline<TContext>` | 不可变管道,`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 中显示名为 `"<delegate>"`。 - -`HandlerPipelineTrace.WasShortCircuited` 为「是否存在任一步为 `ShortCircuited`」;终端 handler 不调用 `next` 时也会为 `true`,不宜单独作为「业务短路」判据。 - -### 特性(Attribute) - -```csharp -// 泛型(推荐,C# 11+ / net8.0) -[HandlerOrder<RequestContext>(10)] -public sealed class LoggingHandler : IHandler<RequestContext> { } - -// 非泛型(netstandard2.0) -[HandlerOrder(10, typeof(RequestContext))] -public sealed class LoggingHandler : IHandler<RequestContext> { } -``` - -数值越小越先执行(与手动 `Use` 注册顺序一致)。 - -同一 handler 类可标注多个 `[HandlerOrder<...>]`(`AllowMultiple = true`),分别加入不同 `TContext` 的生成管道,例如共享日志 handler 同时实现 `IHandler<RequestContext>` 与 `IHandler<AuditContext>`。同一 context 下重复的 Order 仍报 **DP005**。 - -### 生成器产出 - -对每种 `TContext` 生成 `{Context}HandlerPipeline`: - -```csharp -public static partial class RequestContextHandlerPipeline -{ - public static HandlerPipeline<RequestContext> Instance { get; } -} -``` - -引用 `DesignPatterns.Extensions.DependencyInjection` 后,`{Context}HandlerPipeline` 额外生成: - -```csharp -RequestContextHandlerPipeline.RegisterDi(services); -var pipeline = provider.GetRequiredService<HandlerPipeline<RequestContext>>(); -``` - -`Create(IServiceProvider sp)` 按 `[HandlerOrder]` 顺序 `GetRequiredService` 各 handler。Singleton 管道在首次构建时固定 handler 实例;需要每次解析新管道时使用 `registryLifetime: ServiceLifetime.Transient`。 - -手动注册:`services.AddHandlerPipeline<TContext>(builder => ...)`(扩展包)。 - -## 诊断 ID - -| ID | 级别 | 触发条件 | 消息格式 | -|----|------|----------|----------| -| DP005 | Error | 同一 context 下重复的 Order | ... | -| DP008 | Error | 标注 `[HandlerOrder]` 但未实现 `IHandler<TContext>` | ... | -| DP009 | Error | 标注 `[HandlerOrder]` 的 handler 缺少 public 无参构造 | ... | -| DP024 | Info | Analyzer:实现了 `IHandler<TContext>` 但未加 `[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 deleted file mode 100644 index 8f016b7..0000000 --- a/docs/spec/Composite.md +++ /dev/null @@ -1,144 +0,0 @@ -# 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<TSelf>` | `IReadOnlyList<TSelf> Children { get; }` where `TSelf : ICompositeNode<TSelf>` | 节点契约:通过 `Children` 区分叶子与分支,空列表为叶子 | -| `ICompositeBuildable<TNode>` | `void SetChildren(IReadOnlyList<TNode> children)` where `TNode : ICompositeNode<TNode>` | 装配时接收子节点;实现应在装配后冻结 children,后续调用视为无效 | -| `CompositeTraverser` | `static void Traverse<TNode>(TNode root, Action<TNode, int, int> visitor, CompositeTraversalOptions<TNode>? options = null)` | 同步遍历单根树(visitor: node, depth, siblingIndex) | -| | `static void TraverseForest<TNode>(IReadOnlyList<TNode> roots, Action<TNode, int, int> visitor, CompositeTraversalOptions<TNode>? options = null)` | 同步遍历森林(多根树) | -| | `static ValueTask TraverseAsync<TNode>(TNode root, Func<TNode, int, int, CancellationToken, ValueTask> visitor, CompositeTraversalOptions<TNode>? options = null, CancellationToken cancellationToken = default)` | 异步遍历单根树 | -| | `static ValueTask TraverseForestAsync<TNode>(IReadOnlyList<TNode> roots, Func<TNode, int, int, CancellationToken, ValueTask> visitor, CompositeTraversalOptions<TNode>? options = null, CancellationToken cancellationToken = default)` | 异步遍历森林 | -| | `static void TraverseParallel<TNode>(TNode root, Action<TNode, int, int> visitor, CompositeTraversalOptions<TNode>? options = null)` | 并行遍历单根树(访问顺序非确定) | -| | `static void TraverseForestParallel<TNode>(IReadOnlyList<TNode> roots, Action<TNode, int, int> visitor, CompositeTraversalOptions<TNode>? options = null)` | 并行遍历森林(根间串行,子树内并行) | -| | `static ValueTask TraverseParallelAsync<TNode>(TNode root, Func<TNode, int, int, CancellationToken, ValueTask> visitor, CompositeTraversalOptions<TNode>? options = null, CancellationToken cancellationToken = default)` | 异步并行遍历单根树 | -| | `static ValueTask TraverseForestParallelAsync<TNode>(IReadOnlyList<TNode> roots, Func<TNode, int, int, CancellationToken, ValueTask> visitor, CompositeTraversalOptions<TNode>? options = null, CancellationToken cancellationToken = default)` | 异步并行遍历森林 | -| `CompositeTraversalOptions<TNode>` | `CompositeTraversalOrder Order { get; set; }`(默认 `DepthFirstPreOrder`) | 遍历顺序 | -| | `int? MaxDepth { get; set; }`(`null` = 无限制) | 最大访问深度(0 = 仅根) | -| | `bool VisitLeavesOnly { get; set; }` | 仅访问叶子节点 | -| | `Func<TNode, bool>? ShouldSkipSubtree { get; set; }` | 返回 `true` 时跳过该子树 | -| | `int? MaxDegreeOfParallelism { get; set; }`(`null` = `Environment.ProcessorCount`) | 并行度上限 | -| | `int MaxParallelDepth { get; set; }`(默认 32) | 并行递归深度上限,超过后回退串行 | -| `CompositeTreeBuilder<TNode>` | `CompositeTreeBuilder<TNode> Leaf(TNode node)` | 添加叶子节点 | -| | `CompositeTreeBuilder<TNode> Branch(TNode node, Action<CompositeTreeBuilder<TNode>> configure)` | 添加分支节点(嵌套 builder 配置子节点) | -| | `TNode Build()` | 构建单根树(要求恰好一个顶层节点) | -| `CompositeCatalogEntry<TNode>` | `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<TNode>(IReadOnlyList<CompositeCatalogEntry<TNode>> entries)` where `TNode : class, ICompositeNode<TNode>` | 从 catalog 装配单根树(`Activator.CreateInstance`) | -| | `static TNode Assemble<TNode>(IReadOnlyList<CompositeCatalogEntry<TNode>> entries, IServiceProvider serviceProvider)` | 从 catalog 装配单根树(DI 解析节点) | -| | `static IReadOnlyList<TNode> AssembleForest<TNode>(IReadOnlyList<CompositeCatalogEntry<TNode>> entries)` | 从 catalog 装配多根森林(`Activator.CreateInstance`) | -| | `static IReadOnlyList<TNode> AssembleForest<TNode>(IReadOnlyList<CompositeCatalogEntry<TNode>> 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<TContract>`(泛型,C# 11+ / net8.0) - -```csharp -[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)] -public sealed class CompositePartAttribute<TContract> : 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<TContract>` | `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<TContract>` 要求**:catalog 装配的节点类型必须实现 `ICompositeBuildable<TContract>`,`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<TContract>` 仅在 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 deleted file mode 100644 index d242378..0000000 --- a/docs/spec/Decorator.md +++ /dev/null @@ -1,109 +0,0 @@ -# Spec: Decorator - -> **版本**:v0.2.2(与 NuGet 包版本对齐) -> **关联 Design Doc**:[docs/design/Decorator.md](../design/Decorator.md) -> **关联 ADR**:ADR-XXX(如有) - -## API 面 - -### 运行时接口 - -运行时类型位于 `DesignPatterns/Structural/`。 - -| 类型 | 说明 | -|------|------| -| `IDecorator<TService>` | `TService Decorate(TService inner)` | -| `IDecoratorOf<TService>` | 可选标记接口(生成器不强制) | -| `DecoratorStackBuilder<TService>` | `Add<TDecorator>()` / `Add(instance)` / `Add(..., Func<bool>)` / `Build(core)` | - -`DecoratorStackBuilder<TService>` 方法签名: - -| 方法 | 说明 | -|------|------| -| `Add<TDecorator>()` where `TDecorator : TService, IDecorator<TService>, new()` | 注册一个装饰器类型(无参构造) | -| `Add(TService decorator)` | 注册一个装饰器实例(须实现 `IDecorator<TService>`) | -| `Add<TDecorator>(Func<bool> predicate)` | 注册一个带运行时谓词的装饰器类型;谓词在 `Build` 时求值,为 `false` 时跳过该装饰器 | -| `Add(TService decorator, Func<bool> predicate)` | 注册一个带运行时谓词的装饰器实例 | -| `Build(TService core)` | 按 `Add` 顺序(`Order` 越小越靠外)包装 `core`,返回最外层 `TService` | - -### 特性(Attribute) - -```csharp -// 泛型(推荐,C# 11+ / net8.0) -[Decorator<IPaymentService>(10)] -public sealed class LoggingPaymentDecorator : IPaymentService, IDecorator<IPaymentService> { ... } - -// 非泛型(netstandard2.0) -[Decorator(10, typeof(IPaymentService))] -public sealed class LoggingPaymentDecorator : IPaymentService, IDecorator<IPaymentService> { ... } -``` - -`[Decorator]` / `[Decorator<TService>]` 构造函数签名: - -| 特性 | 构造函数 | 说明 | -|------|----------|------| -| `DecoratorAttribute<TService>` | `(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<IPaymentService>(PaymentServiceDecoratorOrder.LoggingPaymentDecorator)] -public sealed class LoggingPaymentDecorator : IPaymentService, IDecorator<IPaymentService> { ... } -``` - -## 诊断 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<TService>` | `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<bool>`)仅适用于手动 `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 deleted file mode 100644 index 12d8c7e..0000000 --- a/docs/spec/EventAggregator.md +++ /dev/null @@ -1,134 +0,0 @@ -# Spec: Event Aggregator - -> **版本**:v0.2.2 -> **关联 Design Doc**:[docs/design/EventAggregator.md](../design/EventAggregator.md) -> **关联 ADR**:ADR-XXX(如有) - -## API 面 - -### 运行时接口 - -| 类型 | 职责 | -|------|------| -| `IEventHandler<TEvent>` | 处理指定事件类型 | -| `IEventAggregator` | 订阅、取消订阅、发布 | -| `EventAggregator` | 默认实现 | - -#### 事件处理器 - -```csharp -public interface IEventHandler<in TEvent> -{ - ValueTask HandleAsync(TEvent evt, CancellationToken cancellationToken = default); -} -``` - -任意类实现该接口即可作为处理器;**不要求**继承基类或注册特性。 - -#### 聚合器 - -```csharp -public interface IEventAggregator -{ - void Subscribe<TEvent>(IEventHandler<TEvent> handler); - void Unsubscribe<TEvent>(IEventHandler<TEvent> handler); - ValueTask PublishAsync<TEvent>(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<FooEvent>]` | 泛型 | `#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<TEvent>` | handler 有公共无参构造 | -| `RegisterDi(IServiceCollection, ServiceLifetime)` | DI:注册 handler 实现到容器 | `DesignPatterns_EnableDiIntegration=true` | -| `SubscribeAll(IEventAggregator, IServiceProvider)` | DI:从容器解析 handler + `Subscribe<TEvent>` | `DesignPatterns_EnableDiIntegration=true` | - -**静态路径**仅包含有公共无参构造的 handler;**DI 路径**包含全部有效 handler。这与其他生成器的无参构造约束一致(DP007/DP009/DP014/DP019/DP022 同源)。 - -#### 静态路径(无 DI 依赖) - -```csharp -public record OrderPlacedEvent(string OrderId); - -[RegisterEventHandler<OrderPlacedEvent>] -public sealed class LogOrderHandler : IEventHandler<OrderPlacedEvent> -{ - 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<IEventAggregator>(); -OrderPlacedEventHandlerRegistry.SubscribeAll(aggregator, provider); -``` - -`RegisterDi` 默认 `implementationLifetime: ServiceLifetime.Transient`(每次解析新实例,因 handler 通常无状态)。 - -## 诊断 ID - -| ID | 级别 | 触发条件 | 消息格式 | -|----|------|----------|----------| -| DP044 | Info | 实现 `IEventHandler<T>` 但未标注 `[RegisterEventHandler]` | 提示未注册的 EventHandler | -| DP045 | Error | 同一 handler 类对同一 event type 重复标注 `[RegisterEventHandler]` | 报告重复标注 | -| DP046 | Error | 标注 `[RegisterEventHandler<T>]` 但类未实现 `IEventHandler<T>` | 报告契约不匹配 | - -## 不变量 - -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 deleted file mode 100644 index 1436ffe..0000000 --- a/docs/spec/FactoryRegistry.md +++ /dev/null @@ -1,232 +0,0 @@ -# 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; - -/// <summary> -/// 按 key 解析工厂实现的只读注册表。每次 Create 调用对应 factory 委托得到新产品实例。 -/// </summary> -public interface IFactoryRegistry<TKey, TProduct> : IReadOnlyRegistry<TKey, TProduct> - where TKey : notnull -{ - bool TryCreate(TKey key, [MaybeNullWhen(false)] out TProduct product); - TProduct Create(TKey key); // 找不到抛 FactoryNotFoundException - IReadOnlyCollection<TKey> Keys { get; } -} -``` - -> **注意**:`IFactoryRegistry` 继承 `IReadOnlyRegistry<TKey, TProduct>`,与 `IStrategyRegistry` 共享该抽象。 - -#### 不可变实现 - -```csharp -public sealed class FactoryRegistry<TKey, TProduct> : IFactoryRegistry<TKey, TProduct> - where TKey : notnull -``` - -net8.0+ 上内部使用 `FrozenDictionary` 优化查找。 - -#### Builder(手动注册,无生成器时) - -```csharp -public sealed class FactoryRegistryBuilder<TKey, TProduct> - where TKey : notnull -{ - // 每次 Create(key) 调用该 factory - public FactoryRegistryBuilder<TKey, TProduct> Register(TKey key, Func<TProduct> factory); - - // factory 接收 key - public FactoryRegistryBuilder<TKey, TProduct> Register(TKey key, Func<TKey, TProduct> factory); - - public IFactoryRegistry<TKey, TProduct> 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>(TKey key) where TKey : notnull; -} -``` - -`ForKey` 生成消息:`"No factory registered for key '{key}'."` - -#### 异步工厂(可选) - -```csharp -public interface IAsyncFactory<TProduct> -{ - ValueTask<TProduct> CreateAsync(CancellationToken cancellationToken = default); -} - -public interface IAsyncFactoryRegistry<TKey, TProduct> : IReadOnlyRegistry<TKey, TProduct> - where TKey : notnull -{ - ValueTask<(bool Success, TProduct? Product)> TryCreateAsync( - TKey key, CancellationToken cancellationToken = default); - ValueTask<TProduct> 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<TContract> : 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<TProduct>`,生成器额外输出 `IAsyncFactoryRegistry`。`false` 时异步检测基于 `IAsyncFactory<TProduct>` 实现自动判断 | -| `PoolSize` | `int` | `0` | 启用对象池的最大池大小(per key)。`0` 禁用池化;正值使生成器输出 `IPooledFactoryRegistry`。仅异步工厂有效 | - -#### 用法示例 - -```csharp -// 泛型 Attribute(C# 11+) -[RegisterFactory<IProductFactory>("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<string, IProductFactory> 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<string, IProductFactory> 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<string, IProduct>(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<TProduct>` | `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<TKey, TValue>` 抽象(提供 `Keys` 和 `TryGet`)。 -4. **标记的类须实现指定的 `TContract`**(DP021)。 -5. **标记的类须有 public 无参构造**:生成器使用 `new()` 实例化(DP022),或启用 DI 集成由容器解析。 -6. **key 唯一性**:同一 `TContract` 下 key 不可重复(DP020 在编译期强制)。 - -## 兼容基线 - -- 运行时 TFM:`netstandard2.0` + `net8.0`(两者均须可用并随包分发)。 -- 泛型 Attribute(`RegisterFactoryAttribute<TContract>`)需要 C# 11+ / `net7.0+`;`netstandard2.0` 目标下用 `#if NET7_0_OR_GREATER` 条件编译。 -- 非泛型 `RegisterFactoryAttribute` 始终可用,功能等价。 -- net8.0 上 `FactoryRegistry<TKey, TProduct>` 内部使用 `FrozenDictionary` 优化查找。 - -## 不在范围内 - -- 不做抽象工厂族(多个产品类型族)的完整框架 -- 不在 Core 中引用 DI 容器 -- 不用反射扫描程序集注册 factory diff --git a/docs/spec/README.md b/docs/spec/README.md deleted file mode 100644 index 2fc1fb2..0000000 --- a/docs/spec/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Spec 索引 - -规范文档(Specification)— 定义模式的稳定契约:API 面、诊断 ID、不变量、兼容基线。 - -- **格式与变更门槛**:见 [DOCUMENTATION.md](../DOCUMENTATION.md#4-spec--规范文档) -- **模板**:[_template.md](_template.md) -- **与 Design Doc 的关系**:Spec 描述 **what**(契约),[Design Doc](../design/) 描述 **how** + **why**(实现) - -## 已有 Spec - -| 模式 | 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) | - -## 迁移状态 - -所有 7 个模式文档已从 `docs/<PatternName>.md` 拆分迁移至 `docs/spec/` + `docs/design/`。旧文件已删除。 diff --git a/docs/spec/StateTransitionTable.md b/docs/spec/StateTransitionTable.md deleted file mode 100644 index aff79aa..0000000 --- a/docs/spec/StateTransitionTable.md +++ /dev/null @@ -1,393 +0,0 @@ -# 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; - -/// <summary> -/// 只读转换表:(当前状态, 触发器) → 下一状态。 -/// </summary> -public interface ITransitionTable<TState, TTrigger> - where TState : struct, Enum - where TTrigger : struct, Enum -{ - bool TryTransition(TState from, TTrigger trigger, out TState to); - bool CanTransitionFrom(TState from, TTrigger trigger); - IReadOnlyCollection<TTrigger> GetAllowedTriggers(TState from); - TState InitialState { get; } -} -``` - -- `TryTransition`:命中边且 guard 通过时返回 `true` 并设置 `to`;否则返回 `false`。 -- `GetAllowedTriggers`:返回从 `from` 出发的所有合法触发器。 -- `CanTransitionFrom`:仅接收 state(无 trigger),判断是否存在以 `from` 为起点的边。 - -`TransitionTable<TState, TTrigger>` 为不可变实现;net8.0 上内部使用 `FrozenDictionary` 优化查找。 - -#### Builder(手动注册,无生成器时) - -```csharp -public sealed class TransitionTableBuilder<TState, TTrigger> - where TState : struct, Enum - where TTrigger : struct, Enum -{ - public TransitionTableBuilder<TState, TTrigger> WithInitial(TState initial); - public TransitionTableBuilder<TState, TTrigger> Add( - TState from, TTrigger trigger, TState to, - Func<TState, TTrigger, bool>? guard = null, - Action<TState, TState, TTrigger>? onEnterSync = null, - Action<TState, TState, TTrigger>? onExitSync = null, - Func<TState, TState, TTrigger, CancellationToken, ValueTask>? onEnterAsync = null, - Func<TState, TState, TTrigger, CancellationToken, ValueTask>? onExitAsync = null); - public ITransitionTable<TState, TTrigger> 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<OrderStatus, OrderTrigger>() - .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<TState, TTrigger>( - this ITransitionTable<TState, TTrigger> table, TState from, TTrigger trigger) - where TState : struct, Enum - where TTrigger : struct, Enum; -} -``` - -#### Guard 委托 - -`TransitionTableBuilder.Add` 提供 `guard` 参数;`TransitionEdge<TState, TTrigger>.Guard` 存储委托。`TransitionTable.TryTransition` 在命中边后调用 guard;guard 返回 `false` 时该次转换视为不存在。 - -```csharp -var table = new TransitionTableBuilder<OrderStatus, OrderTrigger>() - .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<OrderStatus, OrderTrigger>() - .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<TState>` 不暴露执行进度。 - -#### IStateMachine 实例包装器 - -```csharp -public interface IStateMachine<TState, TTrigger> - where TState : struct, Enum - where TTrigger : struct, Enum -{ - TState CurrentState { get; } - bool TryTransition(TTrigger trigger, out TState to); - ValueTask<TransitionResult<TState>> TryTransitionAsync(TTrigger trigger, CancellationToken ct = default); -} - -public sealed class StateMachine<TState, TTrigger> : IStateMachine<TState, TTrigger> - where TState : struct, Enum - where TTrigger : struct, Enum -{ - public StateMachine(ITransitionTable<TState, TTrigger> table); - public TState CurrentState { get; } // 初始 == table.InitialState - public bool TryTransition(TTrigger trigger, out TState to); - public ValueTask<TransitionResult<TState>> TryTransitionAsync(TTrigger trigger, CancellationToken ct = default); -} -``` - -`TryTransition` 委托给底层 table;成功时更新 `CurrentState`。`TryTransitionAsync` 同理,并触发 entry/exit action。 - -```csharp -var machine = new StateMachine<OrderStatus, OrderTrigger>(table); -// machine.CurrentState == table.InitialState - -if (machine.TryTransition(OrderTrigger.Submit, out var next)) -{ - // machine.CurrentState == OrderStatus.Submitted -} -``` - -> **线程安全**:`StateMachine<TState,TTrigger>` 非线程安全,设计为单线程使用。多线程场景应在调用方同步,或每线程使用独立实例。 - -#### IStateHierarchy(v3 层次状态) - -```csharp -public interface IStateHierarchy<TState> - where TState : struct, Enum -{ - TState? GetParent(TState state); - bool IsInState(TState state, TState ancestor); - IReadOnlyList<TState> 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<OrderStatus, OrderTrigger> - , IStateHierarchy<OrderStatus> // 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<OrderTrigger> GetAllowedTriggers(OrderStatus from); - // v3 层次模式时额外实现 IStateHierarchy<OrderStatus> - public OrderStatus? GetParent(OrderStatus state); - public bool IsInState(OrderStatus state, OrderStatus ancestor); - public IReadOnlyList<OrderStatus> 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<OrderStatus, OrderTrigger>), - _ => Instance, - lifetime)); - // v3 层次模式时额外注册 IStateHierarchy<OrderStatus> - return services; -} -``` - -调用方式: - -```csharp -OrderStatusTransitionTable.RegisterDi(services); -// 或链式 -services.AddOtherStuff().RegisterDi<OrderStatus, OrderTrigger>(); -``` - -启用开关:MSBuild 属性 `DesignPatterns_EnableDiIntegration`(引用 DI 扩展包时自动为 `true`)。 - -#### 手动注册扩展 - -`DesignPatterns.Extensions.DependencyInjection.DesignPatternsServiceCollectionExtensions` 提供 `AddTransitionTable` 扩展方法,用于注册预构建的表实例: - -```csharp -services.AddTransitionTable(manualTable); -services.AddTransitionTable(OrderStatusTransitionTable.Instance, ServiceLifetime.Singleton); -``` - -使用 `TryAdd` 语义,重复注册不会覆盖。 - -v3 层次模式下使用 `AddStateHierarchy<TState, TTrigger>` 扩展方法(从容器解析 table 并转型)和 `AddStateMachine<TState, TTrigger>` 扩展方法。 - -## 诊断 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<TState, TTrigger>` **非线程安全**,设计为单线程使用。多线程场景应在调用方同步,或每线程使用独立实例。 -5. 执行顺序(`TryTransitionAsync`):guard → OnExit(sync → async)→ OnEnter(sync → async)→ 返回结果。 -6. Entry/Exit action 仅通过 async 路径(`TryTransitionAsync`)触发;同步 `TryTransition` 不调用 action。 -7. 部分执行语义:若 OnExit 成功后 OnEnter 抛异常,异常传播给调用方,OnExit 副作用已发生;`TransitionResult<TState>` 不暴露执行进度。 -8. `TransitionTable<TState, TTrigger>` 不可变;`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/spec/Strategy.md b/docs/spec/Strategy.md deleted file mode 100644 index 3b26e57..0000000 --- a/docs/spec/Strategy.md +++ /dev/null @@ -1,294 +0,0 @@ -# Spec: Strategy - -> **版本**:v0.2.2(与 NuGet 包版本对齐) -> **关联 Design Doc**:[docs/design/Strategy.md](../design/Strategy.md) - -## API 面 - -### 运行时接口 - -命名空间:`DesignPatterns.Behavioral` - -#### 策略接口(可选标记接口) - -```csharp -namespace DesignPatterns.Behavioral; - -/// <summary> -/// 标记性策略接口。不强制实现,但配合源生成器时提供更好的类型约束。 -/// </summary> -public interface IStrategy<in TInput, out TOutput> -{ - TOutput Execute(TInput input); -} - -/// <summary> -/// 异步策略接口。 -/// </summary> -public interface IAsyncStrategy<in TInput, TOutput> -{ - ValueTask<TOutput> ExecuteAsync(TInput input, CancellationToken ct = default); -} -``` - -> **注意**:`[RegisterStrategy]` 不要求实现 `IStrategy<,>`。任何接口/基类均可作为策略契约。 -> -> `IStrategy<,>` / `IAsyncStrategy<,>` 为**可选标记接口**,便于表达同步/异步算法形状;注册表与生成器不依赖它们。用法见 `tests/DesignPatterns.Tests/Behavioral/StrategyMarkerInterfaceTests.cs`。 - -#### 注册表 - -```csharp -namespace DesignPatterns.Behavioral; - -/// <summary> -/// 按 key 解析策略实现的只读注册表。 -/// </summary> -public interface IStrategyRegistry<TKey, TStrategy> - where TKey : notnull -{ - bool TryGet(TKey key, [MaybeNullWhen(false)] out TStrategy strategy); - TStrategy Get(TKey key); // 找不到抛 StrategyNotFoundException - bool TryGetWithGuard(TKey key, [MaybeNullWhen(false)] out TStrategy strategy); - IReadOnlyCollection<TKey> Keys { get; } -} -``` - -> **注意**:`TStrategy` 为不变(invariant),非协变 `out`——`TryGetWithGuard` 的 `out TStrategy` 参数阻止协变。 - -提供不可变实现 `StrategyRegistry<TKey, TStrategy>`,net8.0 上内部使用 `FrozenDictionary` 优化查找。 - -#### Builder(手动注册,无生成器时) - -```csharp -public sealed class StrategyRegistryBuilder<TKey, TStrategy> where TKey : notnull -{ - public StrategyRegistryBuilder<TKey, TStrategy> Register(TKey key, TStrategy strategy); - public StrategyRegistryBuilder<TKey, TStrategy> Register(TKey key, Func<TStrategy> factory); - public IStrategyRegistry<TKey, TStrategy> Build(); -} -``` - -#### 异步策略解析 - -`IAsyncStrategy<TInput, TOutput>` 与 `[RegisterStrategy]` 使用**同一套** Keys / Registry / `RegisterDi` 路径;契约可以是继承 `IAsyncStrategy<,>` 的专用接口,无需额外 attribute 或并行注册表类型。 - -`StrategyRegistryExtensions` 提供按 key 解析并执行的便利方法: - -```csharp -// 注册表值为 IAsyncStrategy<TInput, TOutput> -var result = await registry.ExecuteAsync(PaymentAsyncStrategyKeys.Stripe, amount); - -// 注册表值为继承 IAsyncStrategy<,> 的契约(需显式指定 TContract / TOutput / TInput) -var length = await registry.ExecuteAsync<ITextProcessor, int, string>( - TextProcessorKeys.Length, "hello"); - -// 或等价写法 -var length = await registry.Get(TextProcessorKeys.Length).ExecuteAsync("hello"); -``` - -`TryExecuteAsync` 与 `ExecuteAsync` 对称,未命中 key 时返回 `false` 而不抛异常。 - -DI 场景:`{Contract}Registry.RegisterDi(services)` 后从容器解析 `IStrategyRegistry<string, TContract>`,再 `await registry.ExecuteAsync<...>(...)` 或 `Get(key).ExecuteAsync(...)`。 - -#### Guard 谓词 - -`TryGetWithGuard` 在解析策略时额外评估注册的 guard 谓词。guard 返回 `false` 时该策略视为未注册(返回 `false`)。 - -```csharp -var builder = new StrategyRegistryBuilder<string, IPaymentStrategy>() - .Register("alipay", new AlipayPayment(), guard: key => isEnabled("alipay")); -var registry = builder.Build(); - -// guard 通过时返回策略;guard 返回 false 时返回 false -if (registry.TryGetWithGuard("alipay", out var strategy)) { ... } -``` - -> **设计约束**:guard 签名为 `Func<TKey, bool>`(仅接收 key),非 `Func<TInput, bool>`。注册表层面不知道 `TInput`(`TStrategy` 不要求实现 `IStrategy<TInput, TOutput>`),因此无法基于输入判断。基于输入的动态路由是业务逻辑,不在此库范围。 - -源生成器支持:`[RegisterStrategy<TContract>("key", Guard = nameof(CanEnable))]`,生成器校验 guard 方法签名(DP047-DP049)。 - -### 特性(Attribute) - -命名空间:`DesignPatterns.Behavioral` - -#### 泛型版本(C# 11 / .NET 7+) - -```csharp -/// <summary> -/// 标记一个类为某策略接口的实现,并注册到编译期生成的策略注册表中。 -/// </summary> -/// <typeparam name="TContract">策略契约接口。</typeparam> -[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] -public sealed class RegisterStrategyAttribute<TContract> : Attribute -{ - /// <summary> - /// 用于解析此策略的 key。 - /// </summary> - public string Key { get; } - - /// <summary> - /// 可选:实现类上的 static guard 方法名。设置后该方法须有签名 - /// <c>static bool Method(TKey key)</c>。guard 返回 false 时该策略视为未注册。 - /// </summary> - public string? Guard { get; set; } - - public RegisterStrategyAttribute(string key) - { - Key = key; - } -} -``` - -#### 非泛型版本(netstandard2.0 / C# 7.3) - -```csharp -/// <summary> -/// 非泛型版本,用于不支持泛型 Attribute 的目标框架。 -/// </summary> -[AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = true)] -public sealed class RegisterStrategyAttribute : Attribute -{ - public string Key { get; } - - /// <summary> - /// 策略契约接口类型。 - /// </summary> - public Type For { get; } - - /// <summary> - /// 可选:实现类上的 static guard 方法名。签名须为 <c>static bool Method(TKey key)</c>。 - /// </summary> - public string? Guard { get; set; } - - public RegisterStrategyAttribute(string key, Type @for) - { - Key = key; - For = @for; - } -} -``` - -#### 用法示例 - -```csharp -// 泛型 Attribute(推荐,C# 11+) -[RegisterStrategy<IPaymentStrategy>("alipay")] -public class AlipayPayment : IPaymentStrategy { ... } - -[RegisterStrategy<IPaymentStrategy>("wechat")] -public class WechatPayment : IPaymentStrategy { ... } - -// 非泛型(netstandard2.0 / C# 7.3) -[RegisterStrategy("alipay", typeof(IPaymentStrategy))] -public class AlipayPayment : IPaymentStrategy { ... } -``` - -### 生成器产出 - -对于每个 `TContract`,生成器输出: - -#### 1. 强类型 Key 常量 - -```csharp -// PaymentStrategyKeys.g.cs -public static partial class PaymentStrategyKeys -{ - public const string Alipay = "alipay"; - public const string Wechat = "wechat"; -} -``` - -命名规则:`{接口名去掉前缀I和后缀Strategy}Keys`,可通过特性参数 override。 - -#### 2. 静态注册表(无 DI 场景) - -```csharp -// PaymentStrategyRegistry.g.cs -public static partial class PaymentStrategyRegistry -{ - private static readonly IStrategyRegistry<string, IPaymentStrategy> _instance = - new StrategyRegistry<string, IPaymentStrategy>( - new Dictionary<string, IPaymentStrategy> - { - ["alipay"] = new AlipayPayment(), - ["wechat"] = new WechatPayment(), - }); - - public static IStrategyRegistry<string, IPaymentStrategy> Instance => _instance; -} -``` - -#### 3. DI 集成(引用 `DesignPatterns.Extensions.DependencyInjection` 时) - -引用扩展包会自动 Import `build/DesignPatterns.Extensions.DependencyInjection.targets`,设置 `DesignPatterns_EnableDiIntegration=true`,生成器额外输出: - -```csharp -public static partial class PaymentStrategyRegistry -{ - // Instance 仍保留(new() 静态实例,无 DI 生命周期) - - public static IStrategyRegistry<string, IPaymentStrategy> Create(IServiceProvider serviceProvider) => - new ServiceProviderStrategyRegistry<string, IPaymentStrategy>(serviceProvider, _diEntries); - - public static IServiceCollection RegisterDi( - IServiceCollection services, - ServiceLifetime implementationLifetime = ServiceLifetime.Singleton, - ServiceLifetime registryLifetime = ServiceLifetime.Singleton); -} -``` - -推荐用法: - -```csharp -var services = new ServiceCollection(); -PaymentStrategyRegistry.RegisterDi(services); -var registry = services.BuildServiceProvider() - .GetRequiredService<IStrategyRegistry<string, IPaymentStrategy>>(); -``` - -| 生命周期组合 | 行为 | -|--------------|------| -| Singleton 实现 + Singleton 注册表 | 默认;`TryGet` 每次解析同一实现实例 | -| Transient 实现 + Singleton 注册表 | `TryGet` 每次从容器取新实例(推荐需要可变策略时) | -| Transient 注册表 | 每次解析注册表时重建 `Create(sp)` | - -手动 Builder 仍可用:`services.AddStrategyRegistry<string, IPaymentStrategy>(...)`(见扩展包),与生成器互不冲突。 - -## 诊断 ID - -| 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。 - -## 不变量 - -1. **key 唯一性**:同一 `TContract` 下 key 不可重复(DP003 在编译期强制)。 -2. **`TStrategy` 不变(invariant)**:`IStrategyRegistry<TKey, TStrategy>` 的 `TStrategy` 非协变 `out`——`TryGetWithGuard` 的 `out TStrategy` 参数阻止协变。 -3. **guard 签名固定为 `Func<TKey, bool>`**:guard 仅接收 key,不接收 `TInput`。注册表层面不知道 `TInput`(`TStrategy` 不要求实现 `IStrategy<TInput, TOutput>`)。 -4. **`[RegisterStrategy]` 不要求实现 `IStrategy<,>`**:任何接口/基类均可作为策略契约。 -5. **标记的类须有 public 无参构造**:生成器使用 `new()` 实例化(DP007)。 -6. **标记的类须实现指定的 `TContract`**(DP004)。 -7. **`IStrategy<,>` / `IAsyncStrategy<,>` 为可选标记接口**:注册表与生成器不依赖它们。 - -## 兼容基线 - -- 运行时 TFM:`netstandard2.0` + `net8.0`(两者均须可用并随包分发)。 -- 泛型 Attribute(`RegisterStrategyAttribute<TContract>`)需要 C# 11+ / `net7.0+`;`netstandard2.0` 目标下用 `#if NET7_0_OR_GREATER` 条件编译。 -- 非泛型 `RegisterStrategyAttribute` 始终可用,功能等价。 -- net8.0 上 `StrategyRegistry<TKey, TStrategy>` 内部使用 `FrozenDictionary` 优化查找。 - -## 不在范围内 - -- 不做策略选择/路由逻辑(这是业务代码) -- 不通过反射扫描程序集 -- 不在注册表里管理对象生命周期(那是 DI 的事) -- 不做 `Func<T1, T2, ..., TResult>` 的无限委托展开 -- 不强制所有策略实现 `IStrategy<,>` diff --git a/docs/spec/_template.md b/docs/spec/_template.md deleted file mode 100644 index 8005591..0000000 --- a/docs/spec/_template.md +++ /dev/null @@ -1,39 +0,0 @@ -# Spec: <模式名> - -> **版本**:vX.Y(与 NuGet 包版本对齐) -> **关联 Design Doc**:[docs/design/<PatternName>.md](../design/<PatternName>.md) -> **关联 ADR**:ADR-XXX(如有) - -## API 面 - -### 运行时接口 - -(接口定义、方法签名、泛型约束) - -### 特性(Attribute) - -(特性类、属性、构造函数签名) - -### 生成器产出 - -(生成的类名、方法签名、命名空间) - -## 诊断 ID - -| ID | 级别 | 触发条件 | 消息格式 | -|----|------|----------|----------| -| DPXXX | Warning | ... | ... | - -## 不变量 - -1. ... -2. ... - -## 兼容基线 - -- netstandard2.0 / net8.0 -- ... - -## 不在范围内 - -- ...