数字电路模拟程序三次作业总结

一、前言

跟上次的航空器配载与货运管理系统比起来,这回迭代的难度明显上了一个台阶。作业重心完全转向数字电路,还牵涉到不少物理知识,而我这块先前几乎是零基础,所以把题目真正吃透就变得特别关键。好在这次给出了很多输入输出样例,有助于我们理解题意,而且测试点的数量也一下子多了不少。

三次作业本身是一个逐层深入的迭代过程:先从五种基本门电路做起,接着引入三态门、译码器、数据选择器和数据分配器,最后完成子电路封装并覆盖五类异常处理。

二、设计与分析

借助PowerDesigner和SourceMontor,我对三次作业源码进行逐一分析与心得总结。

2.1 作业 1 源码设计与分析

2.1.1 类间关系说明

image

在继承关系上,AndGate、OrGate、NotGate、XorGate、XnorGate 这五种基础逻辑门都继承自父类 Gate。父类封装了元件名称、编号、输入引脚列表等公共部分,子类只需要重写 compute() 方法,各自实现专属的数字逻辑运算就可以了,这样公共代码就得到了复用。

至于依赖关系,主类 Main 依赖于全部基础门类和 Utils 工具类。它借助 Utils 提供的静态方法,完成创建、查询、分类、打印等一系列操作。Main 会遍历 Gate 集合,对所有的逻辑门进行统一处理,调用的时不需要关心拿到的到底是哪一种门。

2.1.2 SourceMonitor 代码质量报表分析

image

共355行、255条执行语句,分支覆盖率30.6%,调用148次;每类平均3.13方法,单方法均7.88语句;最大圈复杂度28、平均4,最大嵌套6层、均2.52。

image

五类基础门子类仅包含构造方法和compute方法,圈复杂度控制在3以内,逻辑清晰;Utils工具类承担了元件解析、信号查询、分类打印等职责,九个静态方法中分支占比44.3%,平均复杂度5.78,多个方法的循环与字符串处理推高了复杂度,五类门的排序打印也存在重复模板代码;Main类可以说是整个项目的复杂度瓶颈,圈复杂度达28,嵌套深度6,多层循环和条件分支混杂,整体有待优化。

2.1.3 本次作业心得

第一次接触数字电路仿真这类题目时,确实被题目描述吓了一跳,光是理解整个电路的工作机制就花了不少时间。这次作业还要求引入继承、抽象类这些面向对象的知识点来组织代码,初看时对怎么把电路元件映射到类结构上也没什么头绪。

实际做下来,这次作业让我真正把抽象类和继承这套设计思路落地了一遍。通过Gate抽象父类把五类逻辑门共有的属性和方法提取出来,子类只需要关注各自的计算逻辑就行。这样一来,代码的复用性明显提升,不需要在每个门电路里反复写同样的结构代码。整体上看,这种设计方式既让代码更整洁,也降低了后续功能扩展的成本。

2.2 作业2源码设计与分析

2.2.1 类间关系说明

image

从实现关系来看,Gate 抽象类以及 TriState、Decoder、Mux、Demux 这四种复合元件类,都实现了 Component 接口。这个接口里只定义了一个 addInput() 方法,用来统一规范元件的引脚逻辑,这样一来,九种电路元件便有了完全一致的调用标准。

继承关系上,五种基础与 Gate不变。

依赖关系方面,Main 主程序依赖于所有的元件实体和 CircuitUtils 工具类。Main 通过调用工具类的静态方法,来完成元件的创建、信号取值、排序以及打印输出;同时,它依靠 Component 接口来操作元件。

关联关系可以分成两个层面去看,每个元件在内部通过列表或数组来存储控制引脚、输入引脚所对应的信号来源,引脚和信号之间的对应关系由元件自己维护。Main 这边,则利用多组泛型集合与九类元件建立一对多的关联,所有电路元件实例最终都在 Main 里集中管理。

2.2.2 SourceMonitor 代码质量报表分析

image

项目共13个文件、728行代码,有效执行语句566条,分支覆盖率26.1%,方法总调用214 次;包含13个类,平均每类 5.54个方法,单方法平均 5.74行语句。最大圈复杂度66、均值2.82,最大代码嵌套 7层、平均2.58。

image

门子类比较干净,每个文件也就十来行,圈复杂度最高才3。Gate抽象类42行,提供了8个基础存取方法,圈复杂度1.38,就是封装通用属性和接口实现,没什么复杂分支,设计上比较克制;新加的TriState、Decoder、Mux、Demux这几个复合元件类,单文件在44到68行之间,平均圈复杂度维持在1.15到1.38,主要工作是存引脚信号和状态,具体运算逻辑交给主类去统一迭代处理。CircuitUtils工具类就比较重了,164行代码里塞了5个静态方法,分支占比过半,平均圈复杂度10.40。元件创建、信号解析、排序打印这些活全包了,大量字符串解析和类型判断堆在一起,复杂度自然上去了;Main类是目前最突出的问题点,整个类243行,最大圈复杂度直接飙到66,嵌套深度也到了7层。

2.2.3 本次作业开发心得

相比第一次作业,这次新增了四类复合电路元件,数据处理一下子复杂了不少。这些新元件普遍带有控制引脚和输出引脚,运算规则也各不相同,对代码有更高要求。在写的过程中,这次我加入了Component接口,扩展性提升了一些。

