第二次博客作业

一、前言

在本阶段的Java面向对象编程实践中,我完成了“数字电路模拟程序”的三次迭代开发。该系列作业要求模拟一个由逻辑门元件构成的数字电路系统,逐步增加功能:从基础逻辑门模拟,到组合电路元件与控制引脚,再到子电路封装与异常输入检测。三次作业环环相扣,既考察了面向对象设计能力,又锻炼了对复杂业务需求的分析与实现能力。

  • 第一次作业:实现与门、或门、非门、异或门、同或门五种基本逻辑门,支持任意输入引脚数(与/或门),要求解析引脚连接关系并计算每个门的输出。题目输入较为规整,无异常检测,侧重正则解析和递归求值。

  • 第二次作业:新增三态门、译码器、数据选择器、数据分配器四种元件,引入控制引脚概念,输出格式更加多样(译码器输出0对应的引脚编号,分配器输出带高阻符号的序列)。元件种类从5种扩展到9种,设计复杂度明显上升。

  • 第三次作业:在基础门(仅五种)之上迭代,增加子电路定义与引用、五种异常输入检测(多输入、无输入、无输出、顺序错误、信号冲突)。要求采用组合模式将子电路视为复合元件,输出带子电路前缀。异常检测具有优先级,且仅报告最先出现的异常。

三次作业的题量逐渐增大,第三次作业代码量接近400行。难度曲线从线性解析增长到处理嵌套结构和组合模式,再到复杂的异常规则,每次都需要重新审视架构的可扩展性。下面我将详细分析每次作业的设计思路、源码结构,并分享踩坑经历和改进思考。

二、设计与分析

第一次作业:基础逻辑门电路

设计思路

第一次作业的核心任务是解析门引脚、建立连接关系、计算门输出。我采用了面向过程的函数式解析方法,将每个门抽象为一个包含类型、编号、输入引脚数、输入源列表的结构体。通过正则表达式匹配A(2)1-0等格式,使用HashMap保存目标引脚到源引脚的映射,最后通过递归求值函数得到最终输出。

类图

deepseek_mermaid_20260624_b1c5d9

并没有明确的元件类层次,所有逻辑都耦合在Main类中。

SourceMonitor简要报表

Method ev(G) iv(G) v(G)
Main.main(String[]) 5 8 11
Main.getFullName(Matcher) 1 2 2
Main.registerGateIfOutput(String) 2 2 3
Main.registerGate(String) 3 4 6
Main.evalSource(String) 3 4 5
Main.evalGate(String) 6 9 13
Gate.Gate(char,int,int,String) 1 1 1
Class OCavg WMC
Main 6.67 40
Gate 1.00 1

代码分析

  • 正则表达式([AONXY])(?😦(\d+)))?(\d+)-(\d+)成功匹配各种门引脚,但对后续新增元件扩展性差。

  • 递归求值使用computing集合检测环路,但门之间的依赖通过映射动态查找,存在重复计算,性能尚可。

  • 排序输出时使用字符串"AONXY"定义类型顺序,手动比较,逻辑简单直接。

心得

初次实现时,我过于关注功能实现,忽视了代码的扩展性和可维护性。例如,当需求变为新增元件时,必须修改多处if-else分支。但从快速原型角度,该设计能快速验证逻辑正确性,为后续重构提供了基线。

第二次作业:多元件与控制引脚

设计目标

新增四种元件:三态门(S)、译码器(M)、数据选择器(Z)、数据分配器(F),每种元件的引脚含义不同,输出格式各异。例如译码器需输出“M(3)1:0”表示Y0有效,数据分配器需输出“F(2)1:--0-”表示高阻状态。

重构与类设计

为应对多样性,我引入了抽象类Gate,定义getInputPins()、getOutputPins()和calculate()三个抽象方法,并派生出BasicGate(门)、TriGate(三态门)、Decoder、Multiplexer、Demultiplexer等子类。这种方式将不同类型元件的引脚布局和计算逻辑封装在各自的类中,符合开闭原则。

类图

deepseek_mermaid_20260624_e27c19

