一.前言:
在学习过C语言之后,面向对象的程序设计在本学期如期开启。该课程的编程语言是java,java与所学过的C语言有诸多相似之处,与C语言课程所不同的是,这门课程注重的是面向对象,如果说C语言是语法的学习,那么java就是其实战应用的学习,这门课的学习更让我深刻的感受到比写代码更重要的事情,这便是结构的设计。
三次训练题进行了两次迭代,在一次一次的迭代中,设计结构的问题愈发明显。这促使我们对完成代码的结构进行思考和修正,是否遵循面向对象程序设计的几大原则,其中单一职责原则是否履行是本阶段训练最大的问题,总是将多种职责赋予某一类进行处理,亦或者交予主类处理.这三次的题目从难度而言的确难度不大,主要考察对于类的理解与应用,但是由于对java语言的生疏和C语言学习过程中养成的不良习惯为这三次的训练平添了不小的难度。
二.设计与分析:
* 1.第一次题目集:
设计实现答题程序,模拟一个小型的测试,要求输入题目信息和答题信息,根据输入题目信息中的标准答案判断答题的结果。
输入格式:
程序输入信息分三部分:
1、题目数量
格式:整数数值,若超过1位最高位不能为0,
样例:34
2、题目内容
一行为一道题,可以输入多行数据。
格式:"#N:"+题号+" "+"#Q:"+题目内容+" "#A:"+标准答案
格式约束:题目的输入顺序与题号不相关,不一定按题号顺序从小到大输入。
样例:#N:1 #Q:1+1= #A:2
#N:2 #Q:2+2= #A:4
3、答题信息
答题信息按行输入,每一行为一组答案,每组答案包含第2部分所有题目的解题答案,答案的顺序号与题目题号相对应。
格式:"#A:"+答案内容
格式约束:答案数量与第2部分题目的数量相同,答案之间以英文空格分隔。
样例:#A:2 #A:78
2是题号为1的题目的答案
78是题号为2的题目的答案
答题信息以一行"end"标记结束,"end"之后的信息忽略。
输出格式:
1、题目数量
格式:整数数值,若超过1位最高位不能为0,
样例:34
2、答题信息
一行为一道题的答题信息,根据题目的数量输出多行数据。
格式:题目内容+" ~"+答案
样例:1+1=~2
2+2= ~4
3、判题信息
判题信息为一行数据,一条答题记录每个答案的判断结果,答案的先后顺序与题目题号相对应。
格式:判题结果+" "+判题结果
格式约束:
1、判题结果输出只能是true或者false,
2、判题信息的顺序与输入答题信息中的顺序相同
样例:true false true
我的代码如下:
Question类(问题类):
class Question {
private int num;
private String content;
private String answer;
public Question(int num, String content, String answer) {
this.num = num;
this.content = content;
this.answer = answer;
}
public int getNum() {
return num;
}
public String getContent() {
return content;
}
public String getAnswer() {
return answer;
}
public boolean judgeAnswer(String userAnswer) {
return answer.equals(userAnswer);
}
}
Exampaper类(试卷类):
class ExamPaper {
private List<Question> questions;
public ExamPaper() {
questions = new ArrayList<>();
}
public void addQuestion(Question question) {
questions.add(question);
}
public int getNumQuestions() {
return questions.size();
}
public Question getQuestion(int num) {
for (Question question : questions) {
if (question.getNum() == num) {
return question;
}
}
return null;
}
public boolean judgeAnswer(int num, String userAnswer) {
Question question = getQuestion(num);
if (question != null) {
return question.judgeAnswer(userAnswer);
}
return false;
}
}
AnswerSheet类(答卷类):
class AnswerSheet {
private List<String> answers;
public AnswerSheet() {
answers = new ArrayList<>();
}
public void addAnswer(String answer) {
answers.add(answer);
}
public List<String> getAnswers() {
return answers;
}
}
main类(主类):
public class Main{
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int numQuestions = Integer.parseInt(scanner.nextLine().trim());
ExamPaper examPaper = new ExamPaper();
for (int i = 0; i < numQuestions; i++) {
String line = scanner.nextLine().trim();
String[] parts = line.split("#N:| #Q:| #A:");
int num = Integer.parseInt(parts[1].trim());
String content = parts[2].trim();
String answer = parts[3].trim();
examPaper.addQuestion(new Question(num, content, answer));
}
AnswerSheet answerSheet = new AnswerSheet();
while (scanner.hasNextLine()) {
String line = scanner.nextLine().trim();
if (line.equals("end")) {
break;
}
String[] answers = line.split(" ");
for (String answer : answers) {
answerSheet.addAnswer(answer.substring(3));
}
}
for (int i = 1; i <= numQuestions; i++) {
Question question = examPaper.getQuestion(i);
String userAnswer = answerSheet.getAnswers().get(i - 1);
boolean judgeResult = question.getAnswer().equals(userAnswer);
System.out.println(question.getContent() + "~" + userAnswer);
System.out.println(judgeResult ? "true" : "false");
}
}
}
类图如下:
圈复杂度如下:
题目分析:
本题需要制作一个判题系统,将其划分为问题类、试卷类、答卷类,问题类中包含问题的属性以及判断问题答案真假的方法,试卷类中包含试卷属性和标准答案的判断方法,答卷类中包含输入答案的属性和判断。初见这道题是很懵的,也没有取得一个满意的的分数,但是在一次次修改中逐渐理解了不同类之间构建关系的方式。
踩坑心得:
- 对于输入内容的判断最初试图采用if判断
#N:
、#Q:
、#A:
其后的内容导致代码过于冗长且无意义而后采用数组的形式对三个部分进行切割储存即采用spilt
方法 - Java语言的语言精确度要求极高,对于大小写要求很细致,因为大小写问题导致了过多的报错
- 虽然能沟通过检查点但是结构存在明显问题,输入信息处理方法放在主类中,应当单独设置一个判断类对输入的数据进行处理,保证单一职责,而不是将过多的方法堆滞在主类中
- 按照输出格式输出时切记在同一行中输出最后一个字符后没有空格千万不要多打空格,可以单独对末尾字符判断一次去掉空格
———————————————————————————————————————————
* 第二次题目集:
第二次题目于第一次基础上做出增加
2、试卷信息
一行为一张试卷,可输入多行数据(多张卷)。
格式:"#T:"+试卷号+" "+题目编号+"-"+题目分值
题目编号应与题目信息中的编号对应。
一行信息中可有多项题目编号与分值。
样例:#T:1 3-5 4-8 5-2
3、答卷信息
答卷信息按行输入,每一行为一张答卷的答案,每组答案包含某个试卷信息中的题目的解题答案,答案的顺序与试卷信息中的题目顺序相对应。
格式:"#S:"+试卷号+" "+"#A:"+答案内容
格式约束:答案数量可以不等于试卷信息中题目的数量,没有答案的题目计0分,多余的答案直接忽略,答案之间以英文空格分隔。
样例:#S:1 #A:5 #A:22
1是试卷号
5是1号试卷的顺序第1题的题目答案
22是1号试卷的顺序第2题的题目答案
答题信息以一行"end"标记结束,"end"之后的信息忽略。
输出格式:
1、试卷总分警示
该部分仅当一张试卷的总分分值不等于100分时作提示之用,试卷依然属于正常试卷,可用于后面的答题。如果总分等于100分,该部分忽略,不输出。
格式:"alert: full score of test paper"+试卷号+" is not 100 points"
样例:alert: full score of test paper2 is not 100 points
2、答卷信息
一行为一道题的答题信息,根据试卷的题目的数量输出多行数据。
格式:题目内容+""+答案++""+判题结果(true/false)
约束:如果输入的答案信息少于试卷的题目数量,答案的题目要输"answer is null"
样例:3+2=5true
4+6=~22~false.
answer is null
3、判分信息
判分信息为一行数据,是一条答题记录所对应试卷的每道小题的计分以及总分,计分输出的先后顺序与题目题号相对应。
格式:题目得分+" "+....+题目得分+"~"+总分
格式约束:
1、没有输入答案的题目计0分
2、判题信息的顺序与输入答题信息中的顺序相同
样例:5 8 0~13
根据输入的答卷的数量以上2、3项答卷信息与判分信息将重复输出。
4、提示错误的试卷号
如果答案信息中试卷的编号找不到,则输出”the test paper number does not exist”
我的代码如下:
问题类:
class Question {
private int number;
private String content;
private String answer;
public Question(int number, String content, String answer) {
this.number = number;
this.content = content;
this.answer = answer;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getAnswer() {
return answer;
}
public void setAnswer(String answer) {
this.answer = answer;
}
}
试卷类:
class TestPaper {
private int paperNumber;
private Map<Integer, Integer> questions;
public TestPaper(int paperNumber) {
this.paperNumber = paperNumber;
this.questions = new HashMap<>();
}
public void addQuestion(int questionNumber, int score) {
questions.put(questionNumber, score);
}
public int getPaperNumber() {
return paperNumber;
}
public void setPaperNumber(int paperNumber) {
this.paperNumber = paperNumber;
}
public Map<Integer, Integer> getQuestions() {
return questions;
}
public void setQuestions(Map<Integer, Integer> questions) {
this.questions = questions;
}
}
答卷类:
class AnswerSheet {
private int paperNumber;
private List<String> answers;
public AnswerSheet(int paperNumber) {
this.paperNumber = paperNumber;
this.answers = new ArrayList<>();
}
public void addAnswer(String answer) {
answers.add(answer);
}
public int getPaperNumber() {
return paperNumber;
}
public void setPaperNumber(int paperNumber) {
this.paperNumber = paperNumber;
}
public List<String> getAnswers() {
return answers;
}
public void setAnswers(List<String> answers) {
this.answers = answers;
}
}
主类:
public class Main {
public static void main(String[] args) {
List<String> inputs = new ArrayList<>();
Scanner scanner = new Scanner(System.in);
String input;
while (!(input = scanner.nextLine()).equals("end")) {
inputs.add(input);
}
Collections.shuffle(inputs);
List<Question> questions = new ArrayList<>();
Map<Integer, TestPaper> testPapers = new HashMap<>();
List<AnswerSheet> answerSheets = new ArrayList<>();
for (String inputLine : inputs) {
if (inputLine.startsWith("#N:")) {
String[] parts = inputLine.split(" ");
int number = Integer.parseInt(parts[0].substring(3));
String content = parts[1].substring(3);
String answer = parts[2].substring(3);
questions.add(new Question(number, content, answer));
} else if (inputLine.startsWith("#T:")) {
String[] parts = inputLine.split(" ");
int paperNumber = Integer.parseInt(parts[0].substring(3));
TestPaper testPaper = new TestPaper(paperNumber);
for (int i = 1; i < parts.length; i++) {
String[] pair = parts[i].split("-");
int questionNumber = Integer.parseInt(pair[0]);
int score = Integer.parseInt(pair[1]);
testPaper.addQuestion(questionNumber, score);
}
testPapers.put(paperNumber, testPaper);
} else if (inputLine.startsWith("#S:")) {
String[] parts = inputLine.split(" ");
int paperNumber = Integer.parseInt(parts[0].substring(3));
AnswerSheet answerSheet = new AnswerSheet(paperNumber);
for (int i = 1; i < parts.length; i++) {
answerSheet.addAnswer(parts[i].substring(3));
}
answerSheets.add(answerSheet);
}
}
for (AnswerSheet answerSheet : answerSheets) {
int totalScore = 0;
TestPaper testPaper = testPapers.get(answerSheet.getPaperNumber());
List<String> answers = answerSheet.getAnswers();
List<Integer> scores = new ArrayList<>();
for (int i = 0; i < answers.size(); i++) {
String answer = answers.get(i);
int questionNumber = i + 1;
if (testPaper.getQuestions().containsKey(questionNumber)) {
Question question = findQuestionByNumber(questions, questionNumber);
if (question != null && answer.equals(question.getAnswer())) {
totalScore += testPaper.getQuestions().get(questionNumber);
scores.add(testPaper.getQuestions().get(questionNumber));
} else {
scores.add(0);
}
}
}
int fullScore = testPaper.getQuestions().values().stream().mapToInt(Integer::intValue).sum();
if (fullScore != 100) {
System.out.println("alert: full score of test paper" + answerSheet.getPaperNumber() + " is not 100 points");
}
for (int i = 0; i < answers.size();) {
String answer = answers.get(i);
i++;
int questionNumber = i ;
if (testPaper.getQuestions().containsKey(questionNumber)) {
Question question = findQuestionByNumber(questions, questionNumber);
if (question != null) {
System.out.println(question.getContent() + "~" + answer + "~" + (answer.equals(question.getAnswer())));
}
}
}
for (int i = 0; i < scores.size(); i++) {
System.out.print(scores.get(i));
if (i != scores.size() - 1) {
System.out.print(" ");
}
}
System.out.println("~" + totalScore);
}
}
private static Question findQuestionByNumber(List<Question> questions, int number) {
for (Question question : questions) {
if (question.getNumber() == number) {
return question;
}
}
return null;
}
}
类图如下:
圈复杂度如下:
题目分析:
第二题中对试卷类的信息进行了更加精细化的格式要求,且对于总分值加以判断确保数值的合理性,同样还是以前面提到的三个类进行解答,将判断与输出放到主类中进行准确的判断,但是由于未能拆分出更多类导致结构过于局限且死板,有违背单一职责的风险,代码仍需大幅度的优化。
踩坑心得:
- 注意总分判断输出语句的位置,要在第一行输出,需要将其前置
- 判题信息放到放到答题结果后方,需要注意判断true或false后空格问题,不要携带空格
- 允许乱序排列的话可以单独设计排列类进行数据的排列处理(但是我没作到)
———————————————————————————————————————————
* 第三次习题集:
在习题集二的基础上增加了学生信息、删除题目信息部分情况如下
1、学生信息
学生信息只输入一行,一行中包括所有学生的信息,每个学生的信息包括学号和姓名,格式如下。
格式:"#X:"+学号+" "+姓名+"-"+学号+" "+姓名....+"-"+学号+" "+姓名
格式约束:
答案数量可以不等于试卷信息中题目的数量,没有答案的题目计0分,多余的答案直接忽略,答案之间以英文空格分隔。
样例:
#S:1 #A:5 #A:22
1是试卷号
5是1号试卷的顺序第1题的题目答案
2、删除题目信息
删除题目信息为独行输入,每一行为一条删除信息,多条删除信息可分多行输入。该信息用于删除一道题目信息,题目被删除之后,引用该题目的试卷依然有效,但被删除的题目将以0分计,同时在输出答案时,题目内容与答案改为一条失效提示,例如:”the question 2 invalid~0”
格式:"#D:N-"+题目号
格式约束:
题目号与第一项”题目信息”中的题号相对应,不是试卷中的题目顺序号。
本题暂不考虑删除的题号不存在的情况。
样例:
(每一行首个*忽略即可)
*#N:1 #Q:1+1= #A:2
*##N:2 #Q:2+2= #A:4
*#T:1 1-5 2-8
*#X:20201103 Tom-20201104 Jack
*#S:1 20201103 #A:1-5 #A:2-4
*#D:N-2
end
输出
alert: full score of test paper1 is not 100 points
1+1=5false
the question 2 invalid~0
20201103 Tom: 0 0~0
答题信息以一行"end"标记结束,"end"之后的信息忽略。
我的代码如下:
问题类:
class Question {
private int questionNum;
private String questionContent;
private String questionAnswer;
public Question(int questionNum, String questionContent, String questionAnswer) {
this.questionNum = questionNum;
this.questionContent = questionContent;
this.questionAnswer = questionAnswer;
}
public int getquestionNum() {
return questionNum;
}
public void setquestionNum(int questionNum) {
this.questionNum = questionNum;
}
public String getquestionContent() {
return questionContent;
}
public void setquestionContent(String questionContent) {
this.questionContent = questionContent;
}
public String getquestionAnswer() {
return questionAnswer;
}
public void setquestionAnswer(String questionAnswer) {
this.questionAnswer = questionAnswer;
}
}
试卷类:
class TestPaper {
private int paperNum;
private Map<Integer, Integer> questions;
public TestPaper(int paperNum) {
this.paperNum = paperNum;
this.questions = new HashMap<>();
}
public void addQuestion(int paperNum, int score) {
questions.put(paperNum, score);
}
public int getpaperNum() {
return paperNum;
}
public void setpaperNum(int paperNum) {
this.paperNum = paperNum;
}
public Map<Integer, Integer> getQuestions() {
return questions;
}
public void setQuestions(Map<Integer, Integer> questions) {
this.questions = questions;
}
}
答卷类:
class AnswerSheet {
private int paperNum;
private List<String> answers;
public AnswerSheet(int paperNum) {
this.paperNum = paperNum;
this.answers = new ArrayList<>();
}
public void addAnswer(String answer) {
answers.add(answer);
}
public int getpaperNum() {
return paperNum;
}
public void setpaperNum(int paperNum) {
this.paperNum = paperNum;
}
public List<String> getAnswers() {
return answers;
}
public void setAnswers(List<String> answers) {
this.answers = answers;
}
}
学生类:
class Student {
private String sid;
private String name;
public Student() {
}
public Student(String sid, String name) {
this.sid = sid;
this.name = name;
}
public String getSid() {
return sid;
}
public void setSid(String sid) {
this.sid = sid;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
主类:
public class Main {
public static void main(String[] args) {
List<String> inputs = new ArrayList<>();
Scanner scanner = new Scanner(System.in);
String input;
while (!(input = scanner.nextLine()).equals("end")) {
inputs.add(input);
}
Collections.shuffle(inputs);
List<Question> questions = new ArrayList<>();
Map<Integer, TestPaper> testPapers = new HashMap<>();
List<AnswerSheet> answerSheets = new ArrayList<>();
for (String inputLine : inputs) {
if (inputLine.startsWith("#N:")) {
String[] parts = inputLine.split(" ");
int questionNum = Integer.parseInt(parts[0].substring(3));
String questionContent = parts[1].substring(3);
String questionAnswer = parts[2].substring(3);
questions.add(new Question(questionNum, questionContent, questionAnswer));
}
else if (inputLine.startsWith("#T:")) {
String[] parts = inputLine.split(" ");
int paperNum = Integer.parseInt(parts[0].substring(3));
TestPaper testPaper = new TestPaper(paperNum);
for (int i = 1; i < parts.length; i++) {
String[] pair = parts[i].split("-");
int questionNum = Integer.parseInt(pair[0]);
int score = Integer.parseInt(pair[1]);
testPaper.addQuestion(questionNum, score);
}
testPapers.put(paperNum, testPaper);
}
else if(inputLine.startsWith("#X:")){
String[] parts = inputLine.split(" ");
int sid = Integer.parseInt(parts[0].substring(10));
String name = parts[1];
Student student = new Student(Integer.toString(sid), name);
}
else if (inputLine.startsWith("#S:")) {
String[] parts = inputLine.split(" ");
int paperNum = Integer.parseInt(parts[0].substring(3));
AnswerSheet answerSheet = new AnswerSheet(paperNum);
for (int i = 1; i < parts.length; i++) {
answerSheet.addAnswer(parts[i].substring(3));
}
answerSheets.add(answerSheet);
}
}
for (AnswerSheet answerSheet : answerSheets) {
int totalScore = 0;
TestPaper testPaper = testPapers.get(answerSheet.getpaperNum());
List<String> answers = answerSheet.getAnswers();
List<Integer> scores = new ArrayList<>();
for (int i = 0; i < answers.size(); i++) {
String answer = answers.get(i);
int questionNum = i + 1;
if (testPaper.getQuestions().containsKey(questionNum)) {
Question question = findQuestionByNumber(questions, questionNum);
if (question != null && answer.equals(question.getquestionAnswer())) {
totalScore += testPaper.getQuestions().get(questionNum);
scores.add(testPaper.getQuestions().get(questionNum));
} else {
scores.add(0);
}
}
}
int fullScore = testPaper.getQuestions().values().stream().mapToInt(Integer::intValue).sum();
if (fullScore != 100) {
System.out.println("alert: full score of test paper" + answerSheet.getpaperNum() + " is not 100 points");
}
for (int i = 0; i < answers.size();i++) {
String answer = answers.get(i);
int questionNum = i+1 ;
if (testPaper.getQuestions().containsKey(questionNum)) {
Question question = findQuestionByNumber(questions, questionNum);
if (question != null) {
System.out.println(question.getquestionContent() + "~" + answer + "~" + (answer.equals(question.getquestionAnswer())));
}
}
}
for (int i = 0; i < scores.size(); i++) {
System.out.print(scores.get(i));
if (i != scores.size() - 1) {
System.out.print(" ");
}
}
System.out.println("~" + totalScore);
}
}
private static Question findQuestionByNumber(List<Question> questions, int questionNum) {
for (Question question : questions) {
if (question.getquestionNum() == questionNum) {
return question;
}
}
return null;
}
}
类图如下:
圈复杂度如下:
题目分析:
第三题在第二次的基础上补充添加了学生信息与删除信息,二者应当分别处理为学生类(Student
类)和删除类(Delete
类),在添加这两大类之后再对输出情况进行调整,判断对应符号进行处理。
踩坑心得
- 需注意输出判分信息时name:的冒号后有一个空格,然后接判分信息
- 由于输入学生信息可以是多名学生信息故而需要注意判断学生信息的间隔
- 题目要求需要新添加一个题目失效信息不要忽略掉
三.改进建议
- 经过三次训练可以清晰地认识到处理字符时单纯依靠判断语句的复杂程度,极其容易产生垃圾代码(重复度过高),故而应当学习使用正则表达式进行处理,可以极大地简化字符处理问题
- 在使用方法时注意到大部分方法都放在了主类里,需要把主类的方法都转移到新建的其他类中,要实现单一职责,如将字符判断设置为判断类,将字符连接设置为连接类,如此对代码进行优化整理,使结构层次更加清晰
- java代码方法众多,要保持持续学习的状态接触更多案例才能灵活应对面临的各种难题,辅以大量的练习(即刷题)才能真正掌握好这项技能
四.总结
这三次作业客观而言难度不大但是在完成过程中遇到了诸多问题,例如清楚前后空格的方法(.equal
),切割保存的方法(.split
)。但是在多方面查询资料结果后问题也迎刃而解。在三次层层递进的训练中,对于“类”这一定义的理解更加清晰,他对于结构的优化功能不可限量,熟练掌握类是Java学习的过程中必不可少的一步。同时“面向对象”这一理念深入人心,外部输入对象的信息我们要处理对象的信息,为此结构的设计变得尤为关键,希望老师可以多讲解一些设计的想法,借此打开同学们的思路,使大家更好的更深入的投入这门课程的学习之中。