作业集4~6总结——数字电路模拟程序的迭代设计与反思

Java面向对象程序设计作业集4~6总结——数字电路模拟程序的迭代设计与反思

本篇博客是我针对本学期作业集4、5、6三次数字电路模拟编程作业的完整复盘。这三道作业一脉相承,从最简单的基础逻辑门,一步步叠加复杂元器件、子电路封装,最后补充全套异常输入校验。做完这一整套迭代项目,我也实实在在感受到了面向对象编程、迭代开发和代码健壮性设计在实际写代码过程中的作用,收获远比单纯刷一道算法题更多。

一、前言

这三次作业并不是独立的编程题目,而是一套循序渐进的迭代开发任务。作业4只需要完成最基础的五种逻辑门运算;到了作业5,题目新增了带控制引脚的译码器、选择器等复杂器件;作业6难度直接拉满,不仅支持自定义子电路嵌套调用,还要求程序主动识别各类非法输入并报错。

说实话,刚开始做第一题的时候,我以为只是简单写几个逻辑判断就能过关,真正往后做才发现难点从来不是逻辑运算本身,而是怎么不改旧代码,就能兼容新需求。我身边不少同学前期图省事直接写死代码,到了第二次第三次作业,只能全盘重写,浪费了大量调试时间。

另外这套作业题干很长,输入格式十分繁琐,不仅要解析元件信息,还要处理引脚连接、信号逐级传递、最终排序输出。尤其是第三次加入子电路之后,电路变成了多层嵌套结构,再加上强制要求异常捕获,整个程序的复杂度上升非常明显。

经过这三次迭代开发我最大的感悟就是:面向对象编程核心就是提前做好建模。前期把引脚、元器件、电路整体结构拆分清楚,后面不管加多少新功能,都只需要新增子类、补充方法即可,不用动底层代码,这也是开闭原则最直观的一次实践。

图1 作业集4-6 PTA后台作业完成列表

二、作业总体分析

作业

核心内容

新增难点

个人完成情况

作业集4

基础逻辑门模拟

不规则输入解析、信号逐级传递、基础门电路逻辑运算

好100/100

作业集5

增加复杂组合逻辑元件

区分控制引脚与普通引脚、多路输入输出、高阻无效状态处理

较好98/100

作业集6

增加子电路与异常输入检测

多层嵌套电路管理、全流程异常捕获、边界非法输入排查

中等91/100

三次作业完全贴合现实软件开发的迭代流程:版本1搭建基础框架,版本2拓展业务功能,版本3优化程序容错性与稳定性。平时课堂上学到的迭代开发不再是书本上的概念,而是实实在在体现在每一次代码修改当中。

结合自己全程调试的经历,我总结出本次项目四个最难攻克的痛点,也是大部分同学都会卡壳的地方:

  1. 输入格式杂乱无章:元器件命名格式不统一,既有带括号标注引脚数量的A(4)1,也有简短格式 N1,没办法用统一的字符串分割方式处理输入,很容易出现解析报错。
  2. 信号运算存在先后依赖:电路信号是串联传递的,前一个元件的输出是后一个元件的输入,不能按照输入顺序直接运算,必须循环刷新信号状态,直到电路信号不再变化。
  3. 元器件规格差异巨大:基础门电路大多单输入单输出,后续新增器件多路输入、多路输出,引脚数量完全不统一,通用代码很难适配全部器件。
  4. 异常检测覆盖面广:之前做题默认输入全部合法,第三次作业需要自己预判所有错误场景,在代码前期就做好拦截,对代码整体结构要求更高。

三、第一次作业:基础逻辑门电路模拟

3.1 题目要求与设计目标

作业4只需要实现五种最基础的逻辑门:与门、或门、非门、异或门、同或门。程序读取外部输入信号以及引脚连接关系,自动完成电路信号传递,最后按照规定顺序打印每一个元器件的输出电平。

这次作业核心目的是搭建整套电路仿真的基础骨架。虽然功能简单,但是题目已经出现了一个输出对接多个输入的场景,如果前期代码写得太随意,后续迭代会直接崩盘。

图2 第一次作业基础逻辑门模拟题目要求及样例

3.2 类设计分析

