面向对象程序设计————4~6次作业集总结

前言

本阶段PTA作业围绕“数字电路模拟程序”展开,从基础逻辑门模拟到复杂组合逻辑元件,再到子电路与异常处理,共完成了三个版本的迭代开发。这三次作业构成了一个从逻辑门到组合电路再到层次化设计的完整递进,不仅考察了Java面向对象编程的核心技能,更深刻锻炼了系统架构设计、复杂数据处理和异常处理能力。

第四次作业

知识点:

1.继承与多态:通过抽象类Gate定义统一接口,子类实现各自逻辑。
2.集合与排序:使用HashMap存储信号,ArrayList存储元件,Collections.sort按类型和编号排序输出。
3.队列与广度优先遍历:利用Queue实现信号传播,确保组合逻辑的正确计算顺序。

题量:

较小,核心类约10个(Gate及其子类、CircuitData、SignalPropagator、InputParser、OutputGenerator等),总代码约480行。

难度:

入门级,重点在于理解面向对象的基本概念(继承、多态)以及从“面向过程”到“面向对象”的思维转换。算法方面仅为队列遍历和简单的逻辑运算。

第五次作业

知识点:

1.工厂模式:GateFactory统一创建各类元件,降低耦合。
2.多输出处理:通过Map<Integer,Integer>返回多个引脚输出。
3.控制引脚与高阻态:引入Signal.HIGH_Z = -1表示无效状态。
4.正则表达式:利用Pattern/Matcher解析复杂元件名(如A(8)1、Z(2)2)。

题量:

适中,核心类增至14个,增加了三态门、译码器、数据选择器、数据分配器等新型元件,总代码约745行。

难度:

较简单,虽然元件类型增多,但通过工厂模式和清晰的类继承体系,逻辑上反而更加清晰。主要挑战在于处理多输出元件的不同输出格式以及控制引脚的有效性判断。

第六次作业

知识点

1.组合模式:SubCircuit继承自Gate,使子电路与基本元件对外表现一致。
2.递归求值与缓存管理:采用递归evalNode计算信号,并注意缓存策略防止死缓存。
3.异常优先级检测:按题目规定的顺序检查五种连接错误,并输出第一条。
4.子电路引脚映射:通过前缀区分不同子电路的同名元件。

题量:

偏多,类的数量增至16个(新增SubCircuit、PinClass枚举、异常检测逻辑等),总代码突破1055行。输入处理涉及子电路定义和主电路引用,输出要求带子电路前缀。

难度:

较难,核心难点在于子电路的递归展开和异常检测的优先级逻辑。此外,缓存失效问题需要仔细设计,确保依赖门的正确计算。

设计与分析

第四次作业

作业要求

设计一个基础数字电路模拟系统,支持五种基本逻辑门:与门(A)、或门(O)、非门(N)、异或门(X)、同或门(Y)。
输入包括外部信号(INPUT: A-1 B-0)、连接关系([输出 输入1 输入2 ...])和结束标志(end)。
输出按与、或、非、异或、同或的顺序,同类按编号从小到大排序,格式为“元件名-0:电平值”。若某个门的输入引脚未全部收到信号,则忽略该门的输出。

实现方式

1.类结构:定义抽象类Gate,子类GateA、GateO等分别实现action()方法完成逻辑运算。每个门内部维护inputPins列表和pinValues映射,用于存储各引脚的输入值。
2.数据存储:CircuitData类持有全局的gateMap(元件名→Gate对象)、connections(输出引脚→输入引脚列表)、signalCache(引脚→信号值)和signalQueue(待传播信号队列)。
3.信号传播:采用队列驱动的广度优先遍历。外部输入信号首先入队,每次从队列取出一个信号,查找其所有目标输入引脚,若该引脚所属门的所有输入均已就绪,则计算该门输出,并将输出信号入队。若某门输入不全,则暂不计算,等待后续信号补充。
4.解析与输出:InputParser逐行解析,先将连接关系存储,待全部读取完成后,再统一调用SignalPropagator传播。OutputGenerator按类型和编号排序,仅输出已计算的门。

代码规模

屏幕截图 2026-06-24 171948

类图

屏幕截图 2026-06-24 173254

Bug分析

