第二次Java大作业总结

Java大作业总结

一.前言

这三次大作业,第四次是连接着前三次的,由于还有填空题题和多选题的出现,判分的逻辑要改变,同时还增加了正则表达式判断格式,输出的逻辑也要改变,这次代基本上是对上次代码的大改,只有输入的逻辑和之前两次差不多,其他逻辑都改变了。第五次大作业是新的开始,比较简单,我也没有用到电路这个类和引脚这个类,但这导致第五次代码沿用第四次的根本写不出来,于是又重写了一遍加了串联电路类和并联电路类。这三次大作业都用到了继承。同时也都用到了集合和集合的遍历。还有类里面重写toSting方法。对于集合的遍历的方法,很多都是Java特有的,在C语言未曾见过的语法。

第四次大作业

1.设计与分析

这次的代码的输入逻辑我沿用了上次大作业的逻辑,同时也增加了分割多选题和填空题的代码逻辑。把单选题,多选题,填空题抽象出来,变成了类,这三个类分别是oneChoice multipleChoice fillQuestions ,同时它们都继承至Question这个类,这个类又有id contentAnswer这三个成员变量。同时还有类QuestionMap ,这个类中的成员变量private Map<Integer, Question> questionIDAndQuestion = new LinkedHashMap<>();存储所有题目的信息。

  • scanningPaper这个类的职责是存储输入的信息。先读取一行的信息判断输入信息是否是end,如果不是就用StringBuffer存储同时还在每一行的末尾存储换行符\n
  • 接着便是Split这个类,它获取到刚刚存储到的信息,按开头字母的进行题目的划分,创建相应单选题,多选题和填空题的对象,接着存储到questionIDAndQuestion 里面。在划分时,还有进行题目格式的判断,于是我定义了一个checkFormat类,来检查题目格式是否正确,若不正确,提示信息,同时跳过这个题目的存储。在#T的分割中,我一边存储信息,也一边算总分是否达到了100分,若没有达到也直接提示对应信息。还有学生类中,定义了一个Map的键值对来存储学生的学号和姓名。也有删除题目类,我用Set集合存储删除题目的题号。至此输入信息,分割和存储题目信息等工作都完成。检查格式的正则表达式如下:
	String check = "#N:\\d+(\\s+#Q:.+?\\s+#A:.+?)";
    String check = "#Z:\\d+\\s+#Q:.+\\s+#A:.+";
    String check = "#K:\\d+\\s+#Q:[^#]+\\s+#A:[^#]+";
    String check = "#T:\\d+(\\s+\\d+-\\d+)+";
    String check = "#X:\\d+\\s+\\w+(-\\d+\\s+\\w+)*";
    String check = "#S:\\d+\\s+\\d+(\\s+#A:\\d+-.+)+";
  • 接着便是评分类rate,这里用到了很多Java特有的集合的遍历和Lambda表达式,比如

    split.getAnswerPaper().AnswerSheets.forEach((paperId, studentMap) -> studentMap.forEach((studentId, Answers) -> studentAnswers.computeIfAbsent(studentId, k -> new HashMap<>()).put(paperId, Answers)));
    

    这里就是先遍历AnswerPaper中的AnswerSheets集合,同时这个集合中又存储了studentAnswers这个集合,于是有对学生的答案信息进行遍历,从这个集合中获取学生的学号,同时从AnswerSheets集合获取学生输入的答案,接着用Map<String, Map<Integer, List<Answer>>> studentAnswers = new HashMap<>();存储起来,学号最为键,对应学号的学生的答案存在List集合里面作为值。因为最后输出答案要按学号顺序输出,于是又对这个Map进行按学号排序,最终遍历整个排序好了的集合,通过学号获取到试卷的Id,然后通过试卷的Id获取题目信息,遍历题目信息,对答案为空,被删除的题目和题目本就不存在这三种情况进行过滤,输出对应的信息。若是没有这三种情况,就开始评分.

    • 单选图评分最简单,return question.getAnswer().equals(givenAnswer.trim());直接比较学生给的答案和标准答案是否符合,返回值为布尔类型。
    • 在多选题中,先把存储标准答案的集合和学生答案的集合转为Set集合,接着用containsAll先比较是否包含全部内容,再比较Set集合大小,若是标准答案的Set集合全部包含了学生答案的Set集合,且2个Set大小一样,那么说明是全对的。若是标准答案的Set集合全部包含了学生答案的Set集合,但标准答案的Set集合大小大于学生答案的Set集合,那说明是少选。若是若是标准答案的Set集合没有全部包含了学生答案的Set集合,那就是错选了
            if (studentAnswerSet.containsAll(standardAnswerSet) && standardAnswerSet.size() == studentAnswerSet.size()) {
                return true;
            }
            if (studentAnswerSet.containsAll(standardAnswerSet) && standardAnswerSet.size() < studentAnswerSet.size()) {
                return "partially correct";
            }
            return false;
        }
    
    • 在填空题中,获取到标准答案的字符串,再与学生的答案比较,若是equal,那就是全对。如果不是equal而是contain那就是半对,2中情况都不是那就是错填。

