第二次Blog作业

一、前言
本阶段三次作业集围绕数字电路模拟程序的迭代开发展开,遵循软件工程增量迭代的开发模式,从基础逻辑门电路仿真逐步扩展至复杂组合元件、子电路封装与异常检测,完整覆盖了面向对象设计、数据结构应用、设计模式实践与输入输出处理等核心知识点。三次作业的难度、代码量与设计复杂度呈阶梯式上升,既巩固了 Java 面向对象的编程基础,也锻炼了复杂系统的架构设计与问题排查能力。
1.1 知识点覆盖
作业集 4(数字电路模拟程序 - 1):核心知识点为面向对象的类继承与多态、有向无环图(DAG)的拓扑排序算法、字符串解析与正则表达式、集合框架的排序与遍历。实现 5 种基础逻辑门的电路仿真,完成信号从输入到输出的传播计算。
作业集 5(数字电路模拟程序 - 2):核心知识点为面向对象的开闭原则实践、多类型引脚的统一建模、多输出元件的逻辑设计、复杂元件的状态有效性判断。在原有 5 种门的基础上新增三态门、译码器、数据选择器、数据分配器 4 种组合元件,元件总数扩展至 9 种,同时适配控制引脚、多输出引脚的编号规则。
作业集 6(数字电路模拟程序 - 子电路与异常检测):核心知识点为组合设计模式、分层封装思想、异常处理的优先级机制、命名空间隔离。新增子电路定义与引用功能,支持电路模块化复用;新增 5 类输入异常的检测与报错,提升程序鲁棒性。
1.2 题量与难度评估
三次作业均为单道综合编程题,但代码量与设计难度逐次提升:
作业集 4:代码规模约 420 行,共 7 个类,难度中等偏易。核心难点在于拓扑排序的信号传播逻辑与元件解析的字符串处理,属于入门级综合设计题,侧重面向对象基础与基础算法应用。
作业集 5:代码规模约 760 行,共 11 个类,难度中等。核心难点在于多类型引脚的统一映射、新增元件的特殊逻辑实现,以及多输出元件的输出格式适配,考验初始架构的可扩展性。
作业集 6:代码规模约 1180 行,共 15 个类,难度较难。核心难点在于组合模式的子电路设计、内外引脚的映射关系、异常检测的优先级判断,属于架构级别的设计升级,对整体代码结构的重构能力要求较高。
三次作业呈现出明显的迭代特征,后一次作业均在前一次的代码基础上扩展,既要求初始架构具备可扩展性,也要求开发者具备代码重构与迭代优化的能力,完整模拟了工业界软件产品的版本迭代流程。
二、设计与分析
本次三次作业均采用面向对象的设计思想,基于 Java 语言实现,以下分别从类结构设计、核心代码逻辑、代码质量指标三个维度进行分析,类图均通过 PowerDesigner 16.5 建模完成。
2.1.1 类图设计
cf177df7d3de8ccceabf2b63fa4c6e51
该设计遵循单一职责原则,每个类职责清晰;通过继承实现代码复用,通过多态支持不同门的逻辑扩展,符合面向对象的基本设计规范。
2.1.2 核心逻辑分析
信号传播采用拓扑排序算法,核心思路为:
1.将所有电路元件视为 DAG 的节点,连接关系视为有向边(从输出引脚指向输入引脚),表示信号的依赖方向;
2.统计每个元件的入度(依赖的上游元件数量),将入度为 0 的元件(输入直接连接的门)加入计算队列;
3.依次取出队列中的元件,执行 calculate() 计算输出值,再将信号传递给所有下游元件,下游元件入度减 1,入度为 0 时加入队列;
4.遍历完成后,所有输入完整的元件均完成计算,输入不全的元件因入度始终不为 0,不会被计算,自动忽略,天然适配题目 “忽略输入无效元件” 的要求。
整体代码质量良好,类职责划分清晰,继承结构合理,代码冗余度低,为后续功能扩展打下了良好基础。
2.2 作业集 5:扩展组合元件设计
2.2.1 类图设计
94a11e3c6ed90dc5181e039c16b4d909
2.2.2 核心逻辑分析
本次作业的核心难点在于引脚编号的统一映射。题目规定含控制引脚的元件按 “控制 - 输入 - 输出” 顺序编号,因此设计了通用的引脚索引计算逻辑:
i.对于每个元件,预先定义控制引脚数量 controlCount、输入引脚数量 inputCount、输出引脚数量 outputCount;
ii.引脚号 0 ~ controlCount-1 为控制引脚,controlCount ~ controlCount+inputCount-1 为输入引脚,controlCount+inputCount ~ controlCount+inputCount+outputCount-1 为输出引脚;
iii.所有元件均通过该通用规则进行引脚索引与类型的映射,避免了每个元件单独处理编号的冗余代码,也降低了人为编号错误的概率。
以 3-8 线译码器 M(3)1 为例:控制引脚 3 个,输入引脚 3 个,输出引脚 8 个。引脚 0-2 对应 S1/S2/S3 控制端,3-5 对应 A0-A2 输入端,6-13 对应 Y0-Y7 输出端,完全匹配题目要求。
代码扩展性良好,通过继承结构实现了功能增量,未对原有代码造成侵入式修改,但部分复杂元件的计算方法圈复杂度略高,存在优化空间。
2.3 作业集 6:子电路与异常检测设计
2.3.1 类图设计(组合模式应用)
9dc68c235f6f7360fba823d71e60d6ea
2.3.2 核心逻辑分析
1.子电路引脚映射机制
子电路的 INPUT/OUTPUT 标识作为外部引脚名,内部对应一个虚拟信号节点。当主电路向子电路的输入引脚赋值时,通过映射表将信号写入子电路内部的对应输入节点;子电路内部完成计算后,将输出节点的信号同步到子电路的外部输出引脚,供主电路下游元件使用。
子电路采用独立的命名空间,内部元件编号与主电路互不干扰,输出时自动拼接子电路编号前缀(如 C1-A(2)1-0),从根本上避免命名冲突。
2.异常检测优先级机制
题目规定 5 类异常存在固定优先级,因此采用 “顺序校验、优先返回” 的策略:
按 “多输入异常 > 无输入异常 > 无输出异常 > 顺序错误异常 > 信号冲突异常” 的优先级依次检查每条连接信息,一旦命中优先级更高的异常,立即停止后续校验并记录错误;若多条连接存在异常,仅处理文本中最先出现的异常,其余忽略。
该逻辑通过 InputValidator 类的 validateConnection() 方法实现,按优先级顺序编写判断分支,确保错误输出完全符合题目要求。
三、采坑心得
三次作业的开发过程中遇到了大量实际问题,以下结合具体测试用例、代码结构与调试数据进行总结,所有问题均有对应的测试结果与代码修改记录支撑。
3.1 作业集 4:基础实现阶段的常见问题
3.1.1 元件名称解析的正则匹配错误
问题描述:与门、或门的名称格式为 A(n)m(如 A(2)1),包含括号与数字,初期使用简单的字符串分割处理,导致括号与编号提取混乱。例如测试样例 3 中的 A(4)1,初期代码按字符分割后,误将 (4)1 整体作为编号,元件输入数默认为 2,导致输入 4 个信号时只计算前 2 个,输出结果为 1,与正确结果 0 不符。
排查过程:通过打印所有元件的解析结果(类型、输入数、编号),发现所有多输入门的输入数均为默认值 2,准确定位到名称解析逻辑错误。
解决方案:改用正则表达式 ^([AON])(((\d+)))?(\d+)$ 进行匹配,分组 1 为元件类型,分组 3 为输入引脚数,分组 4 为元件编号。修正后所有元件解析正确,测试样例 3 顺利通过。
3.1.2 拓扑排序未处理输入不全的元件
问题描述:测试样例 6 中 X2 元件仅接 1 个输入引脚,按题目要求应忽略输出。初期代码未做有效性判断,只要元件有部分输入就执行计算,导致异或门因第二个输入为 null 抛出空指针异常,且输出了无效的 X2 结果。
解决方案:在 Gate.isValid() 方法中增加判断:所有输入引脚的信号值均不为 null 时,元件才有效、参与计算。拓扑排序时,仅当元件所有输入都就绪才加入计算队列,输入不全的元件始终停留在队列外,自动被忽略。修正后测试样例 6 输出正确,且无空指针异常。
3.1.3 元件输出顺序不符合要求
问题描述:初期使用 HashMap 存储同类元件,遍历输出时顺序随机,导致同类元件未按编号从小到大排序。例如测试样例 4 中,应按 A、O、N 的类别顺序输出,同类按编号排序,初期代码偶尔出现非门排在或门前面的情况,不符合输出规范。
解决方案:改用 TreeMap 存储同类元件,以元件编号为 key 自动排序;输出时严格按照与门、或门、非门、异或门、同或门的类别顺序遍历,确保输出格式完全匹配题目要求。
3.2 作业集 5:扩展元件阶段的设计陷阱
3.2.1 引脚编号顺序与类型映射错误
问题描述:新增元件的引脚按 “控制 - 输入 - 输出” 顺序编号,初期开发时混淆了三态门的引脚功能,将 0 号引脚当作输入、1 号当作控制。测试样例 7 中,控制端 E 接 S1-0(0 号引脚),输入 I 接 S1-1(1 号引脚),按错误逻辑控制端为 1、输入为 1,输出结果巧合为 1,看似正确;但更换测试用例将控制端设为 0 时,输出仍跟随输入变化,才暴露逻辑错误。
排查过程:逐一核对三态门的引脚定义,对照题目 “0 号引脚为控制端、1 号输入端、2 号输出端” 的规定,发现引脚类型与编号的映射完全颠倒。
解决方案:抽离通用的引脚索引计算方法,每个元件仅需定义控制、输入、输出引脚的数量,由父类统一计算对应编号区间,从机制上避免单个元件手动编号的人为错误。修正后三态门所有测试用例均通过,且后续新增元件无需再手动处理编号映射。
3.2.2 译码器低有效输出逻辑反转
问题描述:题目规定译码器正常工作时,“只有一个输出信号 0,其余输出为 1”,即低电平有效。初期开发时惯性理解为高电平有效,将选中的输出设为 1、其余为 0。测试样例 8 输入 A=0、B=0,控制端有效,按错误逻辑输出 Y0=1,对应输出 M(2)1:0 居然与样例结果一致,误以为逻辑正确;直到测试输入 01 的情况,错误输出 M(2)1:1,与预期一致,才发现是编号巧合,实际电平逻辑完全相反。
排查过程:手动对照 74LS138 译码器的真值表,逐位核对输出电平,确认输出为低有效,修正输出电平逻辑。
解决方案:重写 Decoder.calculate() 方法,控制有效时,所有输出引脚默认置 1,再将选中的输出引脚置 0。同时补充全 0、全 1、控制无效等多组边界测试用例进行验证,确保逻辑正确。
3.2.3 数据分配器无效状态的输出格式错误
问题描述:数据分配器未选中的输出引脚为无效状态,题目要求输出 “-”。初期代码将无效状态默认设为 0,导致测试样例 10 的输出为 F(3)1:01000000,与正确结果 -1------ 不符。
解决方案:为引脚信号值新增 SignalState 枚举,包含 LOW(0)、HIGH(1)、INVALID(无效)三种状态,替代原来的 null;元件计算时,未选中的输出引脚设为 INVALID;输出格式化时,0/1 正常显示,INVALID 显示为 “-”。该方案同时解决了输入不全时的状态表示问题,代码可读性与健壮性均有提升。
3.3 作业集 6:架构升级阶段的深层问题
3.3.1 子电路与主电路的命名空间冲突
问题描述:题目规定子电路内部的元件编号与主电路可以重复。初期开发时将所有元件存入全局的元件 Map 中,子电路实例化后,其内部元件会覆盖主电路同编号的元件。例如测试样例 1 中,子电路 C1 内的 A (2) 1 与主电路的 A (2) 1 冲突,导致主电路元件被覆盖,计算结果错误。
解决方案:为每个子电路实例创建独立的元件容器,主电路维护自身的元件集合,子电路的元件仅在子电路内部可见;输出时,子电路的元件名称前拼接子电路编号前缀(如 C1-A(2)1-0),既实现了命名空间隔离,也符合输出格式要求。
3.3.2 子电路内外引脚的映射断裂
问题描述:初期子电路的 INPUT/OUTPUT 仅作为字符串标识,未建立与内部信号节点的映射关系。测试样例 1 中,主电路向 C1-A、C1-B 赋值后,子电路内部的 A、B 输入节点始终为 null,导致与门无法计算,输出为空。
解决方案:在 SubCircuit 类中维护两张映射表:inputPinMap(外部输入名→内部信号节点)、outputPinMap(外部输出名→内部信号节点)。子电路实例化时,根据 INPUT/OUTPUT 定义创建对应的内部信号节点;主电路写入子电路输入引脚时,通过映射表找到内部节点并赋值;子电路计算完成后,将内部输出节点的值同步到外部输出引脚。修正后信号可正常传入传出子电路,测试样例 1、2 均通过。
3.3.3 异常检测的优先级与范围错误
问题描述:两个典型错误:
优先级颠倒:初期先检测信号冲突,再检测多输入异常,导致测试样例 8 中,同一连接行包含多个输出(多输入异常),却先输出了冲突错误,不符合优先级要求。
多异常输出:初期遍历所有连接行,将所有异常全部输出,而题目要求仅处理最先出现的异常。
解决方案:
严格按照题目给出的 1-5 级优先级顺序编写校验逻辑,在 validateConnection 方法中按顺序判断,命中第一个异常后立即返回,不再执行后续校验。
遍历连接行时,维护一个全局的错误标记;一旦发现第一个异常,立即终止所有解析流程,直接输出错误信息,不再处理后续内容。
修正后所有异常测试用例(3-8)均输出正确的错误信息与优先级。
四、改进建议
结合三次作业的开发经验,针对当前代码的可扩展性、性能与鲁棒性,提出以下可持续改进方向。
4.1 架构层面:从继承模式转向策略模式
当前的元件设计采用类继承结构,每新增一种元件就需要新增一个子类,随着元件类型增多,类层次会愈发臃肿,且继承的静态特性无法支持运行时动态修改元件逻辑。可改用策略模式进行优化:
将元件的计算逻辑抽离为 CalculateStrategy 接口,不同的门逻辑对应不同的策略实现类;
元件类统一为通用的 CircuitElement 类,通过注入不同的计算策略实现不同功能,引脚配置也通过配置类传入,无需通过继承区分元件类型。
该方案的优势在于:元件类型的扩展无需新增类,仅需新增策略实现,更符合开闭原则;同时支持运行时动态修改元件逻辑,灵活性更强,为后续时序元件、可编程元件的扩展打下基础。
4.2 算法层面:从拓扑排序转向事件驱动仿真
当前的拓扑排序算法仅适用于无反馈的组合电路,对于后续迭代中的时序电路(D 触发器、JK 触发器)与带反馈的电路,DAG 假设不再成立,拓扑排序无法处理。可升级为事件驱动的离散事件仿真算法:
维护一个事件队列,记录信号变化的时间与目标引脚;
按时间顺序处理事件,更新引脚信号后,触发下游元件重新计算,产生新的事件加入队列;
支持信号传播延迟与时序逻辑的时钟触发,兼容组合电路、时序电路、反馈电路等所有场景。
该算法是专业数字电路仿真工具的核心机制,完全匹配后续作业的迭代需求,也能更真实地模拟实际数字电路的运行机制。
4.3 鲁棒性层面:扩展异常检测的覆盖范围
当前仅实现了题目要求的 5 种连接异常,实际场景中还存在大量潜在异常,可逐步补充:
语法类异常:元件名称格式错误、引脚号越界、INPUT 格式错误、括号不匹配等输入语法校验;
语义类异常:同种元件编号重复、子电路递归引用、子电路输入输出数量不匹配、输出引脚短接等逻辑校验;
电路类异常:电路存在组合环路、输出引脚悬空、控制引脚未连接等电路逻辑警告。
同时可引入异常分级机制,分为致命错误(无法运行)、警告(可运行但结果可能异常),提升程序的容错能力与用户友好性。
4.4 性能层面:子电路享元优化
当前每个子电路实例都会复制一份完整的内部元件结构,若同一子电路被多次引用,会造成大量内存冗余。可采用享元模式优化:
将子电路的元件模板、连接关系等不变内容作为共享元数据,所有实例共用一份;
每个子电路实例仅保存自身的引脚信号状态,计算时复用共享模板的逻辑。
对于大规模电路、子电路高频复用的场景,该优化可大幅降低内存占用,提升程序运行效率。
4.5 工程层面:完善单元测试与调试工具
当前开发主要依赖黑盒测试,调试效率较低,问题排查成本高。可补充工程化能力:
为每个元件类编写 JUnit 单元测试,覆盖真值表的所有组合,确保单个元件逻辑正确,避免联调时排查底层错误;
增加调试输出模式,可打印每个元件的输入输出值、拓扑排序顺序、信号传播路径,便于快速定位问题;
引入电路可视化功能,根据输入自动绘制电路结构图,高亮显示信号电平,直观展示电路运行状态。
五、总结
5.1 阶段收获
通过本阶段三次作业的迭代开发,我在技术与思维两个层面均有显著收获:
面向对象设计能力的深化:从基础的继承多态,到开闭原则的实践,再到组合模式的应用,逐步理解了面向对象设计的核心思想,学会了从架构层面思考代码的可扩展性,而非仅满足功能实现。
数据结构与算法的落地应用:将拓扑排序算法从课本知识落地到实际的电路信号传播场景中,理解了 DAG 模型的适用场景与局限性,也体会到算法选择对架构扩展性的深远影响。
迭代开发与代码重构的实践:三次作业在前一次代码基础上不断扩展,经历了两次架构级别的重构,学会了在兼容原有功能的前提下进行代码升级,理解了软件工程增量迭代的开发模式,掌握了基本的代码重构技巧。
问题排查与调试能力的提升:通过处理字符串解析、逻辑反转、映射错误等各类问题,掌握了 “定位问题 - 复现 bug - 根因分析 - 验证修复” 的调试流程,学会了用边界测试用例验证逻辑正确性,调试效率大幅提升。
数字电路知识的交叉巩固:编程实现的过程中,同步巩固了数字电路中逻辑门、译码器、数据选择器等元件的工作原理,理解了硬件仿真的底层逻辑,实现了软硬件知识的交叉融合。
5.2 后续学习方向
本阶段的学习也暴露了自身的不足,后续需在以下方向进一步深入研究:
设计模式的系统学习:当前仅应用了组合模式,对策略模式、责任链模式、享元模式等更多设计模式的理解仍停留在理论层面,需结合实际场景多加练习,提升架构设计能力。
时序电路仿真算法研究:后续作业将引入时序电路与反馈,需提前学习离散事件仿真、事件队列算法,了解硬件描述语言的仿真原理,为后续迭代做好技术储备。
大规模代码的重构技巧:随着代码量突破千行,代码的可维护性逐渐下降,需学习代码坏味道识别、重构手法,保持代码的整洁与可扩展。
软件测试体系的建立:当前测试手段较为单一,需系统学习单元测试、集成测试的方法论,养成测试驱动开发的习惯,提升代码质量。
总体而言,本阶段三次作业是一次完整的小型软件项目开发实践,从需求分析、架构设计到编码实现、调试优化,完整覆盖了软件开发的核心流程,为后续更复杂的系统开发打下了坚实的基础。

posted @ 2026-06-24 22:01  椎名真昼无敌  阅读(9)  评论(0)    收藏  举报