面向对象程序设计课程第4-6次作业的总结与心得
前言
面向对象程序设计第二阶段的学习已经结束,在这一阶段的学习中,我学习到了更多有用的程序设计思想和模式:
1.新的面向对象程序设计原则:如开闭原则(OCP),李氏替换原则等。
2.了解了面向对象程序设计中的各大模式(如观察者模式,组合模式等);
3.面向对象程序设计(Java语言)的代码实现基础;
4.面向对象程序设计中的各种设计方法,如继承,多态的使用,接口的定义和使用;
5.迭代性程序的编写和迭代。
这三次作业的考察内容也与上述内容有关,其中体现最多的就继承与多态的使用。在难度上,这三次作业相较上次三次的作业有显著提升,其难度提升主要体现在代码规模,设计,以及继承和多态的使用上。
SourceMonitor复杂度分析
对于4-6次作业的复杂度分析,我依旧采用SourceMonitor软件进行。由于以下内容需要用到SourceMonitor进行复杂度分析,在此处给出各项关键指标的解释:
1.Max Complexity(最大圈复杂度):该文件中所有方法里,圈复杂度最高的那个值;
圈复杂度是衡量代码逻辑分支多少的核心指标:
1-10:健康
11-20:中等风险
>20:高风险,必须重构
2.Avg Complexity(平均圈复杂度):该文件中所有方法的圈复杂度平均值反映了整个文件的整体复杂度水平
3.Max Depth(最大嵌套深度):代码中控制流(if/for/while等)的最大嵌套层数层数越高,代码越难读懂、越容易出错
4.Avg Depth(平均嵌套深度):该文件中所有方法的控制流嵌套深度平均值反映了整体代码的嵌套复杂度,数值越低越好
下面我将从程序结构,测试bug,方法实现等方面对我的三次作业进行分析
第四次作业:数字电路模拟程序-1
由于作业要求篇幅较长,我将其放在下方小字部分。
数字电路是一种处理离散信号的电子电路。与处理连续变化信号(如声音、温度)的模拟电路不同,数字电路只识别和运算两种基本状态:高电平(通常表示为“1”) 和 低电平(通常表示为“0”)。这正好与二进制数制系统相对应,使得数字电路成为所有计算机和数字系统的物理实现基础。
请编程实现数字电路模拟程序,
1、电路元件
电路中包含与门、或门、非门、异或门、同或门五种元件。元件特征如下:
与门:
![]()
包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是高电平,输出引脚才是高电平,只要有一个输入引脚为低电平,输出引脚输出低电平。
或门:
包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是低电平,输出引脚才是低电平,只要有一个输入引脚为高电平,输出引脚输出高电平。
非门:
![]()
包含一个输入引脚和一个输出引脚。输出引脚的电平与输入引脚的电平相反,如输入为低电平,输出则为高电平。
异或门:
包含两个输入引脚和一个输出引脚。当两个输入引脚电平不一致时输出引脚输出高电平,否则输出低电平。
同或门:
包含两个输入引脚和一个输出引脚。当两个输入引脚电平一致时输出引脚输出高电平,否则输出低电平。
2、程序输入
1)元件信息:
用A、O、N、X、Y 分别用作与门、或门、非门、异或门、同或门五种元件的元件标识符。
电路中的每个与门、或门用“标识符(输入引脚数)+编号”作为其元件名。
例如:A(8)1表示一个8输入引脚的与门,O(4)2代表一个4输入引脚的或门。
电路中的每个非门、异或门、同或门用“标识符+编号”作为其元件名。
例如:X8表示一个异或门,Y4代表一个同或门,N1代表一个非门。
约束条件:
不同元件的编号可以相同,如X4、Y4。
同一电路中同种元件的编号不可重复,可以不连续
2)引脚信息:
引脚信息由“元件名-引脚号”构成,。
例如:A(8)1-2代表与门A(8)1的2号引脚。
3)电路的输入信息:
电路的输入格式:
INPUT:英文空格+输入1+”-”+输入信号1+英文空格+输入2+....+输入n+”-”+输入信号n
例如:
“INPUT: A-0 B-1 C-0”代表整个电路包括3个输入:A、B、C 分别输入0,1,0信号。
4)连接信息
引脚的连接信息格式:
[+输出引脚+英文空格+输入引脚1+。。。。+英文空格+输入引脚+]
例如:
[A A(8)1-1 A(8)1-3 X5-2]
代表信号从引脚A发送给与门A(8)1的1、3两个引脚,以及异或门X5的2号引脚。
[Y8-0 N1-1 O(4)2-3 Y2-1]
代表信号从引脚Y8-0发送给非门N1的1号引脚、或门O(4)2的3号引脚、同或门Y2的1号引脚。
约束条件:
一个输出引脚可以连接多个输入引脚,即将输出引脚的信号传给每一个输入引脚。但一个输入引脚不能连接多个输出引脚。
输出引脚不能短接在一起。
5)输入结束信息
所有输入以end为结束标志,end之后出现的内容忽略不计
3、程序输出
按照与门、或门、非门、异或门、同或门的顺序依次输出所有元件的输出引脚电平。同类元件按编号从小到大的顺序排序。
如果某个元件的引脚没有接有效输入,元件输出无法计算,程序输出结果忽略该元件
4、测试输入默认满足以下条件:
1)每个元件的输入引脚连续编号。假设元件有n个输入引脚,则其编号取值范围为[1,n],且引脚号不重复。
2)本题涉及的五种元件都只有一个输出引脚,输出引脚号默认为0。
类图分析
分析上方小字部分所给题目要求后,我对于完成程序所需要的类进行了设计,类图如下:

