《面向对象程序设计-2026上》第二次Blog作业
一、前言
本阶段三次作业围绕数字电路模拟程序展开迭代开发,均为单道综合编程题,功能与难度呈阶梯式上升:
作业集 4:覆盖 5 种基础逻辑门实现、元件引脚解析、电路信号传播与无效元件过滤,难度较难,核心考察面向对象封装与字符串解析能力。
作业集 5:新增三态门、译码器等 4 类组合元件,引入控制引脚、多输出与无效状态规则,难度困难,核心考察类体系扩展与复杂需求落地能力。
作业集 6:基于组合模式实现子电路复用,新增 5 类异常检测与优先级判定,难度困难,核心考察架构设计与程序健壮性编码能力。
三次作业累计完成代码约 1100 行,完整经历了从基础功能实现到架构化设计的迭代过程。
二、设计与分析
2.1 作业 4:基础逻辑门版本
2.1.1 实现思路
本次作业是整个系列的基础版本,核心目标是实现与、或、非、异或、同或五种基础逻辑门的电路信号模拟。整体执行流程分为四步:解析输入文本中的 INPUT 信号定义与元件连接关系、建立电路元件模型、按信号依赖关系计算每个门的输出电平、按指定顺序过滤并输出有效元件的结果。
最开始写的时候图省事,想把所有逻辑都塞在主类里,写了一半发现新增一种逻辑门就要改动多处代码,扩展性很差,于是中途重构了继承结构,抽取出逻辑门抽象父类,为后面两次作业的功能扩展打下了基础。
2.1.2 类结构分析
本次作业的类图采用基础继承架构,共包含 4 类核心角色,类结构如下图所示:

图 1 作业 4 基础逻辑门类结构图
具体类职责与关系如下:
- 抽象类 LogicGate:作为所有逻辑门的统一父类,封装了gateId元件编号、inputPins引脚电平映射、outputValue输出值三个公共属性;定义抽象方法calculate()作为所有门的统一计算接口,同时提供设置引脚、获取输出的公共方法。所有子类只需重写计算逻辑,对外接口保持一致。
- 5 个实体子类:AndGate、OrGate、NotGate、XorGate、XnorGate,分别继承LogicGate,重写calculate()方法实现各自的逻辑运算。例如与门遍历所有输入引脚,全为 1 则输出 1,否则输出 0;非门对单输入取反。
- 电路管理类 Circuit:维护全局输入信号表、元件集合、连接关系映射三组数据。核心方法是topoSortCalculate(),基于连接关系构建有向无环图,通过拓扑排序确定元件计算顺序,避免依赖顺序错误;另有getValidOutput()方法,过滤输入不全的无效元件,生成最终输出列表。
- 输入解析类 InputParser:负责解析 INPUT 行、连接关系行,识别元件名并创建对应实例,将纯文本输入转化为可运算的Circuit对象。
- 类间关系:五个子类均为泛化继承关系,指向父类LogicGate;Circuit与LogicGate为一对多聚合关系,一个电路实例包含多个逻辑门元件;InputParser与Circuit为创建关联关系,解析器负责生成电路对象。
2.1.3 代码复杂度分析
使用 SourceMonitor 对作业 4 源码进行静态度量,核心方法的复杂度指标如下:
![image]()
类维度的度量结果显示,Circuit类的平均圈复杂度 OCavg 为 3.8,总复杂度 WMC 最高,因为集中了信号计算、有效性校验、结果输出多部分逻辑。
整体来看,本次作业代码复杂度偏低,大部分方法都是单一逻辑。复杂度峰值出现在元件名解析方法,原因是需要处理 “带括号 + 编号” 和 “直接字母 + 编号” 两种命名格式,分支判断较多;拓扑排序方法需要维护入度表与队列,也有一定复杂度。
设计心得:第一次作业的架构比较朴素,就是最基础的继承复用,虽然简单但验证了面向对象设计的优势 —— 新增元件只需扩展子类,不用改动原有逻辑,为后面两次迭代减少了很多工作量。
2.2 作业 5:扩展组合元件版本
2.2.1 实现思路
本次在作业 4 的基础上迭代扩展,新增三态门、译码器、数据选择器、数据分配器四类组合元件。核心难点有两个:一是引脚编号规则变更,所有元件统一按 “控制引脚 - 输入引脚 - 输出引脚” 的顺序编号;二是引入了无效状态、多输出引脚的逻辑,元件不再只有简单的 0/1 输出。
为了适配新的引脚规则,没有在原有 int 类型的引脚映射上硬改,而是单独抽离了Pin实体类与PinType枚举,把引脚的编号、类型、电平值、有效性封装成统一对象,避免了用数字硬区分类型的混乱。原有 5 种基础门的代码几乎没有改动,全部通过新增子类实现新功能,基本符合开闭原则。
2.2.2 类结构分析
本次作业在原有架构上升级了引脚模型,扩展了元件子类,类结构如下图所示:
![image]()
核心扩展点如下:
- 1.枚举类 PinType:标注<
>构造型,定义CONTROL、INPUT、OUTPUT三种引脚类型,统一引脚类型的表示,避免魔法数字。 - 2.实体类 Pin:封装引脚的四个核心属性:pinNumber引脚编号、type引脚类型、value电平值、isValid状态有效性。原LogicGate中的Map<Integer, Integer>替换为Map<Integer, Pin>,每个引脚都有完整的属性描述。
- 3.抽象类 LogicGate升级:新增isAllInputValid()方法,统一校验所有必需引脚是否都有有效输入,作为元件能否输出结果的判断依据。
- 4.4 个新增组合元件子类:TristateGate三态门:控制端为高电平时输出等于输入,低电平时输出为无效高阻态;Decoder译码器:3 个控制引脚、n 个输入引脚、2^n 个输出引脚,控制端满足使能条件时,根据输入编码选中一个输出脚置 0,其余置 1,否则所有输出无效;Mux数据选择器:n 个控制引脚、2^n 个输入引脚、1 个输出引脚,根据控制值选通对应输入输出;Demux数据分配器:n 个控制引脚、1 个输入引脚、2^n 个输出引脚,根据控制值将输入分配到对应输出,其余无效。
- 5.Circuit类扩展:新增formatOutput()方法,适配不同元件的输出格式 —— 译码器输出选中的引脚编号,数据分配器输出所有引脚的状态串,基础门输出单引脚电平。
- 6.InputParser类扩展:新增带控制引脚数的元件名解析逻辑,适配Z(2)、M(3)这类命名格式
类间关系:Pin关联PinType枚举;LogicGate与Pin为一对多聚合关系;9 个元件子类均泛化继承LogicGate;Circuit聚合LogicGate;InputParser负责创建Circuit实例。
2.2.3 代码复杂度分析
核心方法复杂度度量结果如下:
![image]()
类维度上,Decoder类的平均圈复杂度最高,OCavg 达到 4.7,原因是译码逻辑集中在单个方法中,包含控制端校验、输入编码转换、多输出循环赋值多层逻辑。
分析来看,整体复杂度比作业 4 明显上升,峰值集中在多输出元件的计算方法和输出格式化方法中。多输出元件需要同时处理有效性判断和循环赋值,分支与循环叠加导致圈复杂度上升;输出格式化方法因为要区分 9 种元件的输出规则,条件分支也较多。
设计心得:本次迭代基本没有修改原有基础门的代码,全部通过新增子类实现功能,切实体会到了开闭原则的实际价值。不足在于各类元件的引脚范围都是硬编码在子类常量中,存在重复代码,后续可以通过配置化进一步优化。
2.3 作业 6:子电路与异常检测版本
2.3.1 实现思路
本次是架构改动最大的一次,核心新增两个功能:基于组合模式的子电路复用,以及按优先级处理的五类输入异常检测。一开始对子电路的实现思路不清晰,想直接把子电路当成特殊元件硬塞进去,后来对照题目提示的组合模式,梳理出抽象构件、叶子构件、组合构件的三层结构,才理顺了整体架构。
异常检测部分需要严格遵循题目指定的优先级,同一条连接存在多个异常时,仅返回优先级最高的那一个;多条连接存在异常时,仅返回最先出现的异常。因此检测逻辑必须按优先级顺序执行,命中即返回,不能全量检测后再排序。
2.3.2 类结构分析
本次作业基于组合模式重构整体架构,类结构如下图所示:
![image]()
图 3 作业 6 组合模式架构类结构图
核心分层如下: - 1.抽象构件类 CircuitComponent:组合模式的顶层抽象,定义所有电路组件的统一接口,包含三个抽象方法:calculate()计算输出、getPinValue()获取引脚电平、isValid()判断组件是否有效。无论是基础逻辑门还是子电路,都实现该接口,主电路可以无差别调用。
- 2.叶子构件 BaseGate:继承CircuitComponent,是所有基础逻辑门的父类,封装基础门的公共属性与通用逻辑,5 个具体门元件再继承它实现各自运算。
- 3.组合构件 SubCircuit:继承CircuitComponent,对应子电路。内部维护innerComponents内部组件列表,以及inputMapping、outputMapping两组引脚映射表,把子电路的外部引脚名与内部元件的引脚做关联。实现接口的三个方法时,会递归计算内部所有组件的输出,对外仅暴露输入输出引脚,完全封装内部细节。
- 4.异常检测类 ExceptionChecker:封装五类异常的检测逻辑,按题目指定的优先级排序。每次检测连接行时从高到低依次判断,命中第一个异常后立即返回错误信息,不再继续检测后续异常。
- 5.主电路类 MainCircuit:管理所有子电路定义、主电路组件集合、全局输入信号,统筹异常前置检测、信号传播计算、结果格式化输出的完整流程。
- 6.输入解析类 InputParser:扩展子电路解析功能,能够识别C开头的子电路定义块,解析为独立的SubCircuit对象存入主电路。
类间关系:BaseGate和SubCircuit均泛化继承CircuitComponent;SubCircuit与CircuitComponent为组合关系,一个子电路包含多个内部组件;5 个基础门子类泛化继承BaseGate;MainCircuit聚合SubCircuit与CircuitComponent,同时关联ExceptionChecker;InputParser负责创建MainCircuit实例。
2.3.3 代码复杂度分析
核心方法复杂度度量结果如下:
![image]()
类维度上,ExceptionChecker类的总复杂度 WMC 最高,因为五类异常的检测逻辑全部集中在类内部。
分析来看,复杂度峰值出现在异常检测主方法,圈复杂度达到 17,超出了 15 的推荐阈值,原因是五类异常的判断逻辑堆叠在一起,且包含边界条件判断,分支嵌套较多,是后续重构的重点。子电路解析方法因为要处理多行的子电路定义块,分支也相对较多。
设计心得:组合模式落地之后,子电路的实现比预想中顺畅很多,主电路完全不用区分接入的是基础门还是子电路,统一调用接口即可,扩展性很强。不足在于异常检测模块写得比较仓促,全靠 if-else 堆叠,设计上还有很大优化空间。
三、采坑心得
3.1 元件名正则匹配错误,解析逻辑混乱
- 问题场景:作业 4 开发初期,元件名存在A(2)1和X1两种格式,图省事用字符串 split 拆分,先按左括号拆,再按数字截取,结果经常把引脚数和编号搞混。例如A(10)2会被错误拆分为引脚数 1、编号 02,完全不符合预期。
- 数据支撑:初始版本 10 组测试样例中,有 4 组输出异常,所有带两位引脚数的元件全部识别错误,元件创建失败率达 40%。
- 排查过程:逐行打印解析结果,发现只要括号内数字超过 1 位,拆分逻辑就会失效,意识到字符串硬拆的方式太脆弱,对格式变化的兼容性极差。
- 修复方案:改用正则表达式分组匹配,最终正则为^([A-Z])(?😦(\d+)))?(\d+)$,三个分组分别捕获标识符、可选引脚数、元件编号。同时封装独立的parseGateName工具方法,所有元件名解析统一调用该方法。
- 修复效果:修复后所有样例的元件识别正确率达到 100%,后续作业 5、6 新增元件类型,也只需在该方法内补充少量判断,无需改动解析主逻辑。
- 心得:格式解析类需求优先使用正则分组匹配,不要图省事用字符串硬拆;解析逻辑必须统一封装,避免散落在代码各处导致逻辑不一致。
3.2 未做拓扑排序,多级电路计算顺序错误 - 问题场景:作业 4 第一版写完后,单级电路测试全部通过,但跑样例 4 那种 “与门→非门→或门” 的三级串联电路时,结果出现错误。初期以为是运算逻辑写错了,反复检查门的计算方法都没问题。
- 排查过程:对着样例 4 单步调试,发现非门计算的时候,与门的calculate方法还未执行,使用的是初始值 0,等于先算了后级元件再算前级元件,完全颠倒了依赖顺序。
- 修复方案:基于连接关系构建有向无环图,统计每个元件的入度,采用 Kahn 算法做拓扑排序,按拓扑顺序依次计算元件输出,保证计算某个元件时,它所有依赖的输入信号都已经计算完成。
- 修复效果:修复后所有多级串联、并联的电路样例全部通过,未再出现依赖顺序导致的计算错误。
- 心得:电路信号传播本质就是有向图的遍历,必须按依赖顺序计算,不能想当然按输入顺序处理。写代码之前先理清楚数据的依赖关系,选对算法能从根源避免很多逻辑错误。
3.3 引脚编号顺序混淆,组合元件逻辑全错 - 问题场景:作业 5 刚写完译码器时,样例 8 的结果始终不对。一开始反复检查译码计算公式都没发现问题,后来逐字对照题目引脚规则,才发现题目规定引脚按 “控制 - 输入 - 输出” 排序,0 号起始为控制引脚,之后是输入引脚,最后是输出引脚。我惯性认为输入引脚在前,把 0、1 号当成输入,3、4 号当成控制,完全颠倒了引脚类型。
- 数据支撑:10 组组合元件测试样例,初始版本仅通过 2 组,所有涉及控制引脚的元件全部逻辑失效。
- 修复方案:为每类元件整理引脚范围映射表,例如M(2)1译码器,0-2 号为控制引脚,3-4 号为输入引脚,5-8 号为输出引脚,作为类常量存储,引脚初始化严格按表执行。同时在设置引脚值时增加类型校验,避免赋值错误。
- 修复效果:修复后所有组合元件样例全部通过,后续新增元件也沿用该方法,未再出现引脚顺序混淆的问题。
- 心得:题目中的编号、顺序类规则,绝对不能凭经验假设,必须逐条对应原文,整理成明确的映射表再写代码。细节规则错了,整个核心逻辑都会失效。
3.4 子电路无作用域隔离,元件互相覆盖 - 问题场景:作业 6 初期实现子电路时,图省事把所有元件存入全局 Map,结果子电路与主电路中编号相同的元件互相覆盖。例如样例 2 定义了 C1、C2 两个子电路,内部各有一个非门,最后全局 Map 里只剩一个非门实例,输出结果直接少了一半。
- 排查过程:打印全局元件集合,发现子电路内部的元件全部暴露到了主电路作用域,编号重复的直接被覆盖,本质是没有做命名空间隔离。
- 修复方案:严格按照组合模式实现,每个SubCircuit对象维护独立的内部组件集合,子电路内部元件仅在自身作用域内可见;主电路只能通过子电路的输入输出引脚交互,不能直接访问内部元件,实现作用域完全隔离。
- 修复效果:修复后多子电路、多层嵌套场景均运行正常,命名冲突问题彻底解决。
- 心得:只要涉及嵌套、复用的模块,作用域隔离是第一原则。封装不是为了凑设计模式而写,是真的能解决实际的命名冲突、数据混乱问题。
3.5 异常判断顺序错误,优先级不符合要求 - 问题场景:异常检测模块写完后,样例 8 始终通不过。该样例的同一条连接同时存在 “多个输出” 和 “无输入” 两个异常,按题目优先级应优先报 “多个输出”,但我按代码书写顺序检测,先命中了 “无输入”,返回了错误的异常类型。
- 数据支撑:5 类异常的混合测试用例,初始版本优先级正确率仅 40%,同一条连接存在多异常的场景几乎全部报错。
- 修复方案:将五类异常按题目给定的优先级存入有序列表,检测时从前往后依次校验,只要命中优先级最高的异常就立即返回,不再继续检查后续类型。
- 修复效果:修复后所有异常样例 100% 符合优先级规则,后续新增异常类型只需按顺序插入列表即可,维护成本很低。
- 心得:有明确优先级的规则,一定要显式定义执行顺序,不能依赖代码的书写顺序,否则调整优先级时很容易出问题。
四、改进建议
4.1 重构异常检测模块,降低圈复杂度
当前ExceptionChecker类的checkFirstError方法圈复杂度达到 17,超出合理阈值,且 if-else 堆叠的写法扩展性差。后续可采用策略模式重构:
- 定义异常检测策略接口,包含check()校验方法和getPriority()获取优先级方法;
- 每类异常对应一个策略类,各自封装检测逻辑,职责单一;
- 检测类内部维护按优先级排序的策略列表,遍历调用即可。
- 重构后单个策略的圈复杂度可降至 3 以内,新增异常类型无需修改原有代码,符合开闭原则。
4.2 引入工厂模式,统一元件创建
当前元件创建逻辑散落在解析类的各个分支中,新增元件需要补充 if 判断,且各类元件的引脚规则硬编码在子类中,重复代码较多。后续可引入工厂模式优化: - 实现元件工厂类,根据元件标识符返回对应的元件实例;
- 将各类元件的引脚参数(控制脚数、输入脚数、输出脚数)做成配置表,工厂创建元件时自动加载,消除子类中的硬编码重复;
- 配合组合模式,子电路也可通过工厂统一创建,屏蔽创建细节。
4.3 补全时序电路功能,完成系列迭代
目前仅实现了组合电路部分,题目规划中还有时序电路的迭代版本。后续可补充实现数字电路模拟程序 - 3 的功能,增加 D 触发器、JK 触发器等时序元件,支持带反馈的电路仿真。
需要补充的核心点包括:增加时钟信号处理机制、为元件增加状态存储、支持时序步进计算;同时新增环路检测功能,避免组合环路导致的计算死循环。
4.4 补充单元测试,提升回归效率
当前测试完全依赖手动运行样例,每次修改代码都要全量重跑,效率低且容易漏测。后续可使用 JUnit 框架补充单元测试: - 为每类元件的计算方法编写测试用例,覆盖正常输入、边界输入、无效状态三类场景;
- 为异常检测模块编写测试用例,覆盖单异常、多异常优先级、边界输入等场景;
- 核心逻辑测试覆盖率目标达到 85% 以上,修改代码后运行测试即可快速验证是否引入新 bug。
五、总结
5.1 阶段收获
三次迭代作业做下来,最大的收获是把面向对象的知识从书本概念落到了实际代码里。以前总觉得继承、多态、设计模式都很抽象,这次从最基础的继承复用,到开闭原则的实践,再到组合模式的落地,一步步感受到了好的架构带来的实际价值 —— 新增功能不用改动旧代码,出问题时更容易定位边界。
其次是处理复杂需求的能力得到了提升。这三次作业的规则细节都非常多,从元件命名、引脚编号到输出格式、异常优先级,稍有疏漏就会出错。慢慢养成了先整理规则表、再动手写代码的习惯,而不是边看题目边写,踩坑的数量明显减少。
另外也积累了很多可复用的实战经验,比如正则分组解析的技巧、拓扑排序的实现、作用域隔离的方法、异常优先级的处理思路,这些在后续的编程任务中都能直接套用。
5.2 存在不足
首先是设计模式的应用不够主动。很多地方比如异常检测、元件创建,其实一开始就能用设计模式优化,但还是习惯先写 if-else 实现功能,等代码变复杂了才想起重构,没有做到设计先行。
其次是代码工程化意识不足。没有建立单元测试体系,全靠手动调试排查 bug,效率偏低;没有规范的日志机制,定位问题只能靠临时打印输出;代码注释虽然逐次补充,但核心方法的前置条件、返回说明仍不够完整。
最后是知识体系还不完整,时序电路、环路检测等进阶内容尚未接触,整个电路模拟的功能闭环还未完成。
5.3 后续计划
第一,本周内完成异常检测和元件创建的重构,落地策略模式与工厂模式,对比重构前后的圈复杂度变化,验证优化效果。
第二,参考题目迭代规划,实现数字电路模拟程序 - 3 的时序电路功能,补全整个系列的同时,学习时序逻辑的实现思路。
第三,系统学习 JUnit5,为核心的元件计算、异常检测模块补充单元测试,逐步养成测试先行的开发习惯。
第四,补充词法分析相关知识,优化输入解析模块,提升复杂格式下的解析鲁棒性。






浙公网安备 33010602011771号