关键实现细节

  • 引脚编号约定:三态门0控制、1输入、2输出;译码器0~2控制、3~(2+n)输入、之后为输出;选择器/分配器类似。在子类中通过循环构建引脚列表,确保getInputPins()返回正确排序。

  • 计算有效性:calculate()会根据引脚连接完整性和控制条件决定valid标志。例如译码器要求S1=1且S2+S3=0才工作,否则valid=false,输出时忽略。

  • 输出格式化:对Decoder,寻找输出为0的引脚相对偏移;对Demultiplexer,遍历输出引脚,未选中的设为-1,输出时'-'。

SourceMonitor简要报表

Method ev(G) iv(G) v(G)
Main.main(String[]) 6 8 12
Main.getFullName(Matcher) 2 3 3
Main.registerGate(String) 4 6 9
Main.evalSource(String) 3 4 5
Main.evalPin(String,int) 3 4 6
BasicGate.BasicGate(char,int,String,int) 1 1 1
BasicGate.getInputPins() 1 1 1
BasicGate.getOutputPins() 1 1 1
BasicGate.calculate(Map,Function) 3 5 7
BasicGate.allOne(int[]) 1 1 1
BasicGate.anyOne(int[]) 1 1 1
TriGate.TriGate(char,int,String) 1 1 1
TriGate.getInputPins() 1 1 1
TriGate.getOutputPins() 1 1 1
TriGate.calculate(Map,Function) 2 3 5
Decoder.Decoder(char,int,String,int) 1 1 1
Decoder.getInputPins() 1 1 1
Decoder.getOutputPins() 1 1 1
Decoder.calculate(Map,Function) 4 6 8
Decoder.getPin(Map,Function,int) 2 2 2
Multiplexer.Multiplexer(char,int,String,int) 1 1 1
Multiplexer.getInputPins() 1 1 1
Multiplexer.getOutputPins() 1 1 1
Multiplexer.calculate(Map,Function) 3 4 7
Demultiplexer.Demultiplexer(char,int,String,int) 1 1 1
Demultiplexer.getInputPins() 1 1 1
Demultiplexer.getOutputPins() 1 1 1
Demultiplexer.calculate(Map,Function) 3 4 7
Gate.Gate(char,int,String) 1 1 1
Gate.getInputPins() 1 1 1
Gate.getOutputPins() 1 1 1
Gate.calculate(Map,Function) 1 1 1
Class OCavg WMC
Main 4.60 46
Gate 1.00 4
BasicGate 2.17 13
TriGate 1.75 7
Decoder 2.75 11
Multiplexer 2.25 9
Demultiplexer 2.25 9

分支语句覆盖率:约85%

最大圈复杂度:evalSource()函数因需判断多种元件类型,圈复杂度达12

代码行数:约280行

心得

第二次作业让我深刻体会到面向对象设计在应对变化时的优势。通过抽象基类和工厂式注册(registerGate),新增元件仅需添加一个子类和相应的switch分支,对已有的基本逻辑门代码影响很小。但evalSource和evalPin等求值函数仍需维护一个巨大的类型判断链,这是由于计算逻辑与数据结构耦合过紧造成的。

第三次作业:子电路与异常处理

设计目标

在保留第一次作业的五种基础门基础上,增加子电路封装(可多层嵌套)和五种语法异常检测。题目明确建议使用组合模式,将子电路视为复合元件。输出格式要求带子电路前缀,如C1-A(2)1-0:0。

架构设计

我采纳了组合模式,核心类层次如下:
deepseek_mermaid_20260624_71da02

  • BasicGate作为叶节点,实现与第一次相同的逻辑;SubCircuitInstance作为组合节点,内部包含克隆的元件集合和连接关系。通过compute()递归驱动整个树的求值。

  • 解析流程与异常检测优先级

  • 首先提取所有子电路定义(C1: … endc),构建SubCircuitDef模板。

  • 解析主电路输入、连接信息方括号。

  • 异常检测:在构建连接前,遍历所有连接信息,按优先级顺序检测:

  • 多个源(输出引脚) → ERROR: ... include more than one input

  • 源数量为0 → ERROR: ... include none input

  • 无输入引脚 → ERROR: ... include none output

  • 源不在首位 → ERROR: ... input and output sequence error

  • 遍历连接信息注册元件,同时检测引脚冲突(同一个目标引脚被不同源驱动)。

  • 设置元件输入源,实例化子电路并建立端口映射,最后递归计算所有叶节点并排序输出。

