第二次Blog——Java题目集4~6的总结与分析
题目集4~6总结性Blog
------------------------------------------目录------------------------------------------
-----------------------------------------------------------------------------------------
一、前言
本阶段的三次作业,主要围绕面向对象设计的继承和多态、字符串处理与解析、正则表达式、工厂模式和组合模式的设计模式等知识点进行展开。具体来说,题目集4聚焦于答题判题程序的设计与实现,涉及了如何通过多态和继承进行题目类型扩展,并通过正则表达式解析输入,题目集5和题目集6的电路模拟,主要考察了如何使用工厂模式、组合模式和继承来设计电路设备类,如何实现电路中的电压计算和设备连接,特别是串联与并联电路中电阻的处理。
题量适中,有挑战性又不至于有挫败感,每周一道大作业,一做就是一上午、一下午、一晚上,做出来就很有成就感。
难度方面,题目集4对题目扩展和判题逻辑提出了较高要求,题目集5和题目集6则在设计模式的应用上有较强的挑战,尤其是在电路电压计算和设备存储管理的抽象上。整个过程中,我逐步提升了对继承和多态、设计模式和代码优化的理解,并通过对各个模块的独立实现及协作,深化了对软件工程的实际应用能力。
本文将对这三个题目集进行总结回顾,包括设计与分析、遇到的问题及解决策略,以及针对未来改进的一些建议。
二、设计与分析
题目集4的答题判题程序4
打乱顺序输入题目、试卷、学生、删除题目信息,题目类型细分为选择题、填空题,允许多张试卷存在,可以以任意的先后顺序输入各类不同的信息,输出总分警示、答卷、判分、题目被删除、引用错误、格式错误的信息。
类的设计沿用了答题判题程序系列的大作业三,新增了继承题目类的选择题类和填空题类,再微调一下输入输出,就完成了。在此,真切感受到多态的好处。总之,主要包括题目、题库、试卷、试卷集、学生信息和答卷、答卷集等模块,采用面向对象的设计方式,通过类和对象封装数据和行为,使用集合来存储和管理相关信息,并利用正则表达式解析输入格式以确保正确性。可以扩展不同类型的题目,这里扩展了选择题和填空题,能够计算试卷总分并检查答案的正确性,同时提供判分信息输出。
源码分析
(1)顺序图:
(2)大部分类功能与迭代前类似:
QuestionBank 类维护一个题库,使用 Map 存储题目,提供添加、删除和查询题目的功能。Test 类表示一张试卷,保存试卷号和题号与分数的映射,能够计算总分并检查是否为100分。
TestBank 类则管理多个试卷,提供保存和检查试卷总分的功能。
Answer 类用于表示学生的答卷,包含试卷号、学号和答案,同时维护评分信息。AnswerBank 存储多个答卷,并提供检查所有答卷的功能。
Student 类和 StudentTable 类用于管理学生信息,包括学号和姓名的存储与查询。
输入信息的解析由 InfoBuild 类完成,它使用正则表达式确保输入格式的正确性,并构建题库、试卷和学生信息。整体设计遵循面向对象原则,结构清晰,便于维护和扩展,但在异常处理方面还有改进的空间。
(3)输入处理的变化:用正则表达式来匹配各种格式,与之前相比,在本题需要增加选择题和填空题的正则表达式,与问题的正则表达式类似,只是从N变成了Z和K。
//#N:1 #Q:1+1= #A:2
private final String questionPattern = "^#N:(\\d+) #Q:([^#]+) #A:(.*)";
//#Z:2 #Q:党十八大报告提出要加强()建设。A 政务诚信 B 商务诚信 C社会诚信 D司法公信 #A:A B C D
private final String choicePattern = "^#Z:(\\d+) #Q:([^#]+) #A:(.*)";
//#K:2 #Q:古琴在古代被称为: #A:瑶琴或七弦琴
private final String fillPattern = "^#K:(\\d+) #Q:([^#]+) #A:(.*)";
(4)继承的处理:choiceQuestion和fillQuestion对Question的继承,重写一下checkAnswer的单道题判断的方法以适应不同题型的评分规则。判决结果原来采用boolean类型,由于除了true、false新增了partially correct,所以类型改为了String。全部题目的判断,要遍历题目列表,比较单题的答案,将结果存入判题列表。
(5)总分的计算上也有所改变:根据判定结果决定在总分上加全部的分数,还是增加一半的分数,还是不加分数。
代码结构较为清晰,平均每类包含的方法数和平均每方法语句数都在合理范围内。
分支语句占比不高,代码流程控制不是特别复杂。
方法调用较多,由于模块化设计较好,功能分散到不同的方法中实现。
注释覆盖率为9.1%,略低,我应该增加更多的注释来提高代码的可读性。
最复杂方法的圈复杂度为10,属于较高水平,应对该方法进行优化或分解,降低其复杂度。
平均复杂度为1.86,整体上代码的复杂度处于一个较好的水平。
InputInfo()方法圈复杂度最大,有10,因为新增了选择、填空题型判断的两个分支。
题目集5的家居强电电路模拟程序1
实现电路模拟程序,电路中设备有受控设备和控制设备。控制设备分为开关、分档和连续调速器等,根据输入电压得到输出电压,受控设备是像灯和风扇,根据电压差获得不同的工作参数。
输入带设备编号和引脚的连接信息与#加设备的控制信息,按指定顺序输出工作参数。
在本题,电路设备分为控制设备和受控设备,电路也可看作电路设备,电路中又包含电路设备,可以采用组合模式,受控设备和控制设备继承电路设备,电路类也集成电路设备,同时电路类又聚合电路设备。
由于本题只考虑串联的形式,所以可以先写一个抽象的电路类,用串联电路继承它,留有扩展的余地。电路中除了开关可能出现多个,其他电路设备均只出现一次,并且没有给出电阻等电路参数,所以这部分处理比较粗略,受控设备其中一侧电压一定为0,电压差就是输入电压。
源码分析
(1)顺序图
(2)ElectricalDevice:抽象基类,代表一个电气设备,定义了基本属性如ID、输入电压、输出电压等,并提供了一些update等公共方法。控制设备的update是根据输入计算输出电压,受控设备的update是根据电压差计算工作参数。这也是多态的体现。
(3)ControlDevice 和 ControlledDevice:从ElectricalDevice继承而来,分别用于表示控制设备和受控设备。ControlDevice负责调整其他设备的状态,而ControlledDevice则响应这些调整。
(4)具体设备类:包括Switch(开关)、MultiSpeedController(分档调速器)、ContinuousSpeedController(连续调速器)、IncandescentLight(白炽灯)、FluorescentLight(日光灯)、CeilingFan(吊扇)。每个具体设备类都实现了其特定的行为。
(5)Circuit:抽象基类,表示一个电路,定义了添加设备、连接设备、更新状态等方法。
(6)SeriesCircuit 和 ParallelCircuit:从Circuit继承而来,分别实现串联电路和并联电路的具体逻辑。本题没有用到ParallelCircuit,但是考虑到之后可能的扩展,也设计了。
Lines: 432,文件总共有432行代码。
Statements: 223,共有223条声明语句。
Percent Branch Statements: 17.5%,分支语句占所有声明语句的比例约为17.5%,这表明代码中有一定比例的决策逻辑。
Method Call Statements: 83,方法调用语句共出现了83次。
Percent Lines with Comments: 8.8%,注释行占总行数的比例为8.8%,说明文档化程度较低。
Classes and Interfaces: 9,定义了9个类和接口。
Methods per Class: 5.67,平均每个类包含约5.67个方法。
Average Statements per Method: 4.24,每个方法平均含有4.24条声明语句。
Line Number of Most Complex Method: 211,最复杂的函数开始于第211行。
Name of Most Complex Method: InfoBuild.buildInfo(),最复杂的方法是InfoBuild.buildInfo()。
Maximum Complexity: 5,最大圈复杂度为5。
Line Number of Deepest Block: 141,最深嵌套块开始于第141行。
Maximum Block Depth: 4,嵌套深度最大值为4层。
Average Block Depth: 1.40,平均嵌套深度为1.40层。
Average Complexity: 1.71,平均复杂度为1.71。
具有较少的类和方法,同时分支语句和方法调用语句也保持在一个合理的水平。然而,注释覆盖率较低,这会影响代码的可维护性和理解难度。此外,虽然大多数方法的复杂度适中,但存在个别较复杂的方法,这需要进一步审查和重构以提高代码质量。
题目集6的家居强电电路模拟程序2
在第一次的基础上,增加落地扇电路设备,增加所有电路设备的电阻的参数,增加一个并联电路。输入电路时,连接信息按电路输入。
第二次大作业在第一次大作业的基础上增加了落地扇、并联电路和电阻,落地扇好改,直接新增一个类就好啦,此外还要解决两个问题,一个是输入的信息怎么转化和存储,第二个是加入了电阻后电压怎么计算。
输入的信息要确保正确存储,改变电压可以通过电阻分压法计算。
其中,电阻计算方法如下:
受控和控制设备电阻都知道,串联并联电路也作为设备,电阻可以算,算法是串联是电路所有电阻相加(把断开的开关设成无穷大的电阻),并联是电路电阻倒数和再取倒数,如果有一路电阻是0,那总电阻就是0。
在主函数调用总电路的计算电阻方法,会计算所有设备的电阻求和,其中,受控控制设备电阻直接返回,电路作为设备计算并返回。这样的类似递归的过程。
(1)串联电路计算电压差的分压方法方法:
(2)并联电路计算电压差的分压方法方法:
(3)顺序图:
注释比例:31.6%的注释比例,表明代码有一定的可读性和维护性。
分支语句比例:17.6%的分支语句比例相对较低,说明代码逻辑较为简单。
方法调用比例:较高的方法调用(108次)意味着代码复用较好,但也需要关注是否存在过度依赖的情况。
最复杂方法:InfoBuild.buildInfo()方法具有最高的复杂度,在信息处理的过程中,进行了许多对字符串的判断。我想,如果使用正则表达式应该可以减少这种不必要的分支判断,之后可以持续改进。
最大嵌套深度:5层的嵌套深度较高,可能会增加理解难度,建议减少嵌套以提高可读性。
平均复杂度:2.25的平均复杂度在合理范围内,但仍然需要注意控制单个方法的复杂度。
总体来看,代码质量尚可,但仍有一些地方可以改进,如减少嵌套、优化复杂方法等。
三、采坑心得
题目集4的答题判题程序4
(1)问题:在解析输入时,初期我并未考虑到各种输入格式的细微差异,导致一些合法输入被错误地拒绝。
解决:为了解决这个问题,我花了更多时间仔细检查每个正则表达式,确保它们能涵盖所有可能的输入情况,并通过单元测试验证其正确性。
if(getStandard().equals( answer)) { return "true"; }
解决:判断时,不要把答案后的空格也加入判断,要先用trim()函数去除空格,在新增的两个题型子类中,没有注意这一点。
(3)问题:本题中,要求乱序输入,这意味着删除题目的信息可以出现在题目之前。
解决:对此的处理是,如果不存在这道题,先创建这道题,后续输入这道题的信息,再修改之前创建的这个题目对象。
//为了保证乱序也正常 if(questions.getOrDefault(qNum,null)==null) {//如果不存在这道题 Question q= new Question(qNum,"",""); q.setDelete(true); saveQuestions(qNum,q); }else{ questions.getOrDefault(qNum,null).setDelete(true); }
(4)在评分逻辑的实现中,我遇到了一些边界情况,比如部分正确答案的评分。在初期的实现中,我没有考虑到不同题型的评分规则,导致评分结果与预期不符。
解决:经过反复测试和调整,我最终为每种题型设计了清晰的评分规则,并在代码中进行了详细的注释,以确保后续维护时能够快速理解。
题目集5的家居强电电路模拟程序1
(1)问题:出现空指针异常。
解决:经过调试,发现没有对VCC进行特殊处理,出现了空指针。
(2)问题:混淆了Java和C++中取子字符串的函数用法,导致截取错误。
解决:Java的String类的substring() 方法和C++中std::string类的std::string::substr成员函数都有两个参数,第一个参数是子字符串开始的位置(基于 0 的索引),第二个参数Java中是不包括的结束索引,C++中是要提取的子字符串的长度。如果不提供第二个参数,两者都是截取到末尾。
(3)问题:怎么实现按照顺序输出的问题。
解决:建立设备类型的枚举,每个设备都返回设备类型,在输出前对所有设备排序,之后再做输出。
// 自定义比较器 class DeviceComparator implements Comparator<ElectricalDevice> { @Override public int compare(ElectricalDevice d1, ElectricalDevice d2) { int typeComparison = d1.getType().ordinal() - d2.getType().ordinal(); if (typeComparison != 0) { return typeComparison; } // 如果类型相同,则按编号排序 return d1.getId().compareTo(d2.getId()); } }
题目集6的家居强电电路模拟程序2
(1)问题:startsWith的参数不是正则表达式,就只是普通的字符串。
解决:要注意。
(2)问题:
短路的情况输入,例如
#T1:[IN D2-1] [D2-2 OUT] #T2:[IN D1-1] [D1-2 OUT] #T3:[IN K3-1] [K3-2 OUT] #M1:[T1 T2 T3] #T4:[VCC K1-1] [K1-2 K5-1] [K5-2 M1-IN] [M1-OUT D3-1] [D3-2 GND] #K1 #K5 #K3 End
输出:D3输出不正常。
问题:经过调试,发现一些变量的值是NaN。接着调试,发现是,根据逻辑计算电阻时,该为0处脑子糊涂代码中写成了无穷大。
(3)问题:很多答案错误。
解决:题目中要求不对工作电压进行判断,所以不应该限制落地扇的最高电压。
(4)问题:和调速器相关的测试点。
解决:调速器与VCC直连,有可能是连在与VCC直连的第一个串联电路的第一个位置。要特别处理一下。还有调速器不止有连续,还有分档,写着写着忘记了。
四、改进建议
题目集4的答题判题程序4
首先,增强输入验证机制是一个重要的改进方向。尽管当前使用正则表达式进行输入解析,但仍可能存在边界情况未被覆盖的风险。在输入处理过程中加入更为详尽的验证逻辑,确保用户输入的每个部分都符合预期格式,并提供清晰的错误提示,以便用户能够快速纠正错误。
其次,考虑到系统的扩展性,可以将题目类型进行抽象化处理。当前我的设计中,选择题和填空题是通过继承实现的,但随着题型的增加,这种设计可能导致代码的复杂性增加。可以用工厂模式来处理不同类型的题目,使得系统能够更灵活地支持未来可能新增的题型,同时降低代码耦合度。
此外,如果不是在PTA上写的,而是一个实用的系统,还可以增加数据持久化功能,并增加图形用户界面。目前,所有数据都存储在内存中。其实可以将题库、试卷、学生信息和答卷等数据存储到数据库中,也可以使用文件存储,确保数据的持久性和可恢复性。在用户体验方面,可以考虑增加图形用户界面。用户可以通过点击和选择的方式进行操作,提升整体的交互体验。
题目集5的家居强电电路模拟程序1
(1)采用工厂模式处理输入,根据设备id创建新设备,如下:
class DeviceFactory { public static ElectricalDevice createDevice(String id) { if (id.startsWith("K")) { return new Switch(id); } else if (id.startsWith("F")) { return new MultiSpeedController(id); } else if (id.startsWith("L")) { return new ContinuousSpeedController(id); } else if (id.startsWith("B")) { return new IncandescentLight(id); } else if (id.startsWith("R")) { return new FluorescentLight(id); } else if (id.startsWith("D")) { return new CeilingFan(id); } else { throw new IllegalArgumentException("Unknown device ID: " + id); } } }
(2)目前,设备的比较器是在 printStatus 方法中创建的。应将比较器作为静态常量定义,以避免每次调用 printStatus 时都重新创建比较器。
private static final Comparator<ElectricalDevice> DEVICE_COMPARATOR = new DeviceComparator(); public void printStatus() { List<ElectricalDevice> deviceList = new ArrayList<>(deviceALL.values()); // 使用自定义比较器对设备进行排序 Collections.sort(deviceList, DEVICE_COMPARATOR); //省略了输出的语句 }
(3)每个设备的 update 方法都直接计算输出电压或工作参数。将一些公共的计算逻辑提取到基类中,以减少重复代码。
public abstract class ElectricalDevice { // 省略了 public void update() { differenceVoltage = inputVoltage - outputVoltage; doUpdate(); } protected abstract void doUpdate(); }
题目集6的家居强电电路模拟程序2
(1)电路的计算逻辑比较复杂,可以通过策略模式将计算部分抽象出来,使用不同的计算策略来处理不同类型的电路。
// 电路计算策略接口 interface CircuitCalculationStrategy { double computeVoltage(double inputVoltage, double[] resistances); } // 电路类 class Circuit { private CircuitCalculationStrategy calculationStrategy; public Circuit(CircuitCalculationStrategy calculationStrategy) { this.calculationStrategy = calculationStrategy; } public double computeVoltage(double inputVoltage, double[] resistances) { return calculationStrategy.computeVoltage(inputVoltage, resistances); } public void setCalculationStrategy(CircuitCalculationStrategy calculationStrategy) { this.calculationStrategy = calculationStrategy; } }
(2)使用 TreeSet(有序集合)来避免每次都进行排序。可以使用 TreeSet 来保证设备按顺序存储。
private Set<ElectricalDevice> devices = new TreeSet<>(Comparator.comparing(ElectricalDevice::getId));
(3)输入处理的圈复杂度:可以用正则表达式处理以减少分支语句,减少圈复杂度。
(4)代码结构和设计模式的优化:
设备接口和类的设计:电器设备和其子类(如控制设备和受控设备)设计上能进一步抽象。可以为 ElectricalDevice 添加一个 getVoltage() 方法来统一处理不同设备的电压获取逻辑。这样可以减少不同设备类中重复的代码。
电路类的职责划分:电路类目前负责太多的逻辑:既要管理设备(addDevice、addConnection 等),又要处理电路计算(如 computeAllVoltage)。可以把电路中的计算和设备管理分离开来。设计一个“电路计算器”类,负责根据设备的状态和电路的连接情况来计算电压、电流等参数。
五、总结
通过本阶段的题目,我不仅深化了对面向对象编程的理解,还在作业实践中学到了许多宝贵经验。
首先,题目集4让我在实际操作中理解了多态、继承和正则表达式的应用,并解决了输入解析、题目删除和答题判分等问题。这一过程中,我学到了如何有效处理复杂的输入格式,以及如何优化判题规则来确保结果的准确性。
题目集5和题目集6则让我使用工厂模式、组合模式、策略模式等设计模式,优化了电路设备的管理与计算逻辑,尤其是在处理电路电阻计算和设备连接管理的方面,得到了很好的锻炼。
然而,在实践过程中,我也意识到一些需要进一步提升的地方。首先,正则表达式的使用上,我还需要进一步掌握其高效应用,尤其是在处理复杂输入时的边界情况。其次,对于设计模式的使用,尽管我已经有了一些初步的理解和实践,但在更复杂的系统设计中,如何选择最合适的设计模式,仍然需要更多的学习与探索。
老师可以在课程中增加更多关于设计模式和软件架构的深入讲解,并通过更多的实际案例来帮助我们更好地理解如何在不同场景下应用这些设计模式。此外,作业中如果能够提供更多的测试用例和边界情况,以便更好地验证代码的鲁棒性和性能,会对我们提高解决实际问题的能力大有帮助。
在课外的自学过程中,如何高效地进行问题分解、程序设计、圈复杂度降低等优化是我未来需要进一步加强的方向。在未来的学习中,我将继续加强对设计模式和程序优化的理解,并通过实践不断提高自己程序设计的能力。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~