架构重构模式 — 常见架构缺陷与应对策略 架构重构模式 — 常见架构缺陷与应对策略版本v3.0维护者WorkFlowPrj Team更新日期2026-07-20目标总结软件架构重构过程中遇到的典型缺陷模式为后续重构提供通用参考适用范围调用链治理、数据源一致性、职责边界划分、依赖关系治理、重复逻辑治理目录架构重构模式 — 常见架构缺陷与应对策略目录1. 概述1.1 本文档的定位1.2 核心设计理念2. 缺陷模式概览3. 缺陷模式 1透传链过长3.1 典型案例3.2 根因分析3.3 应对策略3.4 检查清单4. 缺陷模式 2重构残留4.1 典型案例4.2 根因分析4.3 应对策略4.4 检查清单5. 缺陷模式 3双数据源不一致5.1 典型案例5.2 根因分析5.3 应对策略5.4 检查清单6. 缺陷模式 4职责错位6.1 典型案例6.2 根因分析6.3 应对策略6.4 检查清单7. 缺陷模式 5隐式顺序依赖7.1 典型案例7.2 根因分析7.3 应对策略7.4 检查清单8. 缺陷模式 6逻辑分散8.1 典型案例8.2 根因分析8.3 应对策略8.4 检查清单9. 综合案例ECS 渲染管线重构回顾9.1 重构背景9.2 遇到的缺陷模式9.3 修复方案9.4 经验教训10. 附录重构决策矩阵1. 概述1.1 本文档的定位本文档是CsharpPrj/Structuring/系列文档的一部分与以下文档配合使用文档内容关系Architecture.md日志架构基础设施层CallChain.md函数调用链流程层ControlFlow.md控件使用流程流程层本文档架构重构模式模式/方法论层本文档描述的缺陷模式具有通用性适用于各类软件架构的重构场景。第 9 章以 ECS 渲染管线重构为具体案例展示各模式的实际应用。1.2 核心设计理念架构重构遵循以下通用设计原则数据所有权谁拥有数据谁负责维护契约优先优先使用接口/抽象类定义契约而非运行时动态绑定单一数据源同一概念只应有一种权威表示避免多源不一致显式依赖依赖关系应在构造函数/初始化阶段声明而非运行时隐式注入顺序无关性处理顺序不应影响正确性必要时通过拓扑排序保证逻辑内聚同一业务规则应集中在一处通过策略/注册表统一分发2. 缺陷模式概览#缺陷模式核心问题典型症状1透传链过长调用链路中间层仅透传不做处理功能需经 3 层传递新增参数需修改所有中间层调试需追踪 4 层调用栈2重构残留新旧代码路径共存功能重叠构建通过但运行时行为不一致部分调用方用新接口、部分用旧接口废弃代码未清理3双数据源不一致同一概念有两套表示方式且不同步数据在 A 中存在、在 B 中不存在需相互转换且转换易出错生命周期不同步4职责错位数据所有者不负责维护自身数据数据维护逻辑分散在多个类中外部类需了解内部结构才能维护数据不一致时难以定位5隐式顺序依赖代码正确性依赖集合遍历顺序改变顺序导致不同结果存在循环依赖运行时偶发错误且重启后消失6逻辑分散同一条件分支在 N 处重复出现新增变体需修改多处且易遗漏代码审查时发现这个逻辑在哪里见过3. 缺陷模式 1透传链过长3.1 典型案例场景功能需经过多层调用才能到达最终执行者中间层仅透传不做实质性处理。Adapter.Execute() → 设置 Adapter.CallbackA (x) { ... } // 第 1 层 → 设置 Adapter.CallbackB (y) { ... } // 第 1 层 → Integration.ExecuteStage(StageX) → SystemX.Update() // 第 2 层 → CallbackA?.Invoke(x) // 第 3 层 → CallbackB?.Invoke(y) // 第 3 层问题回调在Adapter中定义在SystemX中执行中间经过Integration透传回调签名限制了参数传递无法传递上下文信息任何一层忘记设置回调功能就失效新增参数需修改 Adapter、Integration、System 三处签名3.2 根因分析因素说明架构层次过多Adapter → Integration → System3 层传递运行时绑定回调是运行时赋值编译器无法检查透传层Integration 的ExecuteStage只是中转不做校验3.3 应对策略策略将回调提升为策略接口通过构造函数注入。Integration.Initialize() → var strategy new ConcreteStrategy(...) // 策略创建 → new SystemX(..., strategy) // 构造函数注入 SystemX.Update() → _strategy.OperationA(x) → _strategy.OperationB(y)维度回调/委托模式依赖注入模式绑定时机运行时初始化时类型安全无有参数传递隐式闭包捕获显式构造函数参数可测试性难需 mock 回调易可 mock 接口调用链长度3 层1 层新增参数影响修改所有中间层仅修改策略实现3.4 检查清单调用链是否超过 3 层中间层是否只是透传参数是否存在运行时赋值的回调/委托是否可以用接口/策略模式替代回调链4. 缺陷模式 2重构残留4.1 典型案例场景引入新机制后旧代码路径未完全清理新旧共存导致功能重叠。重构内容将分散的回调提升为统一的策略接口核心处理类通过构造函数接收策略接口重构遗漏入口类中仍保留旧架构的遗留方法调用遗留方法与新策略接口功能重叠两者执行时机不同导致逻辑分散在两处4.2 根因分析因素说明重构范围不完整只重构了核心逻辑没清理外围调用缺乏清理步骤引入新机制后未移除废弃代码测试覆盖不足构建通过不等于运行时行为正确4.3 应对策略策略全链路追踪法 — 绘制完整调用链标记新旧路径逐层清理。步骤绘制完整调用链从入口到出口列出所有参与方标记新旧路径区分重构前和重构后的路径识别重叠区域找出功能重叠的部分制定清理计划明确哪些旧代码可以移除逐层验证从底层到顶层验证行为一致性示例重构前 Execute() → 设置回调 → ExecuteStage → SystemX.Update() → 回调调用 → LegacyPostProcess() // 旧事后补充处理 重构后 Initialize() → 创建策略并注入 Execute() → ExecuteStage → SystemX.Update() → 策略接口调用 → LegacyPostProcess() // ★ 遗留应该移除4.4 检查清单重构后是否移除了所有废弃代码新旧路径是否有功能重叠所有调用方是否都迁移到了新接口运行时行为是否与重构前一致5. 缺陷模式 3双数据源不一致5.1 典型案例场景同一概念有两套数据结构表示如领域模型 vs 运行时实例操作 A 的数据时遍历的是 B。维度数据结构 A模型/配置数据结构 B运行时实例覆盖范围所有定义的元素实际创建成功的元素创建失败处理元素仍然存在实例不存在生命周期配置生命周期运行时生命周期问题运行时索引是数据结构 B 的概念但注册逻辑却遍历数据结构 A。// ❌ 用模型数据注册运行时索引voidRegisterFromModel(ModelElementelement){foreach(varchildinmodel.GetChildren(element))// 数据结构 A{varinstancepool.GetInstance(child);// 转 B可能失败if(instanceisContainerc)parent.RegisterSubContainer(c);// 注册到运行时索引}}数据源不匹配模型中的子元素不一定在运行时存在创建失败时导致注册遗漏。5.2 根因分析因素说明两套数据结构并存模型和运行时实例有不同的生命周期隐式转换模型→实例的转换可能失败职责错位运行时索引用模型数据来维护5.3 应对策略策略操作哪个数据源的数据就遍历哪个数据源。操作正确的数据源错误的数据源注册运行时索引运行时实例集合模型/配置定义查找父实例运行时实例集合模型树遍历子实例运行时集合模型定义// ✅ 正确遍历运行时集合注册运行时索引voidRegisterFromInstances(){foreach(varinstanceinInstances){if(instanceisContainerc)RegisterSubContainer(c);}}5.4 检查清单是否存在两套表示同一概念但不同步的数据结构操作 A 的数据时是否遍历的是 B两套数据之间是否有隐式转换可能失败是否可以将两套数据合并为一套6. 缺陷模式 4职责错位6.1 典型案例场景数据的维护逻辑分散在多个类中数据所有者不负责维护自身数据。操作负责方位置数据清空数据所有者Reset()方法数据填充策略实现处理流程内部数据补充入口适配器PostProcess()方法问题数据是数据所有者的内部状态但维护逻辑却分散在三个不同的类中。6.2 根因分析因素说明渐进式重构最初在入口类中操作数据提取为策略后未收归职责边界模糊处理类既执行业务逻辑又维护数据状态缺乏内聚性数据定义和操作分离6.3 应对策略策略谁拥有数据谁负责维护。判断方法数据定义在哪个类中→ 该类应该是数据的所有者数据的生命周期由谁管理→ 该管理者应该是数据的所有者谁最了解数据的内部结构→ 该类最适合维护数据// ✅ 正确数据所有者负责维护publicclassDataOwner{privateInternalIndex_index;publicvoidExecute(){_index.Clear();// ... 执行处理流程 ...RegisterFromInstances();}privatevoidRegisterFromInstances(){foreach(varinstanceinInstances)if(instanceisContainerc)_index.Register(c);}}// ❌ 错误外部类维护内部数据publicclassExternalAdapter{voidPostProcess(objecttarget){if(targetisDataOwnerowner)// 外部类操作 DataOwner 的内部数据}}6.4 检查清单数据的定义和维护是否在同一个类中外部类是否需要了解数据的内部结构才能维护数据不一致时是否能快速定位责任方是否存在清空在 A填充在 B的情况7. 缺陷模式 5隐式顺序依赖7.1 典型案例场景代码正确性依赖集合遍历顺序父元素未先处理时子元素查找失败。voidProcess(){varitemsrepository.GetAll();// 遍历顺序不确定foreach(variteminitems){varinstancefactory.Create(item);varparentlocator.FindParent(item);// ↑ 依赖父元素已注册if(parent!null){parent.AddChild(instance);locator.Register(item,instance);}// ↑ parent null 时静默失败}}问题FindParent依赖查找表中已有父元素的注册信息而注册发生在同一个循环中。父元素在子元素之后处理时FindParent返回null。7.2 根因分析因素说明隐式依赖FindParent隐式依赖查找表的注册顺序无排序保证GetAll的返回顺序未定义静默失败parent null时跳过不报错7.3 应对策略策略两阶段处理 — 先创建所有实例并注册再建立关系。voidProcess(){// 阶段 1创建所有实例并注册到查找表varcreatednewDictionaryItem,Instance();foreach(variteminGetUnprocessed()){varinstancefactory.Create(item);locator.Register(item,instance);created[item]instance;}// 阶段 2建立父子关系此时所有实例已在查找表中foreach(var(item,instance)increated){varparentlocator.FindParent(item);// 一定能找到parent?.AddChild(instance);}}7.4 检查清单代码的正确性是否依赖集合的遍历顺序是否存在先有鸡还是先有蛋的依赖问题是否有静默失败的情况条件不满足时跳过不报错是否可以两阶段处理先创建再建立关系8. 缺陷模式 6逻辑分散8.1 典型案例场景同一条件分支在多个类中重复出现新增一个变体需修改多处。// 文件 A创建逻辑voidCreate(Entitye){vartypeGetType(e);if(typeType.Button)controlnewButton();elseif(typeType.Panel)controlnewPanel();}// 文件 B布局逻辑voidLayout(Entitye){vartypeGetType(e);if(typeType.Button)LayoutButton(e);elseif(typeType.Panel)LayoutPanel(e);}// 文件 C事件绑定voidBindEvents(Entitye){vartypeGetType(e);if(typeType.Button)BindButtonEvents(e);elseif(typeType.Panel)BindPanelEvents(e);}问题新增ComboBox类型需修改 3 个文件遗漏任一文件都会导致运行时行为不一致。8.2 根因分析因素说明缺乏统一分发层按类型分发的逻辑没有抽象到一处策略模式未应用每种变体的行为没有封装为独立策略类违反 DRY 原则同一类型判断在 N 处重复8.3 应对策略策略策略模式 注册表 — 每种变体封装为独立策略注册表集中管理映射。// 策略接口interfaceIControlStrategy{ControlCreate();voidLayout(Entitye);voidBindEvents(Entitye);}// 具体策略classButtonStrategy:IControlStrategy{publicControlCreate()newButton();publicvoidLayout(Entitye)LayoutButton(e);publicvoidBindEvents(Entitye)BindButtonEvents(e);}classPanelStrategy:IControlStrategy{publicControlCreate()newPanel();publicvoidLayout(Entitye)LayoutPanel(e);publicvoidBindEvents(Entitye)BindPanelEvents(e);}// 注册表staticclassStrategyRegistry{staticDictionaryType,IControlStrategy_mapnew(){[Type.Button]newButtonStrategy(),[Type.Panel]newPanelStrategy(),};publicstaticIControlStrategyGet(Typet)_map[t];}// 使用方无分支voidCreate(Entitye)StrategyRegistry.Get(GetType(e)).Create();voidLayout(Entitye)StrategyRegistry.Get(GetType(e)).Layout(e);维度条件分支散落策略模式 注册表新增变体影响修改 N 个文件新增 1 个策略类 注册 1 行遗漏风险高低代码重复类型判断重复 N 次零重复可测试性需测试 N 个文件每个策略可独立测试8.4 检查清单同一条件判断/类型分支是否在 3 处以上出现新增一个变体需要修改几处代码超过 2 处即有问题是否可以用策略模式将每种变体封装为独立类是否可以用注册表/工厂集中管理类型→策略的映射9. 综合案例ECS 渲染管线重构回顾本章以 ECSEntity-Component-System渲染管线重构为具体案例展示上述 6 种缺陷模式的实际表现。9.1 重构背景将 WinForms UI 渲染从硬编码迁移到 ECS 体系实现配置驱动的控件创建。9.2 遇到的缺陷模式缺陷模式具体表现影响透传链过长委托链经过 Adapter → Integration → System 三层传递调试困难容易遗漏重构残留策略接口重构后Adapter 中仍保留旧注册方法新旧路径功能重叠双数据源不一致Entity 树和 WinForms 树不一致ContainerIndex 注册遗漏职责错位ContainerIndex 的维护分散在三个类中数据不一致时难以定位隐式顺序依赖FindParent 依赖 Entity 遍历顺序运行时偶发错误逻辑分散控件类型分支在创建、布局、事件绑定中重复新增控件类型需改 3 处9.3 修复方案缺陷模式应对策略具体措施透传链过长依赖注入三个委托提升为IControlCreationStrategy接口重构残留全链路追踪移除RegisterSubContainersFromEntityTree双数据源不一致数据源对齐EcsConfigContainer遍历Controls注册子容器职责错位数据所有权ContainerIndex的维护收归EcsConfigContainer隐式顺序依赖两阶段处理先创建控件注册到 Pool再建立父子关系逻辑分散策略模式 注册表控件策略统一注册到ControlStrategyRegistry9.4 经验教训先分析后重构动手前先绘制完整的调用链和数据流重构要彻底引入新机制后及时移除旧机制数据源要对齐操作哪个数据源的数据就遍历哪个数据源职责要内聚谁拥有数据谁负责维护依赖要显式处理顺序不应影响正确性逻辑要集中同一业务规则只在一处定义10. 附录重构决策矩阵场景推荐模式避免模式多个类需要相同的处理逻辑策略接口 / 依赖注入回调链传递数据需要被多个类维护数据所有权原则职责分散两套数据结构表示同一概念合并为一套两套并存处理顺序影响正确性两阶段处理依赖隐式顺序新旧架构共存全链路追踪 清理遗留废弃代码同一条件分支在 N 处重复策略模式 注册表散落式 if-else

本月热点