Bug 1:忽略引脚号导致多输入门失效
最初从"A(8)1-1"中只提取元件名A(8)1,丢弃引脚号,导致与门所有输入都被当作1号引脚。修正:使用正则表达式提取完整引脚名,并以Map<Integer,Integer>按引脚号存储输入值。
Bug 2:边读边算导致信号丢失
在解析连接行时立即传播信号,但若目标门尚未定义(后续行才出现),则信号永久丢失。修正:先完整解析所有输入和连接,存储到数据结构中,全部读完后统一计算。
Bug 3:排序不稳定
输出时使用冒泡排序,重量相等时错误交换,导致顺序不稳定。修正:改用Collections.sort配合Comparator,保证稳定排序。

改进建议

引入拓扑排序确定门计算顺序,替代队列传播,以更健壮地处理无环组合逻辑。
将输入解析和信号计算分离到不同阶段,提高代码可读性。
使用Enum定义元件类型,避免字符串比较。

第五次作业

作业要求

在第一次作业基础上新增四种元件:
-三态门(S):控制端为1时输出等于输入,为0时输出高阻(-1)。
-译码器(M):控制条件S1=1且S2+S3=0时有效,此时只有一个输出为0,其余为1;无效时所有输出为高阻。
-数据选择器(Z):根据控制引脚选择一路输入输出。
-数据分配器(F):将输入分配到指定输出,其他输出为高阻。

实现方式

1.扩展Gate体系:新增GateS、GateM、GateZ、GateF子类,均继承Gate并重写compute()返回Map<Integer,Integer>以支持多输出。
2.工厂模式:GateFactory使用正则表达式匹配元件名,统一创建实例,降低了InputParser的复杂度。
3.引脚管理:在构造函数中根据元件类型和参数动态填充inputPins和outputPins列表,明确引脚角色。
4.高阻态处理:定义Signal.HIGH_Z = -1,在传播和输出时跳过高阻信号。
5.输出定制:OutputGenerator根据元件类型分别调用不同的打印方法,满足格式要求。

代码规模

屏幕截图 2026-06-24 172025

类图

屏幕截图 2026-06-24 175407

Bug分析

Bug 1:译码器控制条件误判
将条件写为S11&&S20&&S3==0,未考虑控制引脚可能为高阻(-1)。修正:先检查所有控制引脚是否都有有效输入(非HIGH_Z),再判断数值。
Bug 2:数据选择器引脚编号偏移错误
为Z(2)(2个控制引脚)生成数据引脚从1开始,但实际应从2开始。修正:根据控制引脚数计算偏移量ctrlCount,数据引脚编号为ctrlCount + i。
Bug 3:输出格式不匹配
译码器试图输出所有引脚电平,与题目要求“只输出为0的引脚号”不符。修正:扫描所有输出引脚,找到值为0的引脚号并打印。数据分配器则需拼接所有输出引脚信号,高阻用-表示。
Bug 4:三态门输出高阻时仍传播
未判断输出是否为高阻,仍将其加入队列,导致后续门收到-1。修正:在addPinSignal时若值为HIGH_Z,不入队。

改进建议

将引脚编号规则抽象为PinScheme接口,不同元件实现自己的方案,避免硬编码。
使用Enum定义元件类型,提高类型安全性。
分离OutputGenerator中的格式逻辑,每个元件类型独立方法。

第六次作业

作业要求

在第二次作业基础上新增两大核心功能:
子电路(SubCircuit):允许将一部分电路封装为一个可复用的子电路,在主电路中被引用。子电路有自己的输入(INPUT:)、输出(OUT:)和内部连接,定义在C编号:和endc之间。子电路的引脚在主电路中通过C编号-引脚名引用。子电路内部元件编号与主电路独立,但输出时需带上子电路前缀(如C1-A(2)1-0)。
异常检测:按优先级检测五种连接错误:
连接信息包含多个输出(more than one input)
连接信息无输入(none input)
连接信息无输出(none output)
输入输出顺序错误(sequence error)
输入引脚信号冲突(input signal conflict)
一旦发现错误立即输出并停止处理。

实现方式

