选课成绩管理系统-BLOG-3-PTA6-8

发布时间 2023-06-28 13:33:15作者: 22201612-刘健涛

目录

(1)前言

(2)设计与分析

(3)踩坑心得

(4)改进建议

(5)总结

正文

(1)前言

题目 知识点 题量(5分为满) 难度(5分为满)
选课1

1.正则表达式的基本使用(选课1要求会读懂)。

2.多次if-else完成业务判断。

3.解析字符串处理一般业务(之后我会总结到正文第五点总结部分)。

4.简单的继承多态,以及基本的组合。

5.自己要会写正则表达式来匹配用户的命令(选课2要求会写正则表达式)(选课3增加了新业务,也需要新的正则表达式)。

6.排序。尤其是按照中文拼音排序(这里只要求简单的中文排序,不包括多音字)

3分 2分
选课2 3.5分 3分
选课3 3分 4分(测试点不好过)

(2)设计与分析

SourceMonitor分析

选课1

整个工程式是极度复杂的,所以复杂度高(Max Complexity),是正常的。即便超过SourceMonitor给的区间范围,但是这是程序的需要。比如操作系统,一些软件,我相信放到代码质量检测工具,也会显示代码最大的复杂度超标。

 

方法量适中,类的数量也是适中的。不会出现什么类爆炸,这种糟糕的现象。

 

有些方法也没有写的冗长冗长的,一条龙解决非常难的问题。而是独立了很多个方法,封装好。调用多种不同的方法来配合地完成一件业务。平均的stmts(AVG statements)或者说深度(AVG Depth)或者说复杂度(AVG complexity)就是最好的体现。

 

SourceMonitor平均代码行数的统计说超过200行以上的只有一个类(字符串解析类ParseInput)。除此之外所有类都在100行一下,其中的一半类都甚至小于50行。

是我的类设计的太简单了吗?我后来想了想,这个类就是这么简单,这个类的最大体现,就差不多100行以下的代码就是最佳的体现了,就存一下信息(data)就行了。

 

这就是我上述对于SM(SourceMonitor)图表的大概解释。

 

 

 

 

选课2

选课3

类图分析

选课1

由题目可知,最基本最主要的类是选课类(SelectCourse),而一门选课要课程(Course)的信息、学生(Student)的信息以及成绩(Score)的信息。由于成绩要分为考察成绩(InspectionScore),和考试成绩(ExaminationScore),就让这个两个成绩继承Score(成绩类)。最后要按照每个班级输出信息,所以还需要一个StudentClass(学生班级类)。

 

其中类的成员就不具体叙述,如图所述。

 

这里又一大亮点就是成绩类,有两个类继承,这就能在选课类中体现多态(SeclectCourse中),以后万一多增加了成绩的类别,就可以把增加的成绩类别继承Score这个类。

 

三次作业类的基本关系没变。就是方法有变化,我将在下面详细叙述。

选课2

选课3

解读代码一(Main类)

查看代码
 public class Main {
    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        String s_record = s.nextLine();
        ParseInput handle=new ParseInput();
        while (!s_record.equals("end")) {
            handle.parseInput(s_record);//解析用户输入的每一行数据
            s_record = s.nextLine();
        }
        handle.showStudents();
        handle.showCourses();
        handle.showClasses();
    }
}

  用户输入对应的一行字符串,就需要解析用户输入的每一行数据,当用户输入end为止就结束。最后按照用户输入输出所有学生对应信息,所有课程对应信息,所有班级对应信息。

解读代码二(ParseInput类的parseInput方法)

查看代码
 public void parseInput(String line) {
    String[] sections = line.split(" ");
    int ln = sections.length;
    if (line.matches(courseInput)) {
        courseManipulating(sections, ln);
    } else if (line.matches(courseInput2)) {
        courseManipulating2(sections, ln);
    } else if (line.matches(scoreInput)) {
        scoreManipulating(sections, ln);
    } else if (line.matches(experimentInput)) {
        experimentManipulating(sections, ln);
    } else {
        System.out.println("wrong format");
    }
}

其中用到的的正则表达式

查看代码
 static String stuNumMatching = "[0-9]{8}";
static String stuNameMatching = "\\S{1,10}";
static String scoreMatching = "(100|[1-9]?[0-9])";
static String experimentMatching = "[4-9]";
static String courseNameMatching = "\\S{1,10}";
static String courseTypeMatching = "(选修|必修|实验)";
static String checkcourseTypeMatching = "(考试|考察|实验)";
static String courseInput = courseNameMatching + " " + courseTypeMatching + " " + checkcourseTypeMatching;
static String courseInput2 = "\\S{1,10} 实验 实验 [4-9][\\S|\\s]*";
static String scoreInput = stuNumMatching + " " + stuNameMatching + " " + courseNameMatching + " " + scoreMatching
			+ " ?" + scoreMatching + "?";