最开始我试过把所有代码写在一个Main类里,写了一半发现代码混乱、完全没法复用,于是重新拆分,按照功能划分出五个核心类,贴合课堂上学的封装和继承思想:

  • Pin引脚类:单独管理每一个引脚,记录归属元器件、引脚编号、实时信号以及输入输出属性;
  • Gate抽象门电路父类:抽取所有逻辑门的公共属性,统一规定运算抽象方法;
  • 各类具体门电路子类:分别重写运算方法,实现专属的逻辑运算规则;
  • Circuit电路管理类:全权负责输入解析、元器件管理、信号循环刷新;
  • Main主入口类:只负责接收控制台输入,调用对应方法,不写复杂业务逻辑。

这套拆分最大的好处就是各司其职:门电路只关心自己怎么计算结果,电路类只关心信号怎么流转,后续拓展功能不用改动无关代码。

IMG_256图3 第一次作业程序UML类图

从类图也能直观看出来,抽象父类Gate统一约束了全部元器件的公共行为。利用方法重写实现多态,全程没有堆砌大量if-else判断元器件类型,代码可读性高,也方便后续直接新增器件子类。

3.3 信号传播算法分析

我一开始踩了大坑:直接按照输入顺序依次计算元器件输出,结果多级串联电路全部答案错误。后来才想明白电路是信号驱动,必须等待前置引脚信号就绪之后,才能计算当前元器件结果。最终我采用循环迭代刷新信号的方案:

  1. 读取全部外部初始输入信号;
  2. 把初始信号绑定到对应输入引脚;
  3. 遍历所有元器件,判断是否全部输入引脚信号就绪;
  4. 满足条件则计算输出,同步更新下游连接引脚;
  5. 反复循环遍历,直到一轮遍历没有任何引脚信号发生变化,电路达到稳定状态。

这套算法逻辑直白易懂,适配本次无反馈的组合逻辑电路,完全可以满足题目全部运算需求。

IMG_256

图4 电路信号循环传播执行流程图

text
读取输入

解析元件和连接关系

外部输入是否可传递?

更新目标引脚

元件输入是否完整?

计算元件输出

继续传播,直到无新变化

按要求输出结果

3.4 第一次作业中的问题与反思

本次作业我耗时最长的地方就是字符串解析。一开始偷懒直接固定下标截取元器件编号,遇到括号内数字位数变化,直接数组越界报错,调试了很久才找到问题根源。

之后我修改了解析逻辑:先匹配元器件首字母区分类型,再判断字符串是否包含括号,分两种分支分别解析参数,彻底解决格式不统一带来的bug。

除此之外还有一个很隐蔽的错误:引脚信号不全的时候,不能默认补0。一旦强行补0,后续级联电路全部运算出错,这也是很多同学失分的关键原因。

图5 第一次作业最终PTA测试运行结果

四、第二次作业:复杂组合逻辑元件的扩展

4.1 新增功能分析

作业5在原有五种基础门电路之上,新增了三态门、译码器、数据选择器、数据分配器四类复杂器件。和基础门电路最大的不同是,这类器件单独设有控制引脚,同时支持多路输出,并且存在高阻无效态,不再只有0和1两种电平。

三态门依靠控制端决定电路通断,断开之后输出高阻信号;译码器根据输入二进制编码唯一选中一路输出;选择器和分配器互为反向逻辑,分别实现多路选一和一路多分。整体逻辑比基础门电路复杂不少。

图6 第二次作业新增复杂组合逻辑元件说明

4.2 设计变化

版本一代码默认所有元器件只有一个输出引脚,完全无法适配本次多路输出器件,因此我针对性重构了引脚结构,把引脚分为三类,不再写死引脚功能:

  • 控制引脚:专门管控器件启停与通道选择;
  • 普通输入引脚:接收上游传递的电平信号;
  • 输出引脚:向外输出运算结果或者高阻信号。

这次重构很关键,后续不管新增多少种特殊元器件,都不需要改动底层引脚代码,只需要在子类内部自定义引脚布局即可,真正做到了对修改关闭、对拓展开放。

IMG_256图7 第二次作业迭代优化后的程序UML类图