1.组合模式:SubCircuit继承自Gate,内部维护自己的gateMap和连接关系。action()方法递归计算内部元件,并映射输入输出引脚。
2.引脚角色分类:在InputParser中定义PinClass枚举(SOURCE、LOAD、UNKNOWN),通过classifyToken方法根据上下文判断每个token的角色,用于异常检测。
3.异常优先级检查:processConnection方法严格按照题目顺序依次检查:多个输出→无输入→无输出→顺序错误→冲突,一旦命中立即输出错误并设置错误标志。
4.缓存策略优化:evalNode递归求值时,仅在值有效(非-1)时存入signalCache,避免“死缓存”导致依赖门永远为-1。
5.命名前缀:创建子电路内的门时,为其名称添加"C"+cid+"-"前缀,确保全局唯一。

代码规模

屏幕截图 2026-06-24 172057

类图

屏幕截图 2026-06-24 175928

Bug分析

Bug 1:缓存失效导致依赖门永远为-1
在evalNode中,若某节点暂不可算(依赖源未就绪),错误地将其缓存为-1,导致后续即使源就绪也无法更新。修正:仅在值有效时缓存,暂不可算则返回-1但不缓存,让上层重新尝试。
Bug 2:子电路引脚名解析歧义
无法区分C1-A是子电路引脚还是元件引脚(如C1-A(2)1)。修正:通过判断-之后的部分是否匹配门命名规则(包含括号或数字),若不匹配则视为子电路引脚。
Bug 3:异常优先级混乱
未按题目要求的顺序检查,导致输出错误。修正:严格按照文档顺序编写if分支,一旦命中立即返回。
Bug 4:子电路内部元件编号冲突
主电路和子电路可能都有A(2)1,输出时混淆。修正:为子电路内所有门添加前缀Ccid-,存储和输出均用完整名称。
Bug 5:输入引脚冲突检测遗漏
当冲突发生在不同连接行时,未正确记录源。修正:在tempSourceMap中记录每个目标引脚及其源,解析完所有连接后统一检查。

改进建议

将异常检测提取到独立的Validator类,分离关注点。
使用访问者模式遍历电路结构,替代递归evalNode,便于扩展。
引入依赖图和拓扑排序,明确计算顺序,避免递归深度过大。
对子电路引脚映射建立统一命名规范,简化解析逻辑。

总结

通过这三次迭代作业,我在Java编程、设计模式和系统架构方面获得了显著提升:
面向对象设计能力:从简单的继承到工厂模式、组合模式的实际应用,我深刻理解了“开闭原则”和“单一职责原则”的重要性。特别是组合模式,让我体会到“统一处理叶子节点和组合节点”的强大威力——子电路和基本元件对外表现一致,主电路无需关心内部细节。
数据结构与算法应用:从ArrayList到HashMap+Queue的组合,再到递归求值和缓存策略,我学会了根据业务场景选择合适的数据结构。队列驱动的广度优先遍历和递归依赖求解都加深了我对图遍历算法的理解。
复杂需求分析能力:第三次作业的异常优先级和子电路嵌套,迫使我设计状态机式的解析流程,而不是零散打补丁。我学会了在编码前梳理所有可能的输入情况,并制定优先级规则。
调试与问题定位:通过“边读边算”、“缓存失效”、“引脚解析”等多个Bug的排查,我系统掌握了从日志输出到断点调试,再到构造最小测试用例的故障定位方法。
代码质量意识:当代码超过500行时,注释和模块拆分变得至关重要。我养成了每个方法前写功能描述、复杂分支添加注释的习惯,并在多次重构中体会到“可维护性”比“快速通过测试”更有价值。
不足之处与未来方向
异常处理系统性不足:当前异常检测与解析逻辑混杂,应设计统一异常类层次,使用try-catch替代返回值判断。
测试覆盖率低:仅依赖样例测试,边界情况未充分覆盖。后续应学习JUnit,为每个门类和解析方法编写单元测试。
设计模式理解尚浅:虽然用了工厂和组合模式,但未能灵活运用访问者、模板方法等更高级的模式,需进一步学习《设计模式》并实践。
代码重构能力待提升:第三次作业中processConnection方法圈复杂度高达45,应通过提取子方法和引入策略模式重构,降低复杂度。

posted @ 2026-06-24 18:00  UtopiaOvO  阅读(5)  评论(0)    收藏  举报