static String experimentInput = "[0-9]{8} \\S{1,10} \\S{1,10}( (100|[1-9]?[0-9]))*";

  将用户输入的字符串,以空格分隔成String数组,以后要用。

  如果用户输入的字符串是有关添加课程的,我将这行字符串转换成对应的数据,进行课程添加。(对应courseManipulating(),courseManipulating2()这两个方法)。

  如果用户输入的字符串树有关学生选课的,我将这行字符串转换成对应的数据,进行学生选课。(对应scoreManipulating(),experimentManipulating()这两个方法)

  因为一般的课程添加用courseManipulating(),而像实验课这种字符串特殊的课程用courseManipulating2()这个方法。所以也就对应不同的正则表达式。

 

一般的课程添加的方法解释(courseManipulating)

查看代码
 private void courseManipulating(String[] sections, int ln) {
    String courseName = sections[0];
    String courseType = sections[1];
    String courseAccessMode = null;
    if (ln == 3) {
        courseAccessMode = sections[2];
    } else {
        courseAccessMode = "考试";
    }
    if (null == Course.find(listCourses, courseName)) {
        boolean examinationCourseWrong = "必修".equals(courseType)
            && ("考察".equals(courseAccessMode) || "实验".equals(courseAccessMode));
        boolean inspectationCourseWrong = "选修".equals(courseType) && "实验".equals(courseAccessMode);
        boolean experimentCourseWrong = "实验".equals(courseType)
            && ("考试".equals(courseAccessMode) || "考察".equals(courseAccessMode));
        if (examinationCourseWrong || inspectationCourseWrong || experimentCourseWrong) {
            System.out.println(courseName + " : course type & access mode mismatch");
        } else {
            Course course = new Course(courseName, courseType, courseAccessMode);
            listCourses.add(course);
        }
    }
}

   首先先处理一下用户输入的课程是以什么方式通过,是考试还是考察?,如果字符串只能被空格拆分成两个的话,课程的通过方式就一定要是考试(根据题目的意思)。

  之后看到我们从listCourses课程列表根据课程名字中找到课程,如果找不到课程就说明这个以这个课程名字的课程没有被添加过,if条件为真,进行下面语句。

   如果课程类型(必修 vs 选修)与课程通过方式(只能考试 vs 只能考试或者考察)不匹配的话,就输出错误,提示用户出错。如果课程类型与课程通过方式匹配的话,进行下面语句。

  创建(new)一个课程(Course),并添加到课程列表(listCourses)。

 

实验课程添加的方法解释(courseManipulating2)

查看代码
 private void courseManipulating2(String[] sections, int ln) {
    String courseName = sections[0];
    String courseType = sections[1];
    String courseAccessMode = sections[2];
    int experimentTimes = Integer.parseInt(sections[3]);
    if (ln != experimentTimes + 4) {
        System.out.println(courseName + " : number of scores does not match");
    } else {
        double importanceSum = 0;
        Vector<Double> everyImportance = new Vector<>();
        for (int i = 0; i < experimentTimes; i++) {
            int index = i + experimentTimes;
            double parseDouble = Double.parseDouble(sections[index]);
            importanceSum += parseDouble;
            everyImportance.add(parseDouble);
        }
        if (0 != Double.compare(importanceSum, 1.0)) {
            System.out.println(courseName + " : weight value error");
        } else {
            Course course = new Course(courseName, courseType, courseAccessMode);
            course.addExperimentImportance(everyImportance);
            course.addExperimentTimes(experimentTimes);
            if (!listCourses.contains(course)) {
                listCourses.add(course);
            }
        }
    }
}

   首先实验课程的添加要根据实验的次数,对每一次实验赋予权重,所以一开始判断用户输入的实验次数,和给的权重个数是否匹配。如果匹配,进行下面语句。

  每一个权重都记录到一个list列表里面(我这里用的是Vector向量),最后计算权重的总和。如果权重之和不等于1,就输出权重和不等于1,输出错误。如果权重和等于1,进行下面语句。

  如果课程列表不存在这门课程就添加这么实验课,这样就不会出现重复的课程。

 

 

学生选课添加的方法解释(scoreManipulating,是除了实验课的学生选课)