如果这次我选择偷懒,继续用旧代码加大量分支判断兼容新器件,代码会变得极其臃肿,等到第三次作业几乎无法维护。前期适当重构,反而能省下后期大量调试时间。

4.3 复杂元件的计算逻辑

这四种复杂器件不能复用统一的运算逻辑,我选择分开编写独立方法。选择器依靠控制引脚二进制数值选择对应通路;译码器先要校验控制端是否开启工作模式,再完成译码输出;分配器反向分发信号。

经过这次迭代我真正明白:面向对象不是单纯建类,而是把不一样的业务逻辑互相隔离。分散的独立方法远比一坨堆砌判断的大方法更好调试、更好改错。

4.4 第二次作业中的难点

本次作业扣分点大多都是细节问题,我整理了自己实际踩过的五个坑:

  1. 容易混淆控制引脚和普通输入引脚,导致器件始终无法正常工作;
  2. 多路输出引脚格式把控不好,输出格式和题目要求不符;
  3. 最容易出错:把高阻无效信号当成低电平0处理,连锁引发后续电路错误;
  4. 引脚信号没有全部就绪就提前运算,结果全部失效;
  5. 最后忘记按规则排序,逻辑全对但格式测试点全部不通过。

图8 第二次作业复杂测试用例运行结果

五、第三次作业:子电路与异常输入检测

5.1 作业特点

作业6是整套项目的最终版本,难度提升最大。一方面新增了可复用的子电路功能,支持电路嵌套;另一方面要求全方位异常捕获,不能再默认用户输入合法。

这一次程序不再是简单的单层电路仿真工具,而是具备模块化复用能力、自带错误拦截能力的完整小型仿真系统,也更贴近真实软件开发项目。

图9 第三次作业子电路与异常检测题目要求

5.2 子电路设计思路

我直接把子电路定义为一种特殊复合元器件,和普通逻辑门接入同一套电路调度体系。普通门电路依靠固定公式运算,子电路依靠内部封装好的多级电路完成信号计算。

这样设计的优势十分明显:外层主电路不需要关心子电路内部复杂的接线,只需要对接对外的输入输出接口即可,实现了代码和电路结构的双层封装。

IMG_256

图10 子电路分层封装结构示意图

写完子电路模块之后,我彻底理解了软件开发里模块化封装的意义。复杂系统分层拆解之后,每一层只需要关注自己的上下游接口,整体开发难度会大幅下降。

5.3 异常检测分析

异常检测需要覆盖代码运行全流程,我结合题目要求,一共梳理出8类必须拦截的错误场景,在代码解析、连接、运算每一步前置校验:

  • 元器件名称非法,不存在对应器件类型;
  • 引脚编号超出该器件自带引脚最大范围;
  • 同一个输入引脚被多路输出重复连接;
  • 输出引脚之间互相非法对接;
  • 调用子电路时参数和预定义规格不匹配;
  • 整体输入语句格式不符合规范;
  • 调用还没有提前定义的子电路;
  • 引用系统中不存在的外部输入信号。

异常最难处理的地方在于错误分散在不同代码阶段,不能统一在最后判断。全程前置拦截,才能避免非法数据进入运算环节,防止程序直接崩溃。

图11 自定义异常输入测试用例

图12 程序异常报错输出结果

六、SourceMonitor代码复杂度分析

为了客观查看自己三次迭代之后代码的冗余程度和方法复杂度,我使用SourceMonitor工具对完整源码进行扫描,量化分析自己代码架构的优缺点,而不是凭主观感觉判断代码好坏。

图13 SourceMonitor代码整体指标统计报表

图14 程序各方法圈复杂度明细列表

从检测报表可以直观看到,各类门电路、引脚实体类的方法都很简单,圈复杂度很低,符合实体类轻量化的设计要求。

相对而言,输入解析方法、全局信号刷新方法、异常校验方法复杂度明显更高,原因是这部分需要处理大量分支情况。也能看出我目前代码的短板:电路总类职责还是偏多,后续依旧有拆分优化空间。

复杂度高不代表代码错误,但是一个方法包揽太多工作,后续阅读和维护都会变难,这也是我后续优化的核心方向。

类名

主要职责

复杂度情况

后续改进方向

Pin

保存引脚状态、信号数值

