PTA标题集4~6的总结性Blog

打印 上一主题 下一主题

主题 895|帖子 895|积分 2685

· 前言

本次的三个作业,由答题判题程序- 4、家居强电电路模拟程序- 1、家居强电电路模拟程序 -2构成。
答题判题程序-4是对前三次判题程序的末了升级,计划多个子类继承于基础题类来实现对每种题型的判断和计算分值;而家居强电电路模拟程序-1则是对输入的各个设备在串联关系中的状态更新,其中涉及到设备电压的计算和开关状态的判断,以及受控设备的状态输出;末了家居强电电路模拟程序-2则在前一题的基础上增加了并联的内容,在逻辑处置惩罚上更加的复杂了。本文将系统性总结这三次标题集的知识点、题量及难度,并分析其中的焦点内容和实现方法。
· 标题集概述

· 答题判题程序 - 4:

标题分析:


  • 数据结构
    标题信息、试卷信息、门生信息、答卷信息分别存储为字典或列表,方便检索与处置惩罚。
    单选题、多选题、填空题计划为子类单独处置惩罚判题逻辑。
  • 输入处置惩罚
    根据输入前缀(如#N:、#T:)判断当前行信息类型,逐一解析存储。
    本次作业新增输出顺序变化:
    只要是精确格式的信息,可以以任意的先后顺序输入各类不同的信息。比如试卷可以出现在标题之前,删除标题的信息可以出现在标题之前等。
    要成功处置惩罚以上要求就应该将标题、试卷等信息先
    本次作业新增输入格式内容:
    多选题:格式:"#Z:"+标题编号+" "+"#Q:"+标题内容+" "#A:"+标准答案
    填空题:格式:"#K:"+标题编号+" "+"#Q:"+标题内容+" "#A:"+标准答案
    格式根本的约束与一般的标题输入信息一致。 例如:#K:2 #Q:古琴在古代被称为: #A:瑶琴或七弦琴
    删除标题信息需在存储中直接删除,同时记载。
  • 判题计算分数规则
    单选题:答案完全匹配为精确,否则为错误。
    多选题:所有精确选项被选择且无错误选项给满分;部分精确无错误选项得半分;错误或无答案得0分。
    填空题:答案完全匹配为精确;部分匹配得半分;错误或无答案得0分。
  • 输出处置惩罚
    按学号与试卷号排序输出。
    输出每道题的详细判断效果和门生总分。
    若标题回答精确则输出true,多选题或者填空题部分精确输出~partially correct,单选题没有部分精确的环境;
    若答案错误或者包含错误选项或者内容则输出~false。
  • 告诫信息
    如果试卷总分不为100,输出“alert: full score of test paper1 is not 100 points”。
    如果试卷不存在,输出“the test paper number does not exist”
    如果试卷错误地引用了一道不存在题号的试题,在输出门生答案时,提示”non-existent question~”加答案。
    如果该题号被删除而且在答卷中没有答案则只输出answer is null。
计划与分析


  • 正则表达式


  • 使用正则表达式匹配输入格式:匹配标题格式(#N, #Z, #K 等)。匹配试卷信息格式(#T)。匹配答卷信息格式(#S)。
  • Pattern:定义正则表达式。
  • Matcher:实行匹配操作。

  • 数据结构与聚集框架
聚集类型

  • 使用 Map(HashMap 和 TreeMap)存储数据:
  • Map testPapers 用于存储试卷标题及分数。
  • Map answerSheets 用于存储门生答卷。
  • 使用 List(ArrayList)存储问题、答卷等顺序数据。
  • 使用 Set(HashSet)处置惩罚多选题答案的聚集操作。
排序

  • 使用 Comparator 对答卷按试卷号排序。
  • 使用 TreeMap 对 answerSheets 按门生 ID 举行排序。
流操作

  • 使用 stream 和 mapToInt 方法计算试卷总分。

  • 面向对象计划
封装

  • 将不同的功能模块封装为独立的类(如 InputHandler, OutputHandler 等)。使用私有字段和公共方法访问数据。
继承与代码复用

  • 通过继承 Question 类实现不同题型的共同属性与行为(如 getAnswerCorrectnessLevel() 方法)。
多态

  • 使用多态统一调用calculateScore() 等方法。
组合

  • 类中包含其他类的对象,例如 AnswerSheet 包含 QuestionScore 和门生答案。
单一职责

  • InputHandler 专注于输入数据解析。
  • OutputHandler 专注于处置惩罚输出。
源码结构分析


  • 计划模式:
    继承与多态:标题类型(单选、多选、填空)继承自 Question,实现不同的行为。
    组合模式:Exam 包含标题聚集,AnswerSheet 包含标题分数列表。
    分层计划:输入、逻辑处置惩罚、输出各自分离,增强模块化。
    类间关系:
  • Main 负责调用。
    Exam 作为焦点类管理标题。
    AnswerSheet 结合 Exam 和 Question 举行答案校验与得分计算。
    InputHandler 和 OutputHandler 负责与外界的交互。

main类中调用:
inputHandler.readExamData(exam, testPapers, answerSheets, testIds, students);来处置惩罚用户输入
点击查看代码
  1.     // 读取题目、试卷和答卷数据
  2.     public void readExamData(Exam exam, Map<Integer, List<QuestionScore>> testPapers, Map<Integer, List<AnswerSheet>> answerSheets, List<Integer> testIds, Map<Integer, Student> students) {
  3.         while (true) {
  4.             String inputLine = scanner.nextLine();
  5.             if (inputLine.equals("end")) {
  6.                 break;  // 输入结束
  7.             }
  8.             // 解析题目信息
  9.             try {
  10.                 if (inputLine.startsWith("#N:")) {
  11.                     Pattern questionPattern = Pattern.compile("#N:(\\d+)\\s+(?:(#Q:(.+?))\\s+#A:(.+?))");
  12.                     Matcher matcher = questionPattern.matcher(inputLine);
  13.                     if (matcher.matches()) {
  14.                         int num = Integer.parseInt(matcher.group(1));
  15.                         String questionContent, standardAnswer;
  16.                         // 判断匹配的顺序并获取对应的 group
  17.                         if (matcher.group(3) != null) {
  18.                             // #Q 在前
  19.                             questionContent = matcher.group(3);
  20.                             standardAnswer = matcher.group(4);
  21.                         } else {
  22.                             // #A 在前
  23.                             questionContent = matcher.group(6);
  24.                             standardAnswer = matcher.group(5);
  25.                         }
  26.                         exam.addQuestion(num, new BasicQuestion(num, questionContent, standardAnswer));
  27.                     } else {
  28.                         System.out.println("wrong format:" + inputLine);
  29.                     }
  30.                 }                // 解析多选题
  31.                 else if (inputLine.startsWith("#Z:")) {
  32.                     Pattern mcPattern = Pattern.compile("#Z:(\\d+)\\s+#Q:(.+?)\\s+#A:(.+)");
  33.                     Matcher matcher = mcPattern.matcher(inputLine);
  34.                     if (matcher.matches()) {
  35.                         int num = Integer.parseInt(matcher.group(1));
  36.                         String questionContent = matcher.group(2);
  37.                         String standardAnswer = matcher.group(3);
  38.                         exam.addQuestion(num, new ChoiceQuestion(num, questionContent, standardAnswer));
  39.                     } else {
  40.                         System.out.println("wrong format:" + inputLine);
  41.                     }
  42.                 }
  43.                 // 解析填空题
  44.                 else if (inputLine.startsWith("#K:")) {
  45.                     Pattern fbPattern = Pattern.compile("#K:(\\d+)\\s+#Q:(.+?)\\s+#A:(.+)");
  46.                     Matcher matcher = fbPattern.matcher(inputLine);
  47.                     if (matcher.matches()) {
  48.                         int num = Integer.parseInt(matcher.group(1));
  49.                         String questionContent = matcher.group(2);
  50.                         String standardAnswer = matcher.group(3);
  51.                         exam.addQuestion(num, new FillInTheBlankQuestion(num,questionContent, standardAnswer));
  52.                     } else {
  53.                         System.out.println("wrong format:" + inputLine);
  54.                     }
  55.                 }
  56.                 // 解析试卷信息
  57.                 else if (inputLine.startsWith("#T:")) {
  58.                     Pattern testPattern = Pattern.compile("#T:(\\d+) ((\\d+-\\d+ ?)+)");
  59.                     Matcher matcher = testPattern.matcher(inputLine);
  60.                     if (matcher.matches()) {
  61.                         int testPaperId = Integer.parseInt(matcher.group(1));
  62.                         List<QuestionScore> paperQuestions = new ArrayList<>();
  63.                         String[] parts = matcher.group(2).split(" ");
  64.                         for (String part : parts) {
  65.                             String[] tValues = part.split("-");
  66.                             int questionNum = Integer.parseInt(tValues[0]);
  67.                             int score = Integer.parseInt(tValues[1]);
  68.                             paperQuestions.add(new QuestionScore(questionNum, score));
  69.                         }
  70.                         testPapers.put(testPaperId, paperQuestions);
  71.                         int totalScore = paperQuestions.stream().mapToInt(QuestionScore::getScore).sum();
  72.                         if (totalScore != 100) {
  73.                             System.out.println("alert: full score of test paper " + testPaperId + " is not 100 points");
  74.                         }
  75.                     } else {
  76.                         System.out.println("wrong format:" + inputLine);
  77.                     }
  78.                 }
  79.                 // 解析学生信息
  80.                 else if (inputLine.startsWith("#X:")) {
  81.                     Pattern studentPattern = Pattern.compile("#X:(.+)");
  82.                     Matcher matcher = studentPattern.matcher(inputLine);
  83.                     if (matcher.matches()) {
  84.                         String[] parts = inputLine.substring(3).split("-");
  85.                         for (String part : parts) {
  86.                             String[] studentData = part.split(" ");
  87.                             int studentId = Integer.parseInt(studentData[0].trim());
  88.                             String studentName = studentData[1].trim();
  89.                             students.put(studentId, new Student(studentId, studentName));
  90.                         }
  91.                     } else {
  92.                         System.out.println("wrong format:" + inputLine);
  93.                     }
  94.                 }
  95.                 // 解析答卷信息
  96.                 else if (inputLine.startsWith("#S:")) {
  97.                     Pattern answerPattern = Pattern.compile("#S:(\\d+) (\\d+)(\\s+(#A:\\d+-(.+)*)*)*");
  98.                     Matcher matcher = answerPattern.matcher(inputLine);
  99.                     if (matcher.matches()) {
  100.                         String[] parts = inputLine.split("#");
  101.                         int testPaperId = Integer.parseInt(parts[1].split(" ")[0].split(":")[1].trim());
  102.                         int studentId = Integer.parseInt(parts[1].split(" ")[1].trim());  // 提取学号
  103.                         AnswerSheet answerSheet = new AnswerSheet(testPaperId);
  104.                         if (parts.length > 2) {
  105.                             for (int i = 2; i < parts.length; i++) {
  106.                                 if (parts[i].startsWith("A:")) {
  107.                                     String[] answerParts = parts[i].split("-");
  108.                                     if (answerParts.length == 2) {
  109.                                         int questionNum = Integer.parseInt(answerParts[0].split(":")[1].trim());
  110.                                         String str = answerParts[1]; // 去除可能的空格
  111.                                         String answer = removeLastSpace(str);
  112.                                         answerSheet.addAnswer(questionNum, answer);
  113.                                     }
  114.                                 }
  115.                             }
  116.                         }
  117.                         answerSheets.computeIfAbsent(studentId, k -> new ArrayList<>()).add(answerSheet);  // 将答卷关联到学生
  118.                     } else {
  119.                         System.out.println("wrong format:" + inputLine);
  120.                     }
  121.                 }
  122.                 // 解析删除题目信息
  123.                 else if (inputLine.startsWith("#D:N-")) {
  124.                     Pattern deletePattern = Pattern.compile("#D:N-(\\d+)");
  125.                     Matcher matcher = deletePattern.matcher(inputLine);
  126.                     if (matcher.matches()) {
  127.                         int questionNum = Integer.parseInt(inputLine.split("-")[1].trim());
  128.                         exam.removeQuestion(questionNum);  // 移除题目
  129.                     }
  130.                     else {
  131.                         System.out.println("wrong format:" + inputLine);
  132.                     }
  133.                 }
  134.             } catch (Exception e) {
  135.                 System.out.println("wrong format");
  136.             }
  137.         }
  138.     }
复制代码
该函数 readExamData 用于从输入中解析并加载试卷系统的相关数据,包括标题、试卷、答卷和门生信息。它通过扫描输入的每一行,根据不同的前缀(如 #N:、#T: 等)区分处置惩罚不同类型的数据。
解析的逻辑主要包括以下部分:

  • 标题数据解析
    针对不同类型的标题(普通题、多选题、填空题等),使用正则表达式提取标题编号、题干、标准答案等信息。根据标题类型创建相应的标题对象(如 BasicQuestion、ChoiceQuestion 等),并将其添加到考试对象中。输入格式错误会输出提示。
  • 试卷信息解析
    试卷数据以 #T: 开头,提取试卷编号及其对应的标题编号和分值。分值被封装为 QuestionScore 对象,并存入 testPapers 映射中。同时校验总分是否为 100 分,否则会告诫。
  • 门生信息解析
    以 #X: 开头的输入解析门生编号和姓名,将其存入 students 映射中,关联门生 ID 和门生对象。
  • 答卷信息解析
    以 #S: 开头的输入解析门生提交的答卷,包括试卷编号、门生编号及其标题作答信息,将答卷与门生对应关系存储到 answerSheets 中。
  • 删除标题
    以 #D:N- 开头的输入解析需要删除的标题编号,从考试中移除对应标题。
函数通过逐行处置惩罚输入,使用正则表达式确保数据格式的精确性,并对异常或格式错误的输入提供告诫提示,同时确保将各类数据有序地存储到相应的结构中(如 Map、List 等),为试卷系统的后续操作提供数据支持。
AnswerSheet类用于记载考生对某张试卷的答题环境,以及根据试卷内容输出答案详情和得分环境。该类包含了试卷编号、标题信息、考生的答案,以及输出答案和计算得分的逻辑。
outputAnswers方法输出考生的答案以及其对应的精确性。

实现细节:

  • 遍历所有试卷标题(paperQuestions)。
  • 从 Exam 中获取每道题的 Question 实例。
  • 获取考生答案并举行以下判断:

    • 如果试题不存在于试卷中,输出 non-existent question~0;
    • 如果答案为空,输出 answer is null;
    • 如果标题内容无效(如包含 "invalid"),输出内容加 ~0。

  • 调用 Question 的 getAnswerCorrectnessLevel 方法,获取答案的精确性等级,并输出具体效果。
outputScores类输出考生在每道题上的得分以及总分。

实现细节:

  • 初始化 totalScore 和 earnedScore。
  • 遍历所有标题:

    • 如果标题不存在,得分为 0;
    • 如果标题存在,调用 Question 的 calculateScore 方法计算该题得分,并累计到 totalScore。

  • 按标题顺序输出每题得分,用空格分隔,末了输出总分。
时序图


踩坑心得

1. 乱序输入问题
- 问题形貌:
比如 #N, #Z, #K, #T, #X, #S 等只要是精确格式的信息,可以以任意的先后顺序输入各类不同的信息。比如试卷可以出现在标题之前,删除标题的信息可以出现在标题之前等。
  1. else if (inputLine.startsWith("#S:")) {
  2.                     Pattern answerPattern = Pattern.compile("#S:(\\d+) (\\d+)( (#A:\\d+-(.+)*)*)*");
  3.                     Matcher matcher = answerPattern.matcher(inputLine);
  4.                     if (matcher.matches()) {
  5.                         String[] parts = inputLine.split("#");
  6.                         int testPaperId = Integer.parseInt(parts[1].split(" ")[0].split(":")[1].trim());
  7.                         int studentId = Integer.parseInt(parts[1].split(" ")[1].trim());  // 提取学号
  8.                         List<QuestionScore> paperQuestions = testPapers.get(testPaperId);
  9.                         AnswerSheet answerSheet = new AnswerSheet(testPaperId, paperQuestions);
  10.                         if (parts.length > 2) {
  11.                             for (int i = 2; i < parts.length; i++) {
  12.                                 if (parts[i].startsWith("A:")) {
  13.                                     int questionnum = Integer.parseInt(parts[i].split("-")[0].split(":")[1].trim());
  14.                                     String answer = parts[i].split("-")[1].trim();
  15.                                     answerSheet.addAnswer(questionnum, answer);
  16.                                 }
  17.                             }
  18.                         }
  19.                         answerSheets.computeIfAbsent(studentId, k -> new ArrayList<>()).add(answerSheet);  // 将答卷关联到学生
  20.                     }
复制代码
在上面的代码中举行#S答卷内容的解析,如果按代码的逻辑先获得testPapers.get(testPaperId);就会导致乱序输入试卷在答卷前报错。
解决思路:
AnswerSheet answerSheet = new AnswerSheet(testPaperId);修改AnswerSheet类构造方法,
List questionScores = testPapers.get(testPaperId);
answerSheet.setPaperQuestions(questionScores);在输出类中获取questionScores 再将该值赋给该对象。
2. 多选题、填空题判分逻辑问题
- 问题形貌:
①如果多选题答卷答案与标准答案部分相同,且没有包含不包括在标准答案中的答案,就判断为部分精确,分数计算为该题分数的一半,多余小数直接舍去。
②如果为填空题同理多选题,但标准答案以或连接多个答案,所以可以根据字符拆分精确答案来计算分值。
解决思路

  • 多选题:
  1.     @Override
  2.     public boolean isPartiallyCorrect(String answer) {
  3.         String[] correctAnswers = this.standardAnswer.split(" ");
  4.         String[] userAnswers = answer.split(" ");
  5.         Set<String> correctSet = new HashSet<>(Arrays.asList(correctAnswers));
  6.         Set<String> userSet = new HashSet<>(Arrays.asList(userAnswers));
  7.         // 判断用户的答案是否为正确答案的子集,且没有多余选项
  8.         return correctSet.containsAll(userSet) && !userSet.equals(correctSet);
  9.     }
复制代码

  • 填空题:
  1.     @Override
  2.     public CorrectnessLevel getAnswerCorrectnessLevel(String answer) {
  3.         if (isCorrect(answer)) {
  4.             return CorrectnessLevel.CORRECT;
  5.         } else if (isPartiallyCorrectForFillIn(answer)) {
  6.             return CorrectnessLevel.PARTIALLY_CORRECT;
  7.         } else {
  8.             return CorrectnessLevel.INCORRECT;
  9.         }
  10.     }
  11.     // 填空题的部分正确判断
  12.     private boolean isPartiallyCorrectForFillIn(String answer) {
  13.         String[] part = standardAnswer.split("或");
  14.         for (String ne : part) {
  15.             if (answer.equals(ne.trim())) {
  16.                 return true;
  17.             }
  18.         }
  19.         return false;
  20.     }
复制代码
这些判断逻辑根据以下结构体来简化操作逻辑:
  1.     public enum CorrectnessLevel {
  2.         CORRECT,
  3.         PARTIALLY_CORRECT,
  4.         INCORRECT
  5.     }
复制代码
改进发起


  • 分清职责,结构更清楚
    现在的代码把逻辑都放在一个类里,显得臃肿。可以拆分成负责输入输出、流程控制、标题解析等不同模块,各自只做自己的事。
  • 减少重复,提升复用性
    解析标题、验证答案时有很多重复代码。把这些重复逻辑抽取成公共方法或工具类,既省事又方便维护。
  • 代码更易读,少写嵌套
    多用早返回和拆分小方法的方式,避免复杂的 if-else 嵌套,让代码看起来更直观。
· 家居强电电路模拟程序 - 1

标题分析

1. 电路设备分类

设备分为控制设备受控设备两类,每类包含多种具体设备。

  • 控制设备

    • 开关(K)

      • 状态:0(打开/turned on) 或 1(关闭/closed)。
      • 功能:控制电压传递,状态为 1 时,输入电压传递到输出端;为 0 时输出端电压固定为 0。

    • 分档调速器(F)

      • 档位:0 至 3。
      • 功能:输入固定电压,通过档位调节输出电压比例(0.3、0.6、0.9)。

    • 连续调速器(L)

      • 档位范围:[0.00, 1.00],精确到两位小数。
      • 功能:输出电压为档位值与输入电压的乘积。


  • 受控设备

    • 白炽灯(B)

      • 工作状态:亮(0~200lux)或灭。
      • 功能:根据电压差计算亮度(线性比例)。

    • 日光灯(R)

      • 工作状态:亮度为180lux 或 0lux。
      • 功能:仅取决于是否有电压差。

    • 吊扇(D)

      • 工作状态:停止或转动(转速范围 0~360 转/分钟)。
      • 功能:根据电压差线性调整转速,低于 80V 停止。


2. 电路规则


  • 电压传递规则

    • 开关决定电压传递状态。
    • 调速器通过档位或比例控制输出电压。

  • 连接规则

    • 串联方式,电压从电源依次传递。
    • 所有设备需严格按照物理规律接入(如无反馈、并联等复杂环境)。

  • 输入输出规则

    • 所有设备连接以 VCC 为起点,GND 为终点。
    • 输入无连接的引脚默以为接地(0V)。

  • 设备编号与状态输出

    • 同种设备按编号顺序依次输出状态。

3. 输入与输出


  • 输入内容

    • 设备连接信息。
    • 控制设备调节信息。
    • 电路竣事标志(end)。

  • 输出内容

    • 所有设备的状态或参数值,按设备类型和编号顺序输出。

4. 功能实现焦点


  • 设备状态更新

    • 根据连接关系和调节操作动态更新每个设备的状态。

  • 电压传递与计算

    • 模拟串联电路中电压的分布和传递。
    • 处置惩罚调速器对电压的调节和受控设备的状态计算。

运行逻辑分析

1. 初始化设备


  • 程序通过继承和多态计划了多种设备类型,包括电源(VCC)、接地(GND)、开关(Switch)、分档调速器(StepSpeedController)、连续调速器(ContinuousSpeedController)、灯(白炽灯和日光灯)、风扇等。
  • 这些设备通过继承基类 Device,实现了多态行为,比如输入电压的设置和状态更新。
  • 特殊设备(如 VCC 和 GND)在程序启动时被初始化。
2. 解析输入


  • 程序从标准输入中读取指令,分为两类:

    • 连接关系([设备1-引脚 设备2-引脚]):通过 parseConnection 方法将设备连接信息解析成 Device 对象,并将连接关系存储到 Circuit 中。
    • 控制命令(#设备编号:参数):如开关状态切换、调速器调整等,这些命令被存储到 commands 列表中。

3. 建立电路连接


  • 所有设备通过 connect 方法连接,形成设备链。Device 的 nextDevice 属性记载下一个设备。
  • 特殊处置惩罚:

    • 如果某设备连接到 GND,会调用 Ground.setPreviousDevice,设置电压为零。

4. 实行命令


  • 调用 executeCommands 方法,解析存储的命令并对设备举行操作:

    • 切换开关状态(如开关打开或关闭)。
    • 调整分档调速器的档位。
    • 设置连续调速器的电压比例。

  • 在操作竣过后,电路从 VCC 开始,通过 setVoltage 触发电压向后传递,最终更新所有设备的状态。
5. 状态输出


  • 调用 printStatus 方法,根据设备类型输出状态,包括:

    • 开关的开关状态。
    • 调速器的档位。
    • 灯的亮度。
    • 风扇的转速。

  • 每种设备的状态通过其自身的 updateState 方法根据输入电压更新。
知识点总结

1. 面向对象编程 (OOP)


  • 抽象类:Device 是所有设备的基类,定义通用属性和方法(如 inputVoltage、connectTo 等),子类通过继承实现具体行为。
  • 多态:通过 Device 的引用调用子类重写的方法(如 updateState),实现设备的状态更新。
  • 封装:各设备的具体实现细节被封装在子类中,对外提供统一的接口。
2. 继承与类层次计划


  • 程序使用继承,计划了清楚的类层次:

    • Device 是基类。
    • ControlDevice 和 ControlledDevice 是子类,分别表示控制型设备和受控型设备。
    • 各种具体设备(如 Switch、Fan 等)再进一步继承上述子类。

3. 电路仿真逻辑


  • 电压传递:设备通过 connectTo 方法形成链式连接,电压从电源 VCC 开始逐级传递给后续设备。
  • 状态更新:每个设备的状态由输入电压和自身的逻辑决定。
4. 数据结构


  • Map 存储设备:通过设备名称作为键,存储所有设备实例。
  • List 存储连接关系:记载设备间的连接信息。
  • List 存储命令:生存用户输入的操作指令。
5. 输入处置惩罚


  • 使用字符串解析连接信息和控制命令。
  • 运用了 String.split 方法对输入举行拆分,并对输入格式举行了简单验证。
6. 流式操作与排序


  • 使用 Java 8 的流式操作对设备按名称排序后输出状态,代码简洁高效。
7. 异常处置惩罚与约束


  • 设置电压、电流、亮度等参数时,对输入值举行了范围限制,确保模拟行为的合理性。
源码结构分析

1. 焦点模块


  • Device类:所有设备的抽象基类,定义了设备的通用属性和行为(如输入电压、输出电压、连接关系等)。
  • 子类划分

    • 电源与接地

      • VCC:电路电源,起始设备,负责提供固定电压。
      • Ground:电路接地,停止设备,确保电压差为0。

    • 控制设备

      • Switch:控制电路通断。
      • StepSpeedController:分档调速器。
      • ContinuousSpeedController:连续调速器。

    • 受控设备

      • IncandescentLamp:白炽灯,亮度根据电压线性变化。
      • FluorescentLamp:日光灯,亮度仅两种状态(180lux或0)。
      • Fan:风扇,转速根据电压非线性变化。


2. 电路管理模块


  • Circuit类

    • 负责管理设备聚集和连接关系。
    • 提供设备连接、命令实行、状态输出的功能。

  • 方法计划

    • addDevice:添加设备到聚集。
    • connect:记载设备连接关系。
    • setConnections:根据连接信息建立设备之间的串联关系。
    • executeCommands:实行控制命令,调整设备状态。
    • printStatus:打印设备状态。

3. 主程序模块


  • Main类

    • 处置惩罚用户输入。
    • 调用Circuit类的方法完成电路搭建与操作。

4. 类图计划如下:


以下是程序中几个主要方法的解说,包括它们的功能、实现原理及作用:
1. Circuit.setConnections()

功能:
将所有已添加的设备按照连接关系(connect 方法记载)构建实际的电路。
实现逻辑:

  • 遍历 connections 列表,逐个取出两个引脚之间的连接关系(pin1 和 pin2)。
  • 确定 pin1 对应的设备与 pin2 对应的设备之间的连接:

    • 如果 pin2 是接地设备(Ground),通过 Ground.setPreviousDevice() 设置 pin1 为其上一个设备,并设置电压。
    • 如果是普通设备,则通过设备的 connectTo 方法建立连接。

作用:

  • 将用户输入的电路形貌翻译为程序内部的设备连接关系,为后续电压传递和逻辑控制提供支持。
    关键代码:
  1. for (String[] connection : connections) {
  2.     String pin1 = connection[0].split("-")[0];
  3.     String pin2 = connection[1].split("-")[0];
  4.     if (devices.get(pin2) instanceof Ground) {
  5.         Ground GND = (Ground) devices.get(pin2);
  6.         GND.setPreviousDevice(devices.get(pin1));
  7.     }
  8.     devices.get(pin1).connectTo(devices.get(pin2));
  9. }
复制代码
2. VCC.setVoltage()

功能:
为电路提供电压源,并向下传递电压到连接的下一个设备。
实现逻辑:

  • 直接将电压值 voltage 传递给 nextDevice。
  • 递归更新后续设备的输入电压(通过控制设备或受控设备的逻辑处置惩罚)。
作用:

  • 模拟电路中电压从电源流向各设备的过程,开始整个电路的工作。
    关键代码:
  1. public void setVoltage() {
  2.     this.nextDevice.setInputVoltage(voltage);
  3. }
复制代码
3. Switch.toggleStatus()

功能:
切换开关的状态(开/关)。
实现逻辑:

  • 切换 status 的布尔值。
  • 根据当前状态(true/false)更新开关的输出电压。
  • 输出电压传递给下一个连接的设备(递归传播)。
作用:

  • 提供对开关的根本操作,用户可以通过命令控制电路工作或停止。
    关键代码:
  1. public void toggleStatus() {
  2.     this.status = !this.status; // 切换状态
  3.     updateState();              // 更新设备状态
  4. }
复制代码
4. StepSpeedController.updateState()

功能:
根据档位(level)调整设备的输出电压,并传递给下一个设备。
实现逻辑:

  • 计算当前档位的电压比例(通过 getVoltageRatio 方法)。
  • 根据比例计算输出电压,传递给连接的下一个设备。
  • 如果连接的是开关,需查抄开关状态;否则直接传递电压。
作用:

  • 实现分档调速器对设备输入电压的调节。档位和比例对应实际工程中分段电路计划。
    关键代码:
  1. @Override
  2. void updateState() {
  3.     this.outputVoltage = getVoltageRatio(level) * inputVoltage; // 根据档位比例计算输出电压
  4.     if (nextDevice != null) {
  5.         if (nextDevice instanceof ControlledDevice) {
  6.             ((ControlledDevice) nextDevice).setInputVoltage(outputVoltage);
  7.         } else if (nextDevice instanceof Switch) {
  8.             ((ControlDevice) nextDevice).setInputVoltage(outputVoltage);
  9.         }
  10.     }
  11. }
复制代码
5. Light.updateState()

功能:
更新灯的亮度,根据电位差(dianshicha)计算亮度值并设置。
实现逻辑:

  • 白炽灯亮度根据电位差线性计算:

    • 0~10V:亮度为 0;
    • 10V~220V:线性计算亮度。

  • 日光灯亮度只有两种状态:

    • 电位差为 0:亮度为 0;
    • 电位差不为 0:亮度为 180lux。

作用:

  • 模拟灯光设备的行为(白炽灯和日光灯特性不同),并体现亮度变化与输入电压的关系。
    关键代码(以白炽灯为例):
  1. @Override
  2. protected void updateState() {
  3.     setDianshicha(); // 计算电位差
  4.     int brightness = 0;
  5.     if (dianshicha < 10) {
  6.         brightness = 0;
  7.     } else if (dianshicha > 10 && dianshicha <= 220) {
  8.         double ratio = (dianshicha - 10) / (220 - 10);
  9.         brightness = (int) (50 + ratio * 150);
  10.     }
  11.     this.setBrightness(brightness);
  12. }
复制代码
5. 顺序图计划如下:


踩坑心得

问题 1:建立串联设备之间关系的困难

在电路计划中,设备是串联的(比如 VCC -> 开关 -> 灯),更新设备状态时需要沿着串联关系逐一传播电压,但一开始不清楚如何在程序中表达这种连接关系并递归更新状态。
解决方案:


  • 使用属性连接电路上的设备:

    • 每个设备包含一个 nextDevice 属性,用于指向下一个设备。
    • 更新电压时,递归调用 nextDevice 的更新方法。

  • 计划设备基类的接口:

    • 提供统一的 updateState() 和 connectTo(Device nextDevice) 方法。
    • 子类只需在 updateState() 中实现自己的状态逻辑,无需关心全局串联关系。

实现代码示例:
  1. devices.values().stream()
  2.     .filter(device -> device instanceof Switch)
  3.     .sorted(Comparator.comparing(device -> device.name))
  4.     .forEach(device -> {
  5.         Switch s = (Switch) device;
  6.         System.out.println("@" + s.name + ":" + (!s.status ? "turned on" : "closed"));
  7.     });
复制代码

  • 设备的串联关系通过 connectTo 方法逐步构建。
  • 电压的更新从电源(VCC)向后传播,通过递归调用完成。
问题 2:未精确处置惩罚开关的状态对电压传播的影响

假设电路为:VCC -> 白炽灯 -> 开关。

  • 如果开关关闭,白炽灯的状态不应受电压影响,但在计划中大概直接将 VCC 的电压传递给白炽灯,忽略了开关状态。
  • 开关的状态应决定电压是否能继续传递到后续设备。
问题分析:


  • 忽视开关的断路行为:

    • 开关断开时,应阻断电压传播,但大概在代码中直接递归调用后续设备的 setInputVoltage 方法。

  • 开关行为未被单独抽象:

    • 开关在电路中是特殊的设备,既要管理自己的状态(开/关),又要影响电压的传播。

改进方案:


  • 引入开关逻辑:

    • 开关应判断状态(开/关),仅在开启状态下向下传递电压。

  • 修改电压传播逻辑:

    • 在 Switch.updateState() 方法中,控制是否调用后续设备的 setInputVoltage 方法。

实现代码示例:
  1. abstract class Device {
  2.     protected Device nextDevice;       // 下一个连接设备
  3.     protected double inputVoltage;    // 当前设备输入电压
  4.     public void connectTo(Device nextDevice) {
  5.         this.nextDevice = nextDevice;  // 建立连接
  6.     }
  7.     // 更新设备状态,子类需要重写
  8.     abstract void updateState();
  9.     public void setInputVoltage(double voltage) {
  10.         this.inputVoltage = voltage;
  11.         updateState(); // 根据输入电压更新自身状态
  12.         if (nextDevice != null) {
  13.             nextDevice.setInputVoltage(this.inputVoltage); // 递归传播电压
  14.         }
  15.     }
  16. }
复制代码
改进发起

1. 改进串联设备关系的灵活性


  • 问题: 现有的单向链式结构(nextDevice)难以支持复杂电路拓扑(如并联、环路等)。
  • 发起:

    • 使用图或树结构取代单链表,以便更好地形貌复杂的电路连接关系。
    • 提供统一的连接接口,支持动态添加或移除设备。

2. 设备状态传播逻辑优化


  • 问题: 电压传播逻辑和设备的工作状态强耦合,重复判断开关等设备的状态,逻辑分散。
  • 发起:

    • 引入统一的电源状态校验机制(如 isPowered() 方法),由设备自主决定是否传播电压或更新状态。
    • 将电源开关的逻辑抽象为独立模块,减少其他设备对其内部逻辑的依赖。

3. 增强扩展性


  • 问题: 不同设备的状态更新逻辑分散在各个类中,扩展新设备需要频繁修改焦点代码。
  • 发起:

    • 使用策略模式或配置化计划,将设备的状态更新逻辑解耦为独立模块,便于扩展和维护。
    • 提供统一的设备基类接口,子类仅需实现自己的功能逻辑。

4. 计划上的职责分离


  • 问题: 部分设备(如开关)承担了过多的职责,如既要管理自身状态,还要控制电压传播。
  • 发起:

    • 将职责拆分为更小的模块(如一个专门管理电压传播的控制器)。
    • 开关仅管理自己的开关状态,具体的传播逻辑交由上层处置惩罚。

· 家居强电电路模拟程序 - 2

标题分析


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

圆咕噜咕噜

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表