查看代码
 private void scoreManipulating(String[] sections, int ln) {
    String studentId = sections[0];
    String studentName = sections[1];
    String studentCourse = sections[2];
    String classNumber = studentId.substring(0, 6);
    Student student = judgeStudentIsNew_andAddStudent(studentId, studentName, studentCourse, classNumber);

    Course course = Course.find(listCourses, studentCourse);
    String accessMode = course.getCourseAccessMode(listCourses, studentCourse);

    if ("考试".equals(accessMode)) {
        if (5 == ln) {
            int usualPerformance = Integer.parseInt(sections[3]);
            int finalGrade = Integer.parseInt(sections[4]);
            if ((usualPerformance >= 0 && usualPerformance <= 100) || (finalGrade >= 0 && finalGrade <= 100)) {
                Score score = new ExaminationScore(usualPerformance, finalGrade);
                SelectCourse selectCourse = new SelectCourse(course, student, score);
                add(selectCourse);
            }
        } else {
            System.out.println(studentId + " " + studentName + " : access mode mismatch");
        }
    } else if ("考察".equals(accessMode)) {
        if (4 == ln) {
            int finalGrade = Integer.parseInt(sections[3]);
            if (finalGrade <= 100 && finalGrade >= 0) {
                Score score = new InspectationScore(finalGrade);
                SelectCourse selectCourse = new SelectCourse(course, student, score);
                add(selectCourse);
            }
        } else {
            System.out.println(studentId + " " + studentName + " : access mode mismatch");
        }
    } else if (null == accessMode) {
        System.out.println(studentCourse + " does not exist");
    }
}
private Student judgeStudentIsNew_andAddStudent(String studentId, String studentName, String studentCourse,String classNumber) {
    boolean studentEnterClass = (null != Student.find_fromClasses(listStudentClasses, studentId)) ? true : false;
    boolean classExsitList = (null != StudentClass.find(listStudentClasses, classNumber)) ? true : false;
    boolean studentExistList = (null != Student.find_fromStudents(listStudents, studentId)) ? true : false;
    Student student = new Student(studentId, studentName, studentCourse);
    judgeIsNew(classNumber, studentEnterClass, classExsitList, studentExistList, student);
    return student;
}
private void judgeIsNew(String classNumber, boolean studentEnterClass, boolean classExsitList,boolean studentExistList, Student student) {
    if (!classExsitList) {
        StudentClass studentClass = new StudentClass(classNumber);
        add(studentClass);
    }
    if (!studentExistList) {
        add(student);
    }
    if (!studentEnterClass) {
        StudentClass studentClass = StudentClass.find(listStudentClasses, classNumber);
        studentClass.add(student);
    }
}

   学生选课,首先要判断这个学生是不是新的,有没有被添加到学生列表(listStudents)呢?所以要用到judgeStudentIsNew_andAddStudent()方法,而这个方法又会调用judgeIsNew()方法,所以下面两个自然段就来介绍这两个方法。

  judgeStudentIsNew_andAddStudent()方法:首先判断这个学生有没有进入班级,即学生是否存在班级列表中(listStudentClasses),这个班级是否存在在班级列表中(listStudentClasses),学生是否存在学生列表中呢?最后进入JudgeIsNew()方法中。

  judgeIsNew()方法:如果学生班级不存在在班级列表,就创建一个新的放进列表。如果学生不存在在学生列表,就创建一个新的放进列表。如果本来是在这个班级的学生不存在在对应的学生班级,就将学生放进对应的班级中(为什么能将学生放入学生班级呢?是因为学生班级[studentClass]里面有容器可以承装[Student])。

  执行完judgeStudentIsNew_andAddStudent()方法后,进行下面语句。如果学生选的课的课程通过方式是考试的话,就解析成选课添加到选课列表(listSelectCourse)。如果学生选的课的课程通过方式是考察的话,就解析成选课添加到选课列表(listSelectCourse)。如果学生选的课的课程通过方式是空的话,就是没有根据课程名称找到对应的课程,输出这个课程不存在,让用户知道错误。

  这就是除了实验课的选课的添加。

 

学生选课添加的方法解释(学生选的是实验课,experimentManipulating)