判完分最后就是输出了。

类图

顺序图


2.踩坑心得

  • 刚开始由于错误使用equal方法,导致要是半对也会判成错误。

  • 遍历题目时,删除的题目可能还会遍历,导致根据题号获取题目时,题目对象为null报错。

3.改进建议

  • 当前代码在很多地方直接使用了 System.out.println 来输出错误信息,如遇到无效问题或答案为空的情况。建议使用异常处理机制来替代,这样可以更好地控制程序流程,并且可以在更高层次捕获并处理这些异常。

  • checkCorrect 方法在 oneChoice, multipleChoice, 和 fillQuestions 类中实现了相似的功能,但各自独立实现。可以通过在基类 Question 中定义一个默认的检查方法,并允许子类重写它来提高代码复用性。

  • ratingPaper 方法中,有多个循环嵌套,特别是当数据量较大时可能会导致性能瓶颈。考虑使用更高效的数据结构或算法来减少不必要的遍历次数,例如使用哈希表来快速查找答案。

  • rate 类中的 ratingPaper 方法实现了评分逻辑,但该方法过于庞大,包含了太多的功能。可以考虑将其拆分为几个小的方法,每个方法负责评分过程中的一个具体步骤,比如获取学生答案、验证答案正确性等。这样做可以使代码更加模块化,易于维护和测试。

  • 遵循Java命名约定,变量名应采用小驼峰式命名法(camelCase),如 AnswerContent 应改为 answerContent

  • 类名应该清晰地表达其用途,如 DeletionQuestion 可能更合适的名字是 DeletedQuestionsManager




第五次大作业

1.设计与分析

看了题目知道这是第一次迭代,比较简单。我也按照题目信息,划分了device这个类和它的子类controlDeceive还有controlledDeceive,其中controlDeceive又有子类开关k,分档调速器f,还有连续调速器l。controlledDeceive又有子类白炽灯b,日光灯r,和吊扇d。

  • device类中inputVoltage,outputVoltage,默认都是220V,还有一个String类型的deceiveId。

  • 在开关k类里面还有一个独属于它的属性link,默认为false,当检查到输入了#K时,就会调用k类的toggle方法,把link置为相反,同时输入输出电压都变成0V,表示断路。

  • 在分档调速器f类中,也有它的私有属性gears,默认为0,通过这个参数,可以修改分档调速器输出的电压。当检查输入了#F+或#F-时就会调用addGears或sybGears方法,实现分档,最后调用这个类里面的SetOutputVoltage方法,就可以实现分档调速。

  • 在连续调档器l,同样也有proportion表示挡位,也是调用SetOutputVoltage方法设置输出电压,输出的电压直接通过getInputVoltage获取输入电压在乘上挡位。还要注意的是,最后输出我是放在这个类中,重写toSting方法,我用了DecimalFormat类实现输出只保留2为小数。

  • 在柏白炽灯d类中,有表示亮度的属性lightIntensity,当输入的电压大于0时,通过公式
    lightIntensity = (int) ((getOutputVoltage() - 10) * 5 / 7 + 50)就可以计算出亮度。

  • 在日光灯r类中,同样定义了Brightness,这里计算比较简单,亮度只有0和180。

  • 在吊扇d类中,定义了speed,根据电压调节speed,在电压80~150时,根据公式speed = (int) (4 * getOutputVoltage() - 240);算出此时的电压,大于150V时直接设置speed为360,小于80V时speed设置为0。

  • 此外还定义了一个home类。来操控以上这些类,在输入时,也是和上一次大作业一样,定义一个StringBuilder类型变量,如果输入了end,就跳出循环,否则用该变量拼接该行后再拼接换行符\n,再逐行判断如果这一行字符串开头是#,就进入getControlCommand方法,获取命令,否则就进入getDevice方法获取设备,我定义了一个Map存储这些设备

    private static Map<String, device> indexAndDeceive = new LinkedHashMap<>();

    先根据开头首字母判断是哪一种设备,然后再构造对应设备的实例,分割字符串,获取设备Id,因为同一个设备Id会出现2次,所以如果indexAndDeceive里面没有值为这个Id,就存储进去,否则不存储。然后在getControlCommand方法中,判断这一行是包含K,F,L其中的哪一个字母,然后跳转到对应操作。

  • 最后是输出,输出的顺序要按开关、分档调速器、连续调速器、白炽灯、日光灯、吊扇的顺序依次输出,所以先定义一个字符串数组,代表顺序,先遍历整个数组的内容,再在循环中遍历设备的Map,获取设备的Id,如果设备的Id的首字母是这个顺序就输出,否则就跳过

    String[] order = {"K", "F", "L", "B", "R", "D"};
            for (String type : order) {
                for (Map.Entry<String, device> entry : indexAndDeceive.entrySet()) {
                    String deviceId = entry.getKey();
                    device device = entry.getValue();
                    if (deviceId.startsWith(type)) {
                        System.out.println(device);
                    }
                }
            }
    

