第三次博客作业
一、 前言
在过去的几周时间里,通过7~8两组题目集的深入练习,在巩固前两次题目集知识的基础上,我又学习并掌握了更多的知识,比如继承与多态的运用、比较器Comparator的运用等等。一次次练习不仅加强了我的基础编程技巧,还逐步引入了更为复杂的数据结构和算法应用,使我的编程能力和问题解决技巧在作业迭代中得到了显著提升。
大作业7:主要涉及到类和对象的设计、继承与多态以及数组、集合、正则表达式的运用等,使用了try-catch块来处理异常。需编写复杂的逻辑来处理电路的连接、开关状态的改变、电压的分配等。还使用了自定义的Comparator来对Electric对象进行排序,同时还需要熟练掌握字符串的操作,如分割、拼接、比较等。系统包含多种设备和复杂的电路逻辑,难度较大。
大作业8:在大作业7的基础上迭代,增加了最大电流的设置,还涉及抽象类的使用,通过节点电压法计算电路中的电压分布。使用到了工厂模式:createDevice
方法,根据设备类型创建相应的设备实例,还使用到了命令模式:processCommand
方法,根据输入的命令字符串来执行不同的操作,Collections.sort
方法和自定义的比较器用来对设备列表进行排序。本次大作业需要模拟多种设备和它们之间的交互逻辑,包括开关状态、电位差计算、设备响应等,难度较大。
二、 设计与分析
大作业7
作为大作业6的迭代加强版,不仅扩展了系统的功能,还引入了新的组件:互斥开关和受控窗帘,这些新增特性无疑增加了作业的复杂度。尽管实现过程中遇到了很多挑战,但整体思路有着清晰的认识,后续一定完善。
本题设计类图与时序图如下:
本题的设计思路是实现一个模拟电路系统,它能够让用户定义电路组件之间的连接,并通过控制信号来调节电路的行为,最终展示每个组件的状态。系统由多个主要的类组成,包括CircuitDevice、Switch、MutualSwitch、Fendang、Lianxu、Baichi、Riguang、Diaoshan和ShuKongChuanglian,以及一个Main类来管理整个电路的构建和操作流程。
1. CircuitDevice类:表示电路中的一个基本设备,包含设备ID和电压属性。它是所有电路设备的基类,提供了更新电压和显示设备状态的抽象方法。
2. Switch类:表示一个开关设备,继承自CircuitDevice。它能够在打开和关闭之间切换,根据开关状态更新电压,开关状态为0时,无论输入电位是多少,输出引脚电位为0。当开关状态为1时,输出引脚电位等于输入电位。
3. MutualSwitch类:表示互斥开关,继承自CircuitDevice。互斥开关有3个引脚:1个汇总引脚和2个分支引脚。只有两种状态:开关接往2号引脚或3号引脚,默认状态为1、2引脚接通,1、3引脚断开。为避免短路,设置了限流电阻,12引脚之间默认电阻为5,13引脚之间默认电阻为10。
4. Fendang类:表示一个分档设备,继承自CircuitDevice。它能够根据速度档位调节电压,档位值从0档-2(3/4)档变化,每个档位的输出电位分别为0、0.3、0.6、0.9倍的输入电压。
5. Lianxu类:表示一个连续调节设备,继承自CircuitDevice。它能够根据给定的线性值调节电压,没有固定档位,按位置比例得到档位参数,数值范围在[0.00-1.00]之间,含两位小数。输出电位为档位参数乘以输入电压。
6. Baichi类:表示一个白炽灯设备,继承自CircuitDevice。它根据电压显示亮度,亮度在0~200lux(流明)之间。电位差为0-9V时亮度为0,其他电位差按比例,电位差10V对应50ux,220V对应200lux,其他电位差与对应亮度值成正比。白炽灯超过220V。
7. Riguang类:表示一个日光灯设备,继承自CircuitDevice。它根据电压显示亮度,只有两种状态,电位差为0时,亮度为0,电位差不为0,亮度为180。
8. Diaoshan类:表示一个吊扇设备,继承自CircuitDevice。它根据电压显示转速,输入输出电位差小于80V时转速为0,超过150V转速为360转/分钟,其他电压值与转速成正比n=(voltage - 80) * 4 + 80。
9. ShuKongChuanglian类:表示受控窗帘,继承自CircuitDevice。display 方法根据电压和光照强度显示不同开合程度。calculateTotalLux 方法计算总光照强度。reshuV 方法设置电压。
10. Main类:是整个电路系统的管理类,负责读取用户输入的电路连接和控制指令,构建电路,调节电路设备,并最终输出每个设备的状态。
运行流程:
- 通过Scanner类读取用户输入的电路连接和控制指令,输入一共分为两种
第一种:连接信息"["+引脚号+" "+...+" "+引脚号+"]",例如[K1-1 K3-2]表示K1的1引脚,K3的2引脚连接在一起。
第二种:控制设备调节信息
开关:#+设备标识K+设备编号,例如:#K2,代表切换K2开关的状态。
分档调速器:#+设备标识F+设备编号+"+" 代表加一档,例如:#F3+,代表F3输出加一档。#+设备标识F+设备编号+"-" 代表减一档,例如:#F1-,代表F1输出减一档。
连续调速器:#+设备标识L+设备编号+":" +数值 代表将连续调速器的档位设置到对应数值,例如:#L3:0.6,代表L3输出档位参数0.6。 - 根据输入中的设备信息创建相应的电路设备对象,并将其加入到map映射中。
- 当读取到控制指令时,根据指令类型(如开关操作、分档调节等)对相应的设备进行操作,并更新电压。
- 遍历map映射中的所有设备及其连接关系,根据设备的电压和特定逻辑计算设备的状态。包括计算电阻、电压分配等,使用数组
a
和b
存储并联和串联电路的电阻信息,计算总电阻和分电压。 - 使用
Comparator
对电器进行排序并输出每个设备的ID和当前状态,如开关的开闭状态、分档的速度、白炽灯的亮度等。 - 当读取到“end”时,结束程序
主要功能实现:
- 输入处理:
使用 Scanner 读取用户输入,根据输入的前缀(#T
、#M
、#K
、#F
、#L
等)将信息存储到不同的ArrayList
中,用于后续处理电路连接和操作。
Scanner in = new Scanner(System.in);
while (true) {
s = in.nextLine().trim();
if ("end".equals(s)) break;
if (s.startsWith("#T")) {...}
else if (s.startsWith("#M")) {...}
else if (s.startsWith("#K")) {...}
else if (s.startsWith("#F")) {...}
else if (s.startsWith("#L")) {...}
}
- 电路存储和连接处理:
对于存储在connection
中的串联电路信息和connection1
中的并联电路信息,使用Pattern
和Matcher
解析信息,并使用Map
存储电器设备及其连接关系。
for (mmm = 0; mmm < connection.size(); mmm++) {
Map<String, Electric> map1 = new LinkedHashMap<>();
z1 = connection.get(mmm);
Pattern pattern = Pattern.compile("(.*):(.*)");
Matcher matcher = pattern.matcher(z1);
while (matcher.find()) {...}
String[] partsl = pop.split("]");
for (kllk = 0; kllk < partsl.length; kllk++) {...}
if (vb1 == 1) mac.put(zm, map1);
if (vb2 == 1) zhu = map1;
}
对于并联电路,将其存储在 maps
中,进行进一步的处理和操作。
for (mmm = 0; mmm < connection1.size(); mmm++) {...}
- 电器操作和状态更新:
对存储在chw
中的操作命令(如开关操作、分档调节、连续调节),遍历maps
和zhu
中的电器,找到相应的电器并执行操作。
for (i = 0; i < chw.size(); i++) {
String sid = chw.get(i);
for (Entry<String, Map<String, Electric>> entry : maps.entrySet()) {...}
for (Entry<String, Electric> entry : zhu.entrySet()) {...}
}
- 电路计算:
计算电阻:根据电器的开关状态和连接关系,计算串联和并联电路的等效电阻,存储在数组a
和b
中。
double a[] = new double[maps.size()];
double b[] = new double[zhu.size()];
for (Entry<String, Map<String, Electric>> entry : maps.entrySet()) {...}
for (Entry<String, Electric> entry : zhu.entrySet()) {...}
计算电压分配:根据计算得到的电阻,计算串联和并联电路的电压分配。
double chuanR, bingR;
chuanR = 1.0*b[0];
if (lpl1 == 0) bingR = 0;
else bingR = 1.0/lpl1;
double chuanV = 0, bingV = 0;
chuanV = rui * chuanR / (bingR + chuanR);
bingV = rui * bingR / (bingR + chuanR);
根据电压分配更新电器的电压,并将更新后的电器存储在 maso
中。
Map<String, Electric> maso = new HashMap<>();
for (Entry<String, Electric> entry : zhu.entrySet()) {...}
- 排序和输出:
将maso
中的电器存储在ArrayList
中,使用自定义Comparator
对电器进行排序,根据fanhui
方法的结果和id
排序。
ArrayList<Electric> arraylist6 = new ArrayList<>();
Comparator<Electric> numberComparator = new Comparator<Electric>() {...};
Collections.sort(arraylist6, numberComparator);
for (Electric suixcs : arraylist6) {
suixcs.display();
}
SourceMonitor图表如下
从左图可以看到:代码的注释百分比低,方法数量适中,平均和最大复杂度高,平均深度深,导致代码难以理解和维护,代码质量仍有待改进提升。
从右图可以看到:深度为1-2和3-5的代码块数量较多,表明方法调用链不长,易于理解和测试,方法之间存在较多的依赖关系,可能需要重构。
大作业8
是大作业7的迭代,在作业7的基础上x增加了以下内容:
- 管脚电压显示:在输出每个电器的状态信息后,系统将依次输出该电器每个管脚的电压。例如:@K3:turned on 220-0 @H1:closed 0-0-0 @P2:cutoff 0-0
- 电流限制:每个元器件都有最大电流设置,当实时电流超过最大电流时,将在电器输出信息的最后加入提示“exceeding current limit error”。
例如:@B1:190 68-17 exceeding current limit error
各类电器的最大限定电流如下:
开关20、分档调速器18、连续调速器18、白炽灯9、日光灯5、吊扇12、落地扇14、互斥开关20、受控窗帘12、二极管8。 - 短路检测:如果电路出现无穷大的电流造成短路,所有元器件信息不输出,仅输出提示“short circuit error”。
- 并联电路中包含并联:系统考虑并联电路中包含并联电路的情况,即构成并联电路的串联电路可以包含别的并联电路,较难实现。
- 二极管:增加二极管元件,其电路特性为正向导通,反向截止。二极管的标识符为'P',左侧管脚编号为1,右侧管脚编号为2。当电流从左至右流过时,二极管导通”conduction”,电阻为0;电流从右至左流动时,二极管截止”cutoff”,电阻无穷大,相当于开关打开。如果两端电压相等,没有电流流过,分以下两种情况输出:
1、如果两端电压为0,二极管的导通/截止状态由接入方向决定,1号引脚靠近电源则状态为导通,反之为截止。
2、如果两端电压不为0,二极管导通。
本题设计类图与时序图如下:
运行流程
- 通过Scanner类读取用户输入的电路连接和控制指令。
- 根据输入中的设备信息创建相应的电路设备对象,并将其加入到map映射中。
- 当读取到控制指令时,根据指令类型对相应的设备进行操作,并更新电压。
- 遍历map映射中的所有设备,根据设备的电压和特定逻辑计算设备的状态。
- 使用display方法输出每个设备的ID和当前状态,包括管脚电压。
- 如果检测到短路,输出“short circuit error”并停止程序。
- 当读取到“end”时,结束程序。
SourceMonitor图表如下
从左图可以看到:代码质量还行,但最大复杂度较高,会导致代码难以测试和维护。
从右图可以看到:深度为0-2的代码块数量较多,表明有许多独立的语句或小方法、方法调用链不长,有助于代码的模块化,易于理解和测试。
三、踩坑心得
在提交源码的过程中,我遇到了以下问题:
- 管脚电压计算错误:
控制器的管脚电压计算可能因为互斥开关等特殊元件的存在而出错,这些元件既受控制器状态影响,又包含电阻。
解决方案:重新设计电压计算逻辑,确保能够准确反映电路的实际情况。对于互斥开关,需要根据其状态和电阻值精确计算通过它的电压降额。此外,可以引入电压降额检测,确保电压计算与实际电路行为一致。 - 对于复杂的电路无法正确检测:
在处理嵌套的并联电路、多个串联电路等复杂电路时,系统无法正确识别电路间的关系,导致电压分配错误。
解决方案:通过递归算法来处理嵌套电路、优化串联电路的识别逻辑,确保所有层级的电路都能被正确解析和计算。 - 对含二极管的电路识别不准确:
在包含二极管的电路中,系统无法正确处理二极管的单向导电性,影响了电路的准确性。
解决方案:为二极管添加特定的模型,考虑其正向导通和反向截止的特性,确保在电压计算中正确处理二极管的影响。 - 电路状态更新不及时:
电路状态发生变化时,系统无法及时更新所有相关元件的状态,导致电路行为模拟不准确。
解决方案:优化电路状态更新机制,使用更高效的数据结构,确保任何电路状态的变化都能迅速传播到所有相关元件,并及时更新它们的状态。
四、改进建议
- 方法提取与模块化:
代码中的main
方法过长且包含多个逻辑块,可以将不同功能的代码提取到独立的方法中,提高代码的可读性和可维护性。例如:
点击查看代码
// 处理输入信息
private static void processInput(Scanner in, ArrayList<String> connection, ArrayList<String> connection1, ArrayList<String> chw) {
String s = "";
while (true) {
s = in.nextLine().trim();
if ("end".equals(s))
break;
if (s.startsWith("#T"))
processSeriesConnection(s, connection);
else if (s.startsWith("#M"))
processParallelConnection(s, connection1);
else if (s.startsWith("#K") || s.startsWith("#F") || s.startsWith("#L"))
processControlCommand(s, chw);
}
}
// 处理串联连接信息
private static void processSeriesConnection(String s, ArrayList<String> connection) {
Pattern pattern = Pattern.compile("#(.*):(.*)");
Matcher matcher = pattern.matcher(s);
if (matcher.find()) {
String chuanxu = String.valueOf(matcher.group(1));
String pop = String.valueOf(matcher.group(2));
connection.add(chuanxu + ":" + pop);
}
}
// 处理并联连接信息
private static void processParallelConnection(String s, ArrayList<String> connection1) {
Pattern pattern = Pattern.compile("#(.*):(.*)");
Matcher matcher = pattern.matcher(s);
if (matcher.find()) {
String bingxu = String.valueOf(matcher.group(1));
String pop = String.valueOf(matcher.group(2));
connection1.add(bingxu + " " + pop);
}
}
// 处理控制命令
private static void processControlCommand(String s, ArrayList<String> chw) {
Pattern pattern = Pattern.compile("#(.*)");
Matcher matcher = pattern.matcher(s);
if (matcher.find()) {
String pop = String.valueOf(matcher.group(1));
chw.add(pop);
}
}
这样可以将输入处理逻辑分解为几个独立的部分,使 main
方法更简洁。
- 类结构优化:
对于Electric
类及其子类,部分方法的实现可以更加统一和规范。例如,display
、regulate
等方法在父类中是空实现,可考虑将其定义为抽象方法,强制子类实现这些方法,以确保一致性和完整性。
点击查看代码
abstract class Electric {
public String s;
public String id;
public double shuV = 220;
public String ofopen = "turned on";
public int speed = 0;
public double lin = 0.00;
public int resistance;
public Electric(String s, String id) {
this.s = s;
this.id = id;
}
public abstract void display();
public abstract void regulate(String vs);
public abstract void reshuV(double shuop);
}
}
- 变量重命名、添加注释,提高代码可读性:
代码中存在一些命名不清晰的变量,如s
、pop
、chw
、zhu
等,应使用更具描述性的变量名。例如,将chw
改为controlCommands
,zhu
改为mainCircuitDevices
等,以便更好地理解代码的功能。
ArrayList<String> controlCommands = new ArrayList<>();
Map<String, Electric> mainCircuitDevices = new LinkedHashMap<>();
为代码添加更多的注释,尤其是复杂逻辑部分,如电路计算和设备操作部分,以帮助理解代码的目的和实现方式。
- 删除重复代码消除,减少代码冗余:
在代码中存在一些重复的逻辑,例如在处理maps
和zhu
中的电器操作时,部分代码逻辑几乎相同,可以提取为一个独立的方法,减少代码冗余。
点击查看代码
private static void performOperationOnDevice(Map<String, Map<String, Electric>> map, String sid) {
if (sid.startsWith("K")) {
String diu = sid.substring(0, 2);
Electric electric = map.get(diu);
if (electric!= null) {
if (electric.ofopen.equals("turned on"))
electric.ofopen = "closed";
else
electric.ofopen = "turned on";
}
} else if (sid.startsWith("F")) {
String diu = sid.substring(0, 2);
String vs = sid.substring(2);
Electric electric = map.get(diu);
if (electric!= null) {
electric.regulate(vs);
// 其他操作
}
} else if (sid.startsWith("L")) {
String diu = sid.substring(0, 2);
String vs = sid.substring(3);
Electric electric = map.get(diu);
if (electric!= null) {
electric.regulate(vs);
// 其他操作
}
}
}
```
然后在 `main` 方法中调用:
for (i = 0; i < chw.size(); i++) {
String sid = chw.get(i);
performOperationOnDevice(maps, sid);
performOperationOnDevice(zhu, sid);
}
- 优化数据结构:
对于存储设备的Map
和ArrayList
,可以考虑使用更合适的数据结构。例如,如果经常需要根据设备 ID 进行查找操作,可以使用HashMap
存储设备信息,提高查找效率。
Map<String, Electric> allDevices = new HashMap<>();
// 存储设备
allDevices.put(device.getId(), device);
// 查找设备
Electric device = allDevices.get(deviceId);
由上述SourceMonitor生成的报表可以看出代码普遍存在平均深度、平均复杂度和最大复杂度较高,代码注释少的问题,以下是改进建议:
- 要降低代码复杂度
i. 重构代码:将复杂的代码分解成更小、更易于管理的函数或方法。每个函数应该只做一件事情,并且做好一件事情。识别代码中的公共模式和逻辑,将它们抽象成独立的模块或类。这有助于减少重复代码,提高代码复用性。
ii. 减少嵌套:优化循环和迭代,减少条件语句和循环的嵌套层数,利用多态减少条件分支,还可以通过提取方法、使用早返回(early return)等技巧来实现。
iii. 单一职责原则:确保每个类和方法都有一个明确的职责,并且只为这个职责服务。这有助于降低代码的耦合度,提高代码的可读性和可维护性。 - 要提高代码可读性
i. 增加注释:在代码中添加必要的注释,解释复杂的逻辑、重要的决策点和代码的预期行为。注释应该简洁明了,能够为以后改进完善代码时提供足够的信息。
ii. 命名要规范:使用有意义的变量名和方法名,避免使用缩写和模糊的命名,这样即使没有注释,代码的意图也应该是清晰的。
五、总结
在最近的编程实践中,我通过完成两次家居强电电路模拟程序d的迭代,深刻体会到了Java编程的挑战性和实用性。虽然在这些项目中遇到了一些难题,但我在解题和优化代码的过程中收获颇丰,不仅提升了对Java编程的掌握,还对电路模拟有了初步的认识。通过这个项目,我更加熟练地运用了正则表达式来解析复杂的电路配置,并且对ArrayList、HashMap等数据结构的运用也更加熟练,这些数据结构在处理电路元件和状态时至关重要。面向对象设计方面,我更加熟悉了如何定义类、创建对象以及如何通过实例方法和类方法来实现功能,对封装、继承和多态等核心概念有了更深入的认识。
这些经验让我在编写和理解代码时更加得心应手,我意识到了代码的可读性和可维护性的重要性,并学会了如何编写更清晰、更模块化的代码,这为后续优化代码时理解源代码提供了极大帮助。同时,我在实践中不断将Java的七个原则运用巩固,在未来的学习中,我期望能够有更强的动手能力,在实践中不断应用所学的知识,以逐步提高编程技能和解决问题的能力。我期待在未来的项目中,能够将这些经验转化为更高质量的代码和更高效的解决方案。
六、学期总结
在这一学期的Java面向对象程序编程课程中,我获得了宝贵的知识和实践经验。我深入掌握了面向对象编程的基本概念,包括类、对象、封装、继承和多态性。这些概念不仅为我打下了坚实的编程基础,也让我能够从不同角度审视和理解软件系统的复杂性。
技能提升:
• 我已能熟练地运用Java语言进行面向对象的编程,这包括了对类和对象的深入理解以及如何有效地使用它们来构建软件。
• 我掌握了核心的OOP概念,如封装,它让我能够保护对象的内部状态;继承,它允许我复用现有代码;以及多态性,它提供了程序的灵活性和可扩展性。
• 我熟悉了Java中常用的数据结构,特别是ArrayList和HashMap,这些工具不仅提高了我的编程效率,也增强了我的问题解决能力。
设计洞察:
• 我深刻理解了优秀软件设计的重要性,它不仅关乎功能的实现,也关乎代码的可读性、可维护性和可扩展性。这让我意识到,良好的设计是软件成功的关键。
• 我学习了如何使用UML等工具来清晰地设计系统架构,这让我能够更清晰地理解系统组件如何交互。同时,我也学会了如何运用设计模式来解决常见的设计挑战,这提高了我的代码质量和开发效率。
未来展望:
• 我打算学习其他编程语言和技术,比如Python、JavaScript以及Spring框架,这将帮助我拓宽技术视野,提升我的技术竞争力,并为未来的职业生涯做好准备。
通过这些实践,我深刻理解了高质量代码的重要性,并学会了如何通过各种方法和工具提升代码质量。我期待将这些经验应用到未来项目中,实现更优秀的设计和更高效的解决方案。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异