查看代码
 private void experimentManipulating(String[] sections, int ln) {
    String studentId = sections[0];
    String studentName = sections[1];
    String studentCourse = sections[2];
    String classNumber = studentId.substring(0, 6);
    Student student = judgeStudentIsNew_andAddStudent(studentId, studentName, studentCourse, classNumber);
    Course course = Course.find(listCourses, studentCourse);
    String courseAccessMode = Course.getCourseAccessMode(listCourses, studentCourse);
    if (!"实验".equals(courseAccessMode)) {
        System.out.println(studentCourse + " : course type & access mode mismatch");
        return;
    }
    int expermentTimes = course.experimentTimes;
    int neededLength = 3 + expermentTimes;
    if (neededLength != ln) {
        System.out.println(studentId + " " + studentName + " : access mode mismatch");
    } else {
        Vector<Integer> everyGrade = new Vector<>();
        for (int i = 0; i < expermentTimes; i++) {
            int idx = 3 + i;
            int score = Integer.parseInt(sections[idx]);
            everyGrade.add(score);
        }
        Vector<Double> everyImportance = course.everyImportance;
        ExperimentScore experimentScore = new ExperimentScore(expermentTimes, everyGrade, everyImportance);
        SelectCourse selectCourse = new SelectCourse(course, student, experimentScore);
        listSelectCourses.add(selectCourse);
    }
}
private Student judgeStudentIsNew_andAddStudent(String studentId, String studentName, String studentCourse,String classNumber) {
上文已经讲过了。
}

  如果学生选的课程的课程通过方式不是实验,就输出课程类型和课程通过类型不匹配。如果匹配的话,进行下面语句。

  如果学生实验分数的个数不等于实验次数的话,就输出学生课程通过不匹配。如果这个if语句通过,进行下面语句。

  将每次实验分数读到,放入容器,后准备好应有的成员变量,组合成选课(selectCourse),放入选课列表(listSelectCourses)

 

解读代码三(parseInput类中的showStudents,showCourses,showClasses方法)

查看代码
 public void showStudents() {
    listStudents.sort((o1, o2) -> {
        String sid1 = o1.studentId;
        String sid2 = o2.studentId;
        return sid1.compareTo(sid2);
    });
    for (Student ck : listStudents) {
        Vector<Score> score = Student.findScore(listSelectCourses, ck);
        if (0 == score.size()) {
            String sid = ck.studentId;
            String sname = ck.stuName;
            System.out.println(sid + " " + sname + " did not take any exams");
        } else {
            Student.calculateScore(ck);
            String sid = ck.studentId;
            String sname = ck.stuName;
            int sscore = ck.averageTotalScore;
            System.out.println(sid + " " + sname + " " + sscore);
        }
    }
}

  输出学生相关信息,需要对学生的学号(id)进行排序。学生的学号没有中文,只有数字,是字符串的类型。可以直接使用字符串的比较方法,就能学生按照学号升序排序。如果需要降序,直接加个负号加个括号就行。排序完后,执行下面代码。

  将学生每个都取出来,在选课列表中找到这个学生有的选课,并把这个学生的每一个选课的成绩都取出来。放入成绩列表(score)。执行完这条语句,进行下面代码。

  如果成绩列表的长度为0,就说明这个学生压根就没有成绩,所以输出这个学生没有考过任何一场考试。如果学生有成绩的话,进行下面代码。

  对学生进行计算成绩,最后输出。因为一个学生可能有多门课,calculateScore就是来计算学生多门课最后的最终总期末成绩,并输出。

  这样就完成了学生信息的输出。

 

查看代码
 public void showCourses() {
    Comparator<Object> CHINA_COMPARE = Collator.getInstance(java.util.Locale.CHINA);
    listCourses.sort((o1, o2) -> ((Collator) CHINA_COMPARE).compare(o1.courseName, o2.courseName));
    for (Course ck : listCourses) {
        Vector<SelectCourse> need = new Vector<>();
        String courseName = ck.courseName;
        String courseAccessMode = ck.courseAccessMode;
        Course.findNeed(ck, need, listSelectCourses);

        if (0 == need.size()) {
            System.out.println(courseName + " has no grades yet");
        } else {
            Course.calculateScore(ck, need);
            if ("考察".equals(courseAccessMode)) {
                InspectationScore score = (InspectationScore) ck.score;
                int finalGradeAverage0 = score.finalGrade;
                int totalGradeAverage0 = score.totalGrade;
                System.out.println(courseName + " " + finalGradeAverage0 + " " + totalGradeAverage0);
            } else if ("考试".equals(courseAccessMode)) {
                ExaminationScore score = (ExaminationScore) ck.score;
                int usualPerfomanceAverage0 = score.usualPerformance;
                int finalGradeAverage0 = score.finalGrade;
                int totalGradeAverage0 = score.totalGrade;
                System.out.println(courseName + " " + usualPerfomanceAverage0 + " " + finalGradeAverage0 + " "
                                   + totalGradeAverage0);
            } else if ("实验".equals(courseAccessMode)) {
                ExperimentScore score = (ExperimentScore) ck.score;
                int totalGradeAverage0 = score.totalGrade;
                System.out.println(courseName + " " + totalGradeAverage0);
            }
        }
    }
}

  输出课程相关信息,需要对课程进行按拼音排序。因为对于