关键改进点

  • outputMapping:子电路定义中,输出端口通过OUT:声明,内部连接信息中输出端口作为目标出现(例如[X1-0 C])。在构建SubCircuitInstance时,预先提取C对应的内部源(X1-0),存为outputMapping,当外部引用C1-C时,通过映射找到内部源并递归求值。

  • 异常检测独立于电路构建,一旦发现错误立即输出并结束程序,符合“只处理排在最前面的异常”要求。

  • 为避免子电路模板被修改影响其他实例,采用原型克隆:在每个SubCircuitInstance构造时,深拷贝templateGates和templateConn,并加上实例前缀(如C1-),实现命名空间隔离。

SourceMonitor简要报表

Method ev(G) iv(G) v(G)
Main.main(String[]) 7 10 14
Main.checkConnectionError(String) 4 6 8
Main.isSource(String) 4 7 9
Main.registerComponent(String,String) 3 5 6
Main.connect(String,String) 2 3 4
Main.evalSource(String) 6 9 12
Main.findComponent(String) 3 4 5
Main.findInSub(SubCircuitInstance,String) 2 3 4
Main.buildTemplateGates(SubCircuitDef) 3 4 5
BasicGate.BasicGate(String,char,int,int) 1 1 1
BasicGate.setInput(int,String) 1 1 1
BasicGate.compute() 4 6 8
BasicGate.getOutput() 1 1 1
BasicGate.getOutputPinName() 1 1 1
BasicGate.allOne(int[]) 1 1 1
BasicGate.anyOne(int[]) 1 1 1
SubCircuitInstance.SubCircuitInstance(String,SubCircuitDef,String) 2 2 2
SubCircuitInstance.connectInput(String,String) 2 2 2
SubCircuitInstance.compute() 3 4 5
SubCircuitInstance.getOutput() 1 1 1
SubCircuitInstance.getOutputPinName() 1 1 1
SubCircuitInstance.getAllGates() 1 1 1
SubCircuitDef.SubCircuitDef(String) 1 1 1
Component.Component(String) 1 1 1
Component.compute() 1 1 1
Component.getOutput() 1 1 1
Component.getOutputPinName() 1 1 1
Class OCavg WMC
Main 7.00 70
Component 1.00 3
BasicGate 2.33 14
SubCircuitInstance 2.00 12
SubCircuitDef 1.00 1

总行数:约380行

最大复杂度:evalSource方法圈复杂度18(需处理输入信号、门输出、子电路端口)

心得

组合模式的运用使代码结构清晰,子电路可以无限嵌套,求值过程统一。但在实现过程中,由于时间紧张,部分职责划分不够干净,例如Main类仍然承担了过多的解析和构建职责,导致其圈复杂度较高。如果进一步重构,可将解析器独立为Parser类,构建过程独立为CircuitBuilder。

三、踩坑心得

正则表达式与引脚分类

三次作业都要靠正则提取元件信息。早期我编写的正则([AONXY])(?😦(\d+)))?(\d+)-(\d+)无法匹配第二次新增的S、M、Z、F类型,导致解析失败。解决方案是将字符集扩展为[AONXYSZFM],并动态适应不同元件的引脚数捕获组。另一个坑在于输入信号名称可能包含数字(如IN1),原正则[A-Za-z]\w不够严谨,后来改为[A-Za-z_]\w才完全匹配样例。测试时一定要覆盖边界命名。

异常检测优先级与短路逻辑

第三次作业要求“如果一条输入出现了多种异常,按以上异常先后顺序为优先级,最后输出优先级最高的异常输出”。我最初实现时,依次检查所有条件,一旦发现错误就存储,最后统一输出。结果忽略了优先级的严格顺序,导致在同时存在多源和无输入时,可能输出较低优先级的错误。修正方案是严格按题目给出的顺序执行if-else链,一旦匹配立即返回,确保优先级。测试用例样例8([A(2)1-0 O(2)1-0]无输入同时多源)证实了正确性。

子电路命名空间与实例隔离