对于本次作业,我先设计了抽象类Component作为所有电子元件的父类,接着分别设计了ComponentA~Y继承自Component,并且重写父类中的calculateLogic方法,使其能够实现电路的运算。
接下来以ComponentA,也就是与门为例,分析内部结构:
ComonentA类的代码
class ComponentA extends Component{
public ComponentA(String name,int id,int inNumber){
super(name,id,inNumber);
}
@Override
public int calculateLogic() {
// 与门:所有输入为1才输出1
for (Pin pin : inPins) {
if (pin == null || pin.getStatus().equals("0")) {
return 0;
}
}
return 1;
}
}
每个元件的代码和与门的代码相似,先定义一个构造方法,再对父类中的抽象方法calculateLogic进行重写。对于父类的构造方法(componentA类构造方法中使用的super方法)如下:
Component构造方法
public Component(String name,int id,int inNumber){
this.name = name;
this.id = id;
this.inNumber = inNumber;
//初始化
for (int i = 0; i < inNumber; i++) {
inPins.add(null);
}
}
虽然Component类为抽象类,但我们需要在子类中用到他的构造方法,这样才能给父类中的private属性进行赋值,否则在子类中无法直接访问父类中的private属性。
接下来,程序会使用CircuitInput类中的方法接收并分析输入内容,并且创建对应的List将所接收的信息进行储存。这里使用的分析方法是:先读取所有内容存进一个List中,再遍历整个List进行分析。分析过程中使用了startsWtih的方法,通过判断句子的开头,就可以判断输入的句子所表示的含义,如:假设句子以INPUT开头,那么程序将会把这个句子归类到输入的句子中,如果以"["开头,则这个句子属于引脚的连接信息。接下来,CircuitInput会使用split方法将句子拆分开,最后给每个输入的引脚和元件命名,加入到对应的List中。
最后,CircuitSystem类会对每个List中的对象进行分析(即模拟电路运行),模拟时采用while循环,每轮迭代中所有元件检查各自输入引脚是否就绪,若就绪则调用对应的逻辑函数计算输出并更新其输出引脚状态,随后遍历所有连接将源引脚信号复制到目标引脚并通知对应元件更新输入,一旦有引脚状态变化则继续下一轮迭代,如此反复直至整个电路稳定无变化,最终按类型和编号排序输出所有有效元件的输出引脚状态。
复杂度,代码规模分析
第四次作业复杂度,代码规模分析如下:

通过对于图中数据的分析,可以得出:CircuitSystem.java是项目规模最大的文件,拥有 11 个方法、7 层最大嵌套深度、29% 的分支占比与 48 次外部调用,整体逻辑臃肿且耦合度最高;CircuitInput.java、ComponentComparator.java多项复杂度指标显著偏高,Main.java作为PTA平台测试要求类,暂时不做分析;ComponentA、ComponentO 等元件子类存在中等程度的逻辑复杂度,Component、Connection、Pin 等实体类各项度量指标表现健康;项目整体注释覆盖率偏低,多个核心业务文件无注释,高复杂度文件集中在电路系统、输入解析、元件比较与程序入口模块。
针对以上问题,后续迭代可以简化输入解析与比较器分支逻辑,再对巨型 CircuitSystem 类按业务模块解耦拆分,最后统一补充注释、精简子类冗余判断以全面提升可维护性。
BUG分析
本次作业BUG主要分为两种:
1.输入BUG。在编写输入方法时,出现了许多输入逻辑上的bug,对于这些bug,我多次修改了输入和判断的逻辑,最后得到现在的版本,使用startsWith对输入内容进行判断。
2.逻辑BUG。在编写子类的计算逻辑方法时,有几个元件的计算逻辑出现了错误,针对这些逻辑问题,我通过对于单一元件进行输入测试,最后成功修复所有bug,得出稳定最终版本。
#三态门:
三态门的作用类似于电路中的开关。包含一个输入引脚、一个输入控制引脚、一个输出引脚。当控制引脚为高电平时,三态门输入输出之间导通,输出电平等于输入电平;当控制引脚为低电平时,三态门输入输出之间呈现高阻态(类似开关断开),输出为无效状态。
#译码器:
译码器的作用是讲输入的编码转换为一路有效信号。一个译码器包含两个或多个输入引脚(如图中的A2\A1\A0)、三个控制引脚(如图中的S3\S2\S1)、4个或多个输出引脚(如图中的Y7~Y0)。根据输入输出的数量有2-4线译码器、3-8线译码器等。
当控制引脚当S1 =1,S2 +S3 =0时,译码器正常工作,输出引脚只有一个输出信号0,其余输出为1;哪个引脚输出0由输入引脚的编码决定,例如:图中的3-8线译码器三个输入引脚信号的编码与输出引脚的编码对应,A2\A1\A0输入000时,Y0输出0,其余输出1;A2\A1\A0输入001时,Y1输出0,其余输出1;依次类推。
控制引脚不满足S1 =1,S2 +S3 =0时,译码器处于无效状态,所有输出为无效值。
#数据选择器:
数据选择器的作用是从多路输入信号中选择一个,并将其信号直接送往唯一的输出端,选择哪一路输入信号由控制端决定。如图所示控制端有两个则输入端有4个,S1\S0是两个控制端,D3~D0是输入端,S1\S0的4种信号组合00、01、10、11分别选择D3~D0其中一路输入。如S1S0=00,则Y=D0;S1S0=01,则Y=D1;S1S0=10,则Y=D2;S1S0=11,则Y=D3
根据输入引脚数量的不同有二选一数据选择器(1个控制端)、四选一数据选择器(2个控制端)、八选一数据选择器(3个控制端)等
#数据分配器:
数据分配器的作用与数据选择器正好相反,是将唯一的一路输入信号输出到多路输出引脚的其中之一,选择哪一路输出引脚输出由控制端决定。如图所示控制端有两个AB,输出端有4个W0\W1\W2\W3,D是输入端,AB的4种信号组合00、01、10、11分别选择W3~W0其中一路输出,其他三路输出为无效状态。如AB=00,则W0=D;AB=01,则W1=D;AB=10,则W2=D;AB=11,则W3=D。
根据输出引脚数量的不同有二路数据分配器(1个控制端)、四路数据分配器(2个控制端)、八路数据分配器(3个控制端)等
2、程序输入
1)#元件信息:
用A、O、N、X、Y、S 、M、Z、F分别用作
与门、或门、非门、异或门、同或门、
三态门、译码器、数据选择器、数据分配器九种元件的元件标识符。
电路中的每个与门、或门用“标识符(输入引脚数)+编号”作为其元件名。
例如:A(8)1表示一个8输入引脚的与门,O(4)2代表一个4输入引脚的或门。
电路中的每个非门、异或门、同或门用“标识符+编号”作为其元件名。
例如:X8表示一个异或门,Y4代表一个同或门,N1代表一个非门。
#电路中的数据选择器、数据分配器用“标识符(控制引脚数)+编号”作为其元件名。
例如:Z(2)2代表一个四选一数据选择器,F(3)2代表一个8路数据分配器。
译码器用“标识符(输入引脚数)+编号”作为其元件名。
例如:M(3)1表示一个3-8线译码器。
2)#引脚信息:引脚信息由“元件名-引脚号”构成。
例如:A(8)1-2代表与门A(8)1的2号引脚。
3、#程序输出
按照与门、或门、非门、异或门、同或门、三态门、译码器、数据选择器、数据分配器的顺序依次输出所有元件的输出引脚电平。
同类元件按编号从小到大的顺序排序。
以上为本次作业相较于第四次作业所需的迭代内容。
类图分析
第五次作业类图设计如下:

相较前几次作业,本次作业的类图复杂度有进一步的提升,其中大部分新增的类其实是新增的Component子类。新增的元器件有三态门,译码器,数据选择器,数据分配器。首先要给这些元器件编写对应的计算逻辑,以三态门为例,它需要两个输入引脚和一个开关作为输入,所以需要在原有元件基础上新增一个开关作为属性,控制其开关。其次,由于输出方式有所改变,针对这些新增的元件也需要重新编写输出逻辑。
复杂度,代码规模分析
第五次作业复杂度,代码规模分析如下:

通过复杂度分析可以看出,CircuitSystem 作为核心调度类存在明显缺陷,大量分支与多层嵌套导致圈复杂度、嵌套深度超标,ComponentM、ComponentComparator 方法粒度偏大。项目整体注释缺失严重,仅少数类留有少量说明。后续可以通过拆分超长方法、扁平化嵌套逻辑,优化多分支判断,并为核心类与复杂逻辑补充注释等方面进行优化,提升代码可读性与可维护性。
BUG分析
1.三态门输出引脚名称错误
基类创建的是 name-0,但题目要求三态门输出引脚为 name-2,导致后续连接找不到信号。
2.译码器控制引脚数量错误
题目规定译码器始终有 3 个控制引脚,但代码用输入引脚数初始化,导致 2-4 线译码器只有 2 个控制引脚。
3.数据分配器输出引脚编号偏移
输入引脚占用 controlNumber 号,输出应从此编号 +1 开始,但初始代码从 controlNumber 开始。
4.数据分配器输出无法传播
calculateOutput() 未调用 setOutput(0) 标记完成,导致仿真主循环认为它无效,输出引脚无法注册。
第六次作业 数字电路模拟程序-4
本次作业在第四次作业的基础上进行迭代,没有加入第五次作业迭代所产生的内容,迭代内容如下:
5)#子电路信息(本题在数字电路模拟程序-1基础上新增的内容)
可以将一部分电路设定为一个子电路,子电路可以在主电路中被引用。子电路的输入按顺序包括以下几部分:
子电路起始信息:C+子电路编号+:
子电路输入信息:INPUT:输入1+英文空格+输入2+英文空格+....+英文空格+输入n,
子电路输出信息:OUT:输出1+英文空格+输出2+英文空格+....+英文空格+输出n,
子电路连接信息:(格式与主电路输入信息相同),子电路的元件编号与主电路中的编号可能相同。
子电路结束标志:endc
输入、输出信息的编码可以作为子电路的引脚号在主电路中引用。
子电路元件输出时带上子电路的编号,格式为:子电路编号+“-”+元件编号+“-”+引脚号+“:”+引脚输出
例如:定义一个编号2的子电路,包含A、B两个输入和一个输出O:
C2:
INPUT:A B
OUT:C
[A X1-1]
[B X1-2]
[X1-0 Y1-1]
[A Y1-2]
[Y1-0 C]
endc
主电路中的运用:
INPUT: A-1 B-1
[A C2-A]
[B C2-B]
[C2-C N1-1]
[N1-0 OUT]
end
输出信息:
C2-X1-0:0
C2-Y1-0:0
N1-0 :1
所有子电路信息都在主电路信息之前定义完成。
6)#异常输入信息(本题在数字电路模拟程序-1基础上新增的内容)
当出现各类异常输入情况时,电路应输出相应提示,异常情况包括:
1.一个连接信息中包含两个或多个输入,输出:ERROR:“连接信息”+“英文空格”+include more than one input
例如:
INPUT: A-1 B-1
[A B A2-1]
应输出:
ERROR: [A B A2-1] include more than one input
2.一个连接信息中没有输入信息,输出:ERROR:“连接信息”+“英文空格”+include none input
例如:
INPUT: A-1 B-1
[C1-1 C2-1]
应输出:
ERROR: [C1-1 C2-1] include none input
注意:C1-1 C2-1引脚是元件的输入引脚,但作为连接信息,元件的输入引脚属于连接系统的输出,所以是none input3.一个连接信息中没有输出,输出:ERROR:“连接信息”+“英文空格”+include none output
例如:
INPUT: A-1 B-1
[A]
应输出:ERROR: [A] include none output
4.一个连接信息中输入输出写反,输出:ERROR:“连接信息”+“英文空格”+input and output sequence error
例如:
INPUT: A-1 B-1
[A2-1 A]
应输出:ERROR: [A2-1 A] input and output sequence error
5.一个输入引脚接受中来自多个不同输出的信号,输出:ERROR:“输入引脚”+“英文空格”+input signal conflict
例如:
INPUT: A-1 B-1
[A A2-1]
[B A2-1]
应输出:ERROR: A2-1 input signal conflict
注:如果一条输入出现了多种异常,按以上异常先后顺序为优先级,顺序靠前者优先级越高,最后输出优先级最高的异常输出。当电路输入信息中多条输入都包含异常,只处理排在最前面的异常信息。
以上内容为本次迭代新增要求。
类图分析
第六次作业类图设计如下