类图

顺序图

2.踩坑心得

  • 开始存储Id值时,只是把K,L,F...这些当成Id,但是后面发现应该把K1这种当成Id存储。
  • 开始默认电压为220V,但是后面经过分档调速器和连续调速器时,电压会变小,但是我又没用到引脚,如果后面有多个电器,就会出现前面和后面的电器联系不上的情况,导致电压也不知道是多少了。

3.改进建议

  • 类名应该使用驼峰式命名法(Camel Case),例如 ControlledDevice 而不是 controlledDeceive

  • 变量名和方法名也应该遵循驼峰式命名法,如 deviceIDsetDeviceID

  • 避免使用容易引起误解的名称,如 deceiveId,建议改为 deviceId

  • 抽象类:可以考虑将 device 类定义为抽象类,并在其中定义一些通用的方法,如 turnOn()turnOff()

  • 接口:可以定义一些接口来描述不同的行为,如 SwitchableDimmable 等,这样可以使代码更加模块化和易于扩展。

  • 设备管理:home 类中的设备管理可以使用更高效的数据结构,如 HashMapTreeMap,以便更快地查找和操作设备。

  • 输入解析:输入解析部分可以使用正则表达式来简化和优化,避免手动分割字符串。

  • 状态管理:可以使用枚举类型来管理设备的状态,如 ONOFF 等,这会使代码更加清晰。

  • 电压设置:在 fl 类中,电压设置的逻辑可以提取到一个单独的方法中,以减少重复代码。

  • 输入验证:在解析输入时,应添加更多的异常处理,确保输入格式正确,防止程序因错误输入而崩溃。

  • 边界条件:在设置电压和速度等参数时,应检查边界条件,确保不会出现非法值。




第六次大作业

1.设计与分析