较低

保持现有精简结构,无需修改

Gate

抽象所有电路元件公共行为

较低

持续作为顶层父类,统一规范接口

Circuit

管理电路全局连接、信号传播

较高

拆分解析模块与运算模块,分离职责

SubCircuit

封装子电路内部逻辑与接口映射

中等偏高

抽离子电路通用接口,降低耦合

Main

程序入口、调度整体流程

较低

保持极简入口,不写入业务逻辑

七、踩坑心得与测试分析

7.1 输入解析不能只依赖样例数据

这次作业给我最大的教训就是:千万不要面向样例写代码。样例输入格式规整,但是测试点边界情况很多,只适配样例一定会翻车。写代码之前一定要先梳理全部输入规则,再动手写解析逻辑。

7.2 无效高阻态不可和0、1电平混用

我前期一半的bug都来源于分不清高阻态和低电平。后来专门用-1代表无效高阻信号,区分三种引脚状态,才彻底修好信号错乱的问题。也明白了电路里不存在默认0电平,悬空引脚就是未知状态。

7.3 输出排序细节极易失分

编程题很多时候不是逻辑难,而是细节苛刻。哪怕全部运算都正确,只要最后没有按要求排序,依旧拿不到满分。之后我养成习惯:代码写完之后,单独检查输出格式,避免细节丢分。

7.4 异常检测需要前置布局

异常处理绝对不能后期补代码。如果前期实体类没有预留引脚连接状态、元器件注册状态字段,后期再加异常判断,就要大面积改动旧代码,违背迭代开发的初衷。容错设计一定要放在项目初期。(一个简单的例子)

图15 个人开发过程中遇到的典型代码bug及调试记录

八、后续代码优化改进建议

8.1 统一顶层元件通用接口

后续如果继续拓展更多新型电路器件,可以向上抽取顶层Component通用接口,统一全部元器件的规范方法,让主电路不用区分普通门电路和子电路,进一步提升代码通用性:

text
Component
├── getName() // 获取元件名称
├── getInputPins() // 获取全部输入引脚
├── getOutputPins() // 获取全部输出引脚
├── compute() // 执行信号运算
└── isReady() // 判断元件是否满足运算条件

8.2 彻底拆分代码模块,落实单一职责原则

目前Circuit类承担的工作依旧太多,后续可以拆分为输入解析器、电路构建器、信号仿真器、异常校验器、输出格式化器五个独立工具类,每一个类只负责一件事,彻底解决大类臃肿问题。

8.3 基于拓扑排序优化信号传播效率

当前循环遍历刷新信号的方法简单好写,但是效率一般。后续可以把电路建模为有向图,借助拓扑排序精准找到待运算元器件,减少无效循环遍历,提升程序运行效率。

8.4 自主搭建全覆盖测试用例库

不能完全依赖平台自带测试点。平时自己写代码,主动构造边界用例:单元器件电路、多级串联电路、引脚悬空、非法编号、子电路嵌套等场景,提前排查隐藏bug。

九、全文总结

做完这三次迭代作业,我彻底跳出了刷题只注重答案正确的固化思维,真正理解了面向对象编程在实际项目里的落地用法。

首先,继承和多态从来不是课堂上花哨的语法考点,而是迭代开发最实用的工具。三次需求不断变更,我依托抽象父类+子类重写的架构,几乎没有改动前期写好的底层代码,只用新增子类就完成功能升级,适配需求变更十分顺畅。

其次,模块化封装是解决复杂项目混乱问题的核心。子电路的嵌套封装,和实际开发中拆分功能模块、封装工具类思路完全一致,让我打通了课堂代码和真实项目开发的思维壁垒。

最后,前期架构设计远比后期调试重要。前期偷懒少写几行代码、结构拆分不规范,后期调试就要花费成倍的时间来弥补。合理的前期设计,可以极大降低后续维护和迭代的成本。

总体来说,这三次作业提升了我的编码能力,也帮我建立了基础的软件工程开发思维。后续我也会继续学习设计模式、标准UML建模知识,让自己写出来的代码更加规范、健壮、易维护。

posted @ 2026-06-22 21:49  褐过  阅读(3)  评论(0)    收藏  举报