本次类图基于第四次作业迭代,新增子电路相关 SubCircuitDef、ConnectionLineInfo 类,拓展了电路分层解析能力。抽象父类 Component 衍生多类元器件子类,统一重写逻辑计算方法;新增 ComponentComparator 实现元件排序。CircuitInput 负责文本解析、子电路实例化,CircuitSystem 统筹所有元件、引脚与连线,Connection、Pin 作为基础实体承载连接信息,整体结构分层清晰,拓展性更强。
复杂度,代码规模分析
第六次作业复杂度,代码规模分析如下:

从本次复杂度检测数据可见,CircuitInput 新增子电路解析功能后体量大幅膨胀,316 行代码、最大圈复杂度 31、最大嵌套深度 7,分支占比 30.4%,是项目维护风险最高的文件;CircuitSystem 次之,分支与嵌套逻辑较多。所有文件注释覆盖率均为 0,无任何说明文本。后续我会拆分 CircuitInput 内超长解析方法,抽取多层嵌套逻辑,优化多分支判断,并为解析、调度相关核心代码补充注释,降低理解与修改成本。
BUG分析
1.在计算逻辑时,未正确处理输入引脚为null的情况
解决方法:在ComponentA和ComponentO的calculateLogic()方法中,遍历inPins时应该先检查pin是否为null,避免空指针异常。特别是在组件刚创建时,输入引脚可能尚未连接。
2.在子电路实例化时,组件名称的完整路径构建错误
解决方法:在instantiateSubCircuit()方法中,为子电路内的组件添加前缀"C{cid}-"时,需要确保完整名称格式正确。同时要检查子电路的输入输出引脚在处理时是否正确地与外部引脚对应。
3.在解析输入时,主电路和子电路的输入输出范围判断逻辑混乱解决方法:在isSourceInContext()和isSinkInContext()方法中,需要明确区分主电路和子电路的上下文环境。对于主电路,输入源包括主输入和子电路输出;对于子电路,输入源包括子电路输入和内部组件的输出。
4.在连接冲突检测中,未正确处理相同源引脚连接到多个目标引脚的情况
解决方法:在checkInputConflict()方法中,检测信号冲突时应该只检查同一目标引脚是否有多个不同的源,而不应该阻止一个源连接到多个目标。需要修正冲突检测的逻辑判断条件。
5.在组件输出引脚创建时,outPin对象被重复创建但未与系统同步
解决方法:在Component的calculateOutput()方法中,如果outPin已经存在但状态更新了,需要确保更新后的outPin被同步到CircuitSystem的allPins列表中,否则后续的连接无法获取到最新的引脚状态。
关于面向对象程序设计第二阶段学习的思考与总结
在这一阶段的学习中,我学到了更多和面向对象程序设计有关的设计模式和思想,这些思想都可以用于平时的程序设计中,多在平时的设计中使用这些方法,也能巩固我这些时间的学习。许多有用的设计方法如继承,多态都可以用于平时的程序设计,可以使代码更加简洁,提升代码的复用性。同时,抽象类的应用让我能够定义统一的接口规范,而子类则根据具体需求实现各自的逻辑,这种"开闭原则"的设计思想极大地增强了代码的可扩展性。此外,通过封装将数据和行为捆绑在一起,有效降低了模块间的耦合度,使得程序更易于维护和调试。在解决实际问题的过程中,我还深刻体会到合理的设计模式(如工厂模式、观察者模式)能够帮助我更好地组织代码结构,应对复杂业务逻辑的变化。这些面向对象的思维方式不仅提升了我的编程效率,也让我在后续的开发中能够更从容地设计出高质量、可维护的软件系统。




浙公网安备 33010602011771号