子电路克隆时,我直接引用了def.templateGates中的BasicGate对象,然后在多个SubCircuitInstance间共享同一对象,导致一个实例计算会污染另一个实例的inputSources。例如定义两个子电路C1和C2均使用N1,若不做深拷贝,修改C1的N1输入会影响C2。踩坑后,我在SubCircuitInstance构造时为每个模板门创建全新的BasicGate对象,并添加实例前缀(prefix)作为新全名,保证了隔离性。对应测试样例2(两个相同子电路)通过。

输出引脚映射与连接误导

子电路输出端口在定义中是作为连接信息的目标(如[X1-0 C]),但在主电路引用时C1-C是作为源。这种角色转换容易让人混淆。我最初在构建outputMapping时,错误地将端口名映射到端口名本身,导致求值死循环。后来通过追溯templateConn中该端口作为目标时的源,正确存储了内部源路径。该问题通过单步调试并绘制连接关系图得以发现。

递归求值与环路检测

递归求值存在潜在的栈溢出风险。虽然在作业环境中未出现环路,但我主动加入了computing集合,防止意外环路(如门输出反馈到自身输入)导致无限递归。在第三次作业的evalSource中,我加入了evaluating与evalCache,既避免重复计算,又防止环路,这一机制在所有测试中表现稳定。

四、改进建议

应用设计模式进一步解耦

虽然第三次作业应用了组合模式,但解析和构建部分依旧高度耦合在Main类中。建议将解析、构建、计算三大职责分离:

Parser:负责从文本提取子电路定义、顶层输入、连接信息,并进行异常检测。

CircuitBuilder:根据解析结果创建元件实例并建立连接。

Simulator:仅负责执行compute()并收集输出结果。

这样的分离符合单一职责原则,会使单元测试更加容易,也便于未来支持图形化界面或文件输入。

引入策略模式处理元件输出格式化

第二次作业中不同类型元件的输出格式差异很大(基本门引脚值、译码器编号、分配器高阻序列)。当前是将格式化逻辑写在main的输出循环中,通过instanceof判断。更好的做法是让每个元件重写formatOutput()方法,返回字符串,这样增加新元件时无需修改输出逻辑。例如:

java
abstract String formatOutput();
在BasicGate中返回fullName + "-0:" + output,在Decoder中返回fullName + ":" + activeLowPin,符合开闭原则。

异常检测模块化

目前的异常检测是顺序式if-else链,若未来增加更多异常类型会变得臃肿。可以考虑实现责任链模式,将每种异常检测封装为独立的ConnectionChecker对象,按顺序链接。这样不仅提高扩展性,还可以方便地动态调整优先级。

性能优化

当前求值过程中,findComponent采用遍历所有元件和子电路内部递归查找,时间复杂度较高。对于大型电路,可维护一个全局名称到Component的HashMap,在注册元件时直接添加(含子电路内部的元件),将查找降为O(1)。本次作业规模下虽无性能问题,但养成性能意识有备无患。

五、总结

通过本阶段三次数字电路模拟程序的迭代开发,我深刻体会到了面向对象设计在软件演化中的价值。

  • 技术层面:掌握了正则表达式在复杂文本解析中的高级应用;熟练运用了组合模式解决树形结构问题;理解了如何通过抽象类和接口为系统构建可扩展的骨架。此外,异常处理的优先级设计、原型克隆解决实例隔离等具体技巧都是书本上学不到的宝贵经验。

  • 过程层面:理解了敏捷开发中“简单设计—重构—扩展”的螺旋模型。第一次作业快速实现功能,第二次在原有基础上抽象出元件类体系,第三次引入设计模式进一步解耦,每次迭代都带来架构上的反思与优化。

  • 测试与调试:在实现过程中,我学会了用边界值测试、等价类划分等方法验证解析器和异常处理逻辑,体会到了单元测试的重要性。虽然本次没有使用JUnit框架,但手动构造的大量测试用例(如样例1-8)保障了代码可靠性。

  • 不足与改进方向:我在异常检测模块化、代码规范注释以及性能前瞻等方面仍有提升空间。未来将进一步学习设计模式、重构手法,并尝试使用UML工具进行更规范的建模。

posted @ 2026-06-24 15:51  ice咩  阅读(3)  评论(0)    收藏  举报