作业集4~6的总结

一、前言
本阶段完成了PTA平台作业集4~6的三次迭代开发任务,题目围绕“数字电路模拟程序”逐步展开。从最基础的逻辑门模拟,到复杂组合逻辑元件的扩展,再到子电路封装与异常检测,每一次迭代都在前一版本的基础上增加了新的功能需求。
三次作业均为单道大题,题量虽不大,但代码规模呈显著增长趋势。根据SourceMonitor的统计,作业4约481行,作业5增至745行,作业6达到1055行。难度也逐级攀升:第一次是从无到有的建模,第二次是要打破“一输出”的思维定式,第三次则要处理子电路和异常优先级这类抽象问题。
本文将结合SourceMonitor报表与类图设计,对三次作业的源码进行复盘分析,总结踩坑经历与改进思考。
二、设计与分析
(一)作业集4:基础逻辑门电路模拟
第一次作业要求实现五种基本逻辑门(与门、或门、非门、异或门、同或门),并支持输入信号解析、连接关系建立和信号传播计算。
类设计
我采用了继承结构:LogicGate作为抽象父类,五种门分别继承并重写calculate()方法。AndGate和OrGate使用ArrayList存储不定数量的输入,NotGate用单个变量存储,XorGate和XnorGate用两个变量存储。此外设计了SignalSource接口,统一表示外部输入和元件输出两种信号来源。
类图:
屏幕截图 2026-06-24 123011
SourceMonitor分析显示最大复杂度12出现在主方法中,说明输入解析和信号传播逻辑还不够精简。
关键错误与修正
错误一:忽略引脚号。连接信息[A A(8)1-1 A(8)1-3 X5-2]中,我只取了元件名A(8)1,丢弃了引脚号。多输入门的所有输入都被当成1号引脚处理,导致顺序错乱。修正:用正则表达式提取完整引脚信息,用Map<Integer, Integer>按引脚号存储输入值。
错误二:我最初的思路是读到连接信息立即取值并设置给目标门,但连接顺序不可控——[A(2)1-0 N1-1]可能在[A A(2)1-1]之前出现,此时A(2)1尚未创建。修正:先完整解析所有输入和连接关系,全部存起来后再统一计算

(二)作业集5:复杂组合逻辑元件
第二次作业新增三态门、译码器、数据选择器、数据分配器四种元件,这些元件拥有控制引脚和多个输出引脚,对程序设计提出新挑战。
关键结构改进
改进一:用Map存储门实例。第一次作业用五个独立ArrayList分别存五种门,查找时需遍历所有列表。改用HashMap<String, Gate>后,通过门ID直接获取对象,代码简洁许多。
改进二:统一输入存储方式。第一次作业中不同门用不同方式存输入(ArrayList、单个变量、两个变量),处理麻烦。这次统一用Map<Integer, Integer>存储,key为引脚号,value为输入值。判断是否就绪只需检查size()是否等于所需引脚数。
改进三:支持多输出引脚。第一次作业默认所有门只有一个输出,但译码器和数据分配器有多个输出。将LogicGate中的单个OutputPin改为ArrayList,子类根据自身特性决定输出数量。
类图:
屏幕截图 2026-06-24 123450
SourceMonitor分析表明虽然代码行数增加,但最大复杂度从12降到5,说明功能拆分后单个方法反而更简单了。

(三)作业集6:子电路与异常检测
第三次作业新增子电路定义和五类异常检测,是三次中最复杂的迭代。
子电路设计
采用组合模式:SubCircuit继承自LogicGate,内部维护自己的元件集合和连接关系。展开时给子电路内的门添加前缀(如C1-N1),避免与主电路元件重名。

第三次作业在基础逻辑门和组合逻辑元件的基础上,新增了两个核心功能:
子电路(SubCircuit):允许将一部分电路封装为可复用的子电路,在主电路中被引用
异常检测(Exception Handling):对五种典型输入错误进行检测并按优先级输出提示信息
这也是三次作业中代码量最大、逻辑最复杂的一次。根据SourceMonitor统计,代码总行数达到1055行,最大圈复杂度45,最大嵌套深度8。

核心数据结构
// 主电路
Map<String, Component> mainComponents // 主电路中的所有元件
Map<String, SubCircuit> subCircuits // 所有子电路定义
// 子电路
List inputs // 子电路输入引脚列表
List outputs // 子电路输出引脚列表
Map<String, Component> innerComponents // 子电路内部元件
Map<String, String> inputMap // 输入引脚 → 信号源映射
// 连接
String outputPin // 输出端
String[] inputPins // 输入端列表
// 异常检测
List errorMessages // 按优先级检测错误

类图:
屏幕截图 2026-06-24 124103

三、踩坑心得
坑1:子电路输入映射混乱
主电路中[X C1-A]要将X的值传给子电路C1的输入引脚A。我最初试图在连接解析阶段直接处理,但此时子电路内部的元件可能尚未完全构建。修正:先用inputMap存储子电路输入引脚到信号源的映射,待所有解析完成后,在build()方法中统一将映射关系应用到内部元件。
坑2:子电路嵌套展开顺序
C2内部引用了C1,展开C2时必须先展开C1。我的代码虽然递归调用展开方法,但C1展开后的输出引脚映射未能正确传回,导致C2中[C1-B B]找不到信号。修正:在展开方法中返回输出引脚映射,供上层使用。
坑3:异常优先级判断
我最初将“信号冲突”检测放在最前面,导致一条连接同时有多个输出和信号冲突时,应报“多个输出”却报了“信号冲突”。修正:严格按题目给定的优先级顺序检测,一旦命中立即返回。

四、改进建议

  1. 主方法拆分:作业6中instantiateSubCircuit()方法圈复杂度达45,代码臃肿。建议拆分为:parseSubCircuitHeader()、parseSubCircuitInputs()、parseSubCircuitOutputs()、parseSubCircuitConnections()、expandSubCircuitReferences()等独立方法。
  2. 引入端口方向判断:目前PinRef.isOutput()中集中了所有元件的端口规则,新增器件时需修改该方法。建议让每个器件类自己判断端口方向,或建立端口元数据描述对象
    3.添加单元测试:三次作业均缺少单元测试覆盖,每次修改后需手动测试多个用例。建议针对关键逻辑(门计算、信号传播、异常检测)编写JUnit测试。

五、总结
通过本阶段三次迭代作业,我在以下几个方面有所收获:
面向对象设计:从第一次的五个独立门类,到第二次用Map管理实例,再到第三次用组合模式封装子电路,逐步理解了“职责清晰、易于扩展”的设计原则。
信号传播算法:从最初“边读边算”的错误思路,到“先解析后计算”的正确做法,再到循环迭代的信号传播方式,对图状依赖关系的处理有了更深认识。
输入解析与错误处理:学会了用正则表达式解析复杂格式输入,以及按优先级处理多种异常情况。
代码质量意识:通过SourceMonitor的量化反馈,认识到高圈复杂度方法需要拆分,代码嵌套过深需要重构。
仍需进一步学习的内容包括:instantiateSubCircuit()方法的重构方案、更完善的单元测试框架、以及如何在不破坏现有功能的情况下进行大规模代码重构。

posted @ 2026-06-24 12:44  危博轩  阅读(7)  评论(0)    收藏  举报