由于上次大作业设计的不好,这次大作业重写了一边类和逻辑,现在我定义了一个电路设备类CircuitDevice用来被继承,这个类属性有id,resistance,voltage,outputVoltage,inputVoltage。和上次一样,定义了受控设备类ControlledDevice和控制设备类ControlDevice继承它,这两个类有被其他类继承。在开关类的构造方法中,直接报开关电压置为-1,代表开路。若开关闭合,则设置电阻为0Ω。其他控制设备类也是和上次一样,只是电阻要设置为0Ω。其他的受控设备也是和上次一样,多的一步就是要在构造方法里面设置电阻。同时我还定义了一个电源类和一个地类它们都继承电路设备类。特别的是,根据老师上课讲的思路,我把并联电路也继承了电路设备类,当成一个设备,这样好算电阻和电压。

  • 在输入时,判断开头,如果是“#T”就调用parseSeriesCircuit方法,如果是“#M”就调用parseParallelCircuit方法,否则调用parseOperation方法。同时输入解析类中有3个成员变量。

    private Map<String, CircuitDevice> devices; // 所有设备的映射
    private List<SeriesCircuit> seriesCircuits; // 串联电路列表
    private List<ParallelCircuit> parallelCircuits; // 并联电路列表
    
  • 在parseSeriesCircuit方法中,先过滤IN,OUT,VCC,GND这些字符串,然后判断是哪一个设备,然后构造该设备的实例,添加到devices这个Map集合,同时这个方法创建了一个SeriesCircuit实例,这个类里面有private List<CircuitDevice> devices; // 串联电路中的设备列表,会把设备的实例也添加到这个List里面,之后把SeriesCircuit这个实例加入到seriesCircuits这个List集合。

  • 在parseParallelCircuit方法里面,把并联设备存储起来,同时计算并联电路的总电压。

  • 最后是输出,遍历seriesCircuits,判断总电阻是不是-1,若是-1代表断路,直接跳过,若不是,继续遍历,是否是调速器,是的话计算经过调速器的之后的电压,然后用分压法计算各个受控设备的状态。接着又遍历遍历parallelCircuits,计算它内部串联电路的的电阻和电压,更新设备状态。

        public void updateCircuits(double voltage) {
            for (ParallelCircuit circuit : parallelCircuits) {
                circuit.total_resistance();
            }
            for (int i = seriesCircuits.size() - 1; i >= seriesCircuits.size() - 1; i--) {
                boolean flag = false;
                if (seriesCircuits.get(i).getTotalResistance() == -1)
                    break;
                if (seriesCircuits.get(i).getDevices().get(1) instanceof ContinuousSpeedController || seriesCircuits.get(i).getDevices().get(1) instanceof SteppedSpeedController) {
                    ((ControlDevice) (seriesCircuits.get(i).getDevices().get(1))).update(220);
                    flag = true;
                }
                for (int i1 = 2; i1 < seriesCircuits.get(i).getDevices().size(); i1++) {
                    double v = 0;//工作电压
                    if (flag) {
                        if (seriesCircuits.get(i).total_resistance() != -1)
                            v = seriesCircuits.get(i).getDevices().get(1).getOutputVoltage() * (seriesCircuits.get(i).getDevices().get(i1).resistance / seriesCircuits.get(i).total_resistance());
                    }
                    else
                        v = 220 * (seriesCircuits.get(i).getDevices().get(i1).resistance / seriesCircuits.get(i).total_resistance());
                    seriesCircuits.get(i).getDevices().get(i1).update(v);
                }
            }
            for (ParallelCircuit circuit : parallelCircuits) {
                int i = 0;
                for (SeriesCircuit seriesCircuit : circuit.getCircuits()) {
                    if (seriesCircuit.getTotalResistance() == -1) {
                        i++;
                        break;
                    }
                    for (CircuitDevice circuitDevice : seriesCircuit.getDevices()) {
                        circuitDevice.update(circuit.voltage * (circuitDevice.resistance / circuit.getCircuits().get(i).getResistance()));
                    }
                    i++;
                }
            }
        }
    

    最后输出和上一次大作业输出一样。


类图

顺序图


2.踩坑心得

  • 在开关类toggle方法中,我先判断开关状态设置电阻为0或-1,再取反开关状态导致后面逻辑错误,正确的应该是先取反再判断

  • 再判断#M时,通过findParallelCircuitById方法获取并联对象时,传入Id的值出错导致获取不到并联对象。


3.改进建议

  • 模块化:考虑将不同的功能模块化,例如将设备的创建逻辑、电路的构建逻辑、电路的更新逻辑等分别放在不同的类或方法中。这有助于提高代码的可读性和可维护性。
  • 封装性:进一步增强类的封装性,确保内部状态不会被外部直接修改。例如,CircuitDevice 类中的 resistancevoltage 等属性可以设置为私有,并提供公共的方法来访问或修改这些属性。
  • 避免重复计算:在 SeriesCircuitParallelCircuit 类中,总电阻的计算可以缓存起来,避免每次调用 total_resistance() 方法时都重新计算。

  • 减少对象创建:在 InputParser 中解析输入时,如果设备已经存在,则不需要重新创建对象。可以通过检查 devices 映射来确定是否需要创建新对象。

  • 命名规范:遵循一致的命名规范,例如使用驼峰命名法(camelCase)或下划线命名法(snake_case)。目前代码中既有驼峰命名法也有下划线命名法,建议统一。

  • 集成测试:编写集成测试来验证整个系统的各个部分是否协同工作。例如,可以测试完整的电路配置是否能够正确计算每个设备的状态。

  • 接口和抽象类:考虑定义更多的接口和抽象类,以便更容易地添加新的设备类型。例如,可以定义一个 SpeedController 接口,让 SteppedSpeedControllerContinuousSpeedController 实现该接口。

  • 工厂模式:使用工厂模式来创建设备对象,这样可以在不修改现有代码的情况下添加新的设备类型。


总结

通过这三大作业,让我认识到了代码逻辑结构设计的重要性。由于上次代码设计的不好,导致这次代码又得推倒上次代码重写,浪费了很多时间,如果刚开始就设计好了,直接在上次代码上增加内容,这样可以节省不少时间。这三次大作业也让我对继承有了更加深入的了解,同时我也学会了JAVA集合的遍历方法和Lambda表达式语法。还有学到了HasMap和LinkedHashMap等集合的异同点。对于什么场景使用什么类型的集合有了更加深入的认识。

posted @   22207130-叶盛东  阅读(35)  评论(1编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~
点击右上角即可分享
微信分享提示