不过这次暴露的问题也挺明显。所有元件的迭代求值、状态判断和差异化输出全挤在Main主方法里,导致这个方法体量过大,圈复杂度居高不下,多层嵌套循环让代码读起来很费劲,维护起来也不方便。

2.3 作业 3 源码设计与分析

2.3.1 类间关系说明

image

从泛化层次来看,顶层的 Component 抽象父类提供了一套统一的接口规范。Leaf 继承自 Component,然后 Gate 再继承 Leaf,把元件名、编号、引脚、子电路编号这些公共属性都封装在里面。五种基础门仍然继承 Gate。在另外一条分支上,Composite 直接继承 Component负责存储输入输出、内部连线以及计算状态。

说到实现关系,Gate、Composite 和 Leaf 各自重写了 operation()、add()、remove()、getChild() 这些组合模式的标准方法。这样一来,叶子元件和复合子电路对外暴露的调用方式完全一致,调用者根本不用区分面对的到底是一个基础门还是一个嵌套子电路。

依赖关系上,Main 主类依赖于全部元件类、Composite 以及 Utils 工具类。它利用 Utils 提供的静态方法,先解析出元件的名称、编号和引脚数量,再借助 Component 组合结构把独立门电路和嵌套子电路统一管起来,完成输入解析、连线绑定、分层迭代求值、排序打印这一整套核心流程。

再来看关联关系。各种门在内部都是用列表来保存引脚信号的来源。Composite 则用了多组 List 和 Map,来存储子电路的输入输出名称、引脚映射以及输出结果。

2.3.2 SourceMonitor 代码质量报表分析

image

本项目共 11 个文件、697 行代码,有效执行语句 544 条,分支覆盖率 29.4%,方法调用 221 次;包含 11 个类,平均每类 4.64 个方法,单方法平均 8.39 行代码。最大圈复杂度 21、均值 4.29,最大代码嵌套 9 层、平均 2.69。

image

Gate抽象类有41行,提供了8个基础存取方法和接口方法,圈复杂度只有1.38,封装了所有门的通用属性,没有复杂分支;Component、Leaf、Composite这几个组合模式的基础类,单文件在12到18行之间,只实现了组合模式规定的标准空方法,圈复杂度为1;Utils工具类34行,包含2个静态解析方法,分支占比29.6%,平均圈复杂度7.50,主要负责解析元件名称中的数字和引脚参数,字符串的分支判断拉高了复杂度;Main主类505行,虽然相比之前复杂度有所控制,但最大圈复杂度仍有21,嵌套深度达到了9层

2.3.3 本次作业开发心得

本次作业引入组合设计模式,新增子电路和多种一场情况的检验以及对电路的排序输出,相比前两次难度大幅提升。虽然最终有6个测试点未通过,但同时我也知道了operation()、add()、remove()、getChild()的标准写法,对组合模式有一定的掌握。

三、采坑心得

坑点1:输入问题

问题现象:默认为输入顺序固定为例题数据顺序,实际上INPUT这一行不一定在第一行,门类引脚(A(2)1-0)不一定要写在最后才能赋值,题目的意思是先完成全部电路连线绑定,再统一迭代计算。

解决方案:将输入解析改为两阶段处理。第一阶段按行读取所有输入,将INPUT行、连接信息行、子电路定义行分别存入不同列表;第二阶段统一进行归属。

以下输入也需能运行,不然有一个测试点会一直过不了。
imageimage

坑点2:元件编号处理

问题现象:看样例默认与元件编号只会是个位数,最初只能处理一位字符的编号

解决方案:从后向前遍历字符串,连续读取数字字符,直至遇到非数字字符,完整解析出编号。

坑点3:数据分配器无效状态输出格式错误

问题现象:数据分配器未选中的引脚需输出“-”无效标记,初始代码默认填充0/1,导致输出格式与样例不符。

解决方案:初始化所有输出引脚均为无效状态“-”,然后再赋值,其余引脚保留无效标记,最终按题目输出格式。

错误输出:F(3)1:101----(未选中引脚输出错误值1)

正确输出:F(3)1:-01----(未选中引脚输出"-")

四、改进建议

4.1 代码结构层面:第一次的工具类Utils对不同的门用相同的排序方法,但却写了重复五次,可以将sortAndPrintAnd、sortAndPrintOr等五个方法合并为泛型方法:public void sortAndPrint(List gates)

4.2 代码复用层面:每次代码中Main 职责过重,承担业务过多,单一主方法臃肿,可以拆分 3 个独立类:输入类、运算类、输出类。

4.3 扩展性层面:新增时序元件子电路时,需要同步修改findOrCreate、getVal、Main,扩展性不太好。

五、总结

首先是面向对象设计能力。从第一次作业的简单继承,到第二次引入接口,再到第三次应用组合模式,特别是第三次作业中,Component接口这种“对扩展开放、对修改关闭”的效果让我体会到了良好架构带来的维护便利。但是,我还有许多地方需要进一步研究,比如测试环节。设计模式也需要抓紧时间学习。

posted @ 2026-06-23 21:07  Lien90  阅读(6)  评论(0)    收藏  举报