HNU 个人项目互评
一、简介
本篇博客为对结对编程队友蒋云涛同学的个人项目(中小学数学卷子自动生成程序的)的评价。该项目使用的编程语言为java,
基于了面向对象的思路,与本人项目所用编程语言一致。为了更好的实现结对项目的功能,在评价蒋同学的代码的同时,也不断
学习他代码的优点,总结反思,共同进步。
二、项目结构
-
User类
// 类的成员变量
private String type; // 小学/初中/高中
private String name; // 姓名
static HashMap<String, String> UserLib = new HashMap<>(); // 存储项目中的9个账户对应的账号和密码,例如 UserLib.put("张三1", "123");
static HashMap<String, String> UserType = new HashMap<>(); // 存储项目中的9个账户对应的账号和类型,例如 UserType.put("张三1", "小学");
该类共有三个方法 仅展示代码的关键部分
// 用户登陆方法,检查用户账号和密码,无误后调用paraSet()方法,进行试卷参数设定
public void logIn() {
···
while (true) {
String[] input = in.nextLine().split("\\s+");
if (input.length < 2) {//输入少于两个字段
System.out.println("----请输入正确的用户名、密码:----");
continue;
}
name = input[0];
String passWord = input[1];
if (!UserLib.containsKey(name)) {
System.out.println("----无此用户----");
continue;
} else if (!UserLib.get(name).equals(passWord)) {
System.out.println("----用户密码错误,请重试----");
continue;
}
type = UserType.get(name);
break;
}
···
可以看到,蒋同学使用的登录方法是接收姓名和密码的字符串,然后利用hashmap容器,检验是否存有该用户。
// 参数设定方法,检查用户输入参数,无误后调用paperMake()方法,进行完整试卷生成
public void paraSet() {
···
while (true) {
num = in.nextInt();
if (num == -1) {
System.out.println("----即将重新登录----");
logIn();
return;
} else if (num < 10 || num > 30) {
System.out.println("----题目数量的有效输入范围是“10-30”(含10,30,或-1退出登录)----");
System.out.println("----请重新输入:----");
} else {
break;
}
}
Paper paper;
if (type.equals("小学"))
paper = new PriPaper();
else if (type.equals("初中"))
paper = new JunPaper();
else {
paper = new HigPaper();
}
paper.makePaper(num, name);
···
在paraSet()方法中,蒋同学对输入的范围做了限制,符合项目要求。同时针对该用户的类型,生成相应类型的试卷对象,进行出题。
值得一提的是,这里使用了面向接口的编程思想,即调用了接口的方法,规范了各个子类。
// 类型改变方法,改变用户默认的出题类型
public void toggleType(String newType) {
type = newType;
}
这里通过不断修改user类的type属性实现,重新登陆后,该type会恢复到默认类型。
优点:
1. 将程序的主要功能封装在User类中,布局严密完整
2. 充分使用了Java中的容器进行存储
3. 对各个边界考虑到位,控制台提示清晰
缺点:
1. 由于是在程序内部存储用户信息,比较局限,如果要增加用户的话,比较复杂
2. 在用户出完题后,不能再修改出题类型,只能在未出题前进行修改。
-
Paper接口
public interface Paper {
void makePaper(Integer num, String name);
String makeArith();
void writeArith(String arith, String filePath, String fileName);
}
该接口定义了三个方法。
makePaper方法,指定生成题目的数量和出题人,函数内部会调用其他两个方法
makeArith方法,以字符串形式返回一个符合要求的算式
writeArith方法,把算式写入指定路径下的文档中。
优点:
1. 定义了接口,即规范,体现了面向对象中的多态。
2. 方法名定义合理清晰。
3. 将这几个方法定义在该接口中,体现了高内聚的风格。
-
PriPaper类(实现类Paper的接口)
下面结合代码讲一下,实现各个方法的思路
// makeArith():以字符串形式返回一个符合要求的算式
public String makeArith() {
String arith = "";
Random rand = new Random();
//[0,bound)
int dataNum = rand.nextInt(5) + 1;
if (dataNum == 1) {
dataNum++;
}
//随机决定加不加括号,如果只有2个数,就不加
int brackets = rand.nextInt(2);
if (dataNum < 3) {
brackets = 0;
}
String[] operator = {"*", "/", "+", "-"};
//添加数字和运算符和括号
for (int j = 0; j < dataNum; j++) {
int randData = rand.nextInt(100) + 1;
int randOperaIndex = rand.nextInt(4);
//加括号
if (j != dataNum - 1 && randOperaIndex >= 2 && brackets == 1) {
brackets = 0;
arith += "(";
arith += String.valueOf(randData);
arith += operator[randOperaIndex];
randData = rand.nextInt(100) + 1;
arith += String.valueOf(randData);
arith += ")";
randOperaIndex = rand.nextInt(4);
arith += operator[randOperaIndex];
j++;
}
//不加括号,先加数,再加运算符
arith += String.valueOf(randData);
if (j != dataNum - 1) {
arith += operator[randOperaIndex];
}
}
return arith;
}
在小学的出题规则中,蒋同学采用了先定义空字符串,然后每次循环,随机加入指定格式的子字符串,如果选择加入括号,那么每次循环加上类似于"(a+b)"的字符串,否则,每次循环加上类似"a+"的字符串。
优点:
1. 随机的方式,通过代码可以清晰的了解到,利用了rand函数实现随机。
2. 完全符合题目要求
缺点:
1. 只能添加一次括号。
// writeArith 把算式写入指定路径下的文档中
public void writeArith(String arith, String filePath, String fileName) {
FileWriter writer = null;
try {
writer = new FileWriter(filePath + "/" + fileName, true);
writer.write(arith + "\r\n\n");
writer.flush();
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
个人建议:
这个方法未必要写在这个接口中。可以发现每个实现Paper接口的类,他们的这个方法完全相同,可以将这个方法单独作为一个工具类的静态函数出现。
makePaper 方法调用了上述writeArith方法。主要是做了生成文件夹和项目所需格式的文件,然后遍历该文件夹下的文件进行查重,如果本次题目与之前不重复,那么将题目写入文件。代码不做具体呈现。
共同问题:
蒋同学和我查重的思路一样,都是遍历整个文件夹的题目,相对来说效率低下,但目前没有好的解决办法。
个人建议:
- 与writeArith方法问题一样,每个实现Paper接口的类,该方法都一样,完全可以不在该类出现。
- 与writeArith函数未必需要分割开来,都是实现了写函数的功能,恰恰是查重可以作为一个单独的函数出现。
-
JunPaper类(实现类Paper的接口)
public class JunPaper implements Paper {
@Override
// makeArith():以字符串形式返回一个符合要求的算式
public String makeArith() {
...
//随机决定加不加括号,如果只有2个数,就不加
int brackets = rand.nextInt(2);
if (dataNum < 3) {
brackets = 0;
}
String[] operator = {"*", "/", "+", "-"};
int Pow = 0;
if (brackets == 0) {
Pow = 1;
}
//添加数字和运算符和括号
for (int j = 0; j < dataNum; j++) {
int randData = rand.nextInt(100) + 1;
int randOperaIndex = rand.nextInt(4);
//加括号与根号
if (j != dataNum - 1 && randOperaIndex >= 2 && brackets == 1) {
brackets = 0;
arith += "√";
arith += "(";
arith += String.valueOf(randData);
arith += operator[randOperaIndex];
randData = rand.nextInt(100) + 1;
arith += String.valueOf(randData);
arith += ")";
randOperaIndex = rand.nextInt(4);
arith += operator[randOperaIndex];
j++;
}
//不加括号,先加数,再加运算符
arith += String.valueOf(randData);
int randPow = rand.nextInt(2);
if (randPow == 1 && Pow == 1) {
arith += "^2";
Pow = 0;
}
if (j != dataNum - 1) {
arith += operator[randOperaIndex];
}
}
return arith;
}
优点:
1. 随机的方式,通过代码可以清晰的了解到。
2. 完全符合题目要求,通过设置状态变量体现让平方或根号出现。
缺点:
1. 这里沿用了小学出题的模式,不过当有括号时,必有根号,这就注定了题目中括号与根号同时出现,而且只出现一次。同时,平方的随机也存在同样的问题如果题目中出
现平方,那么只出现一次。
2. 这样的随机限制了根号和平方最多只出现一个
-
HighPaper类(实现类Paper的接口)
优点:
1. 结构逻辑性强,注释清晰,可读性好。
2. 出题的逻辑划分为两种解决方案(无括号/考虑括号存在),避免了大段代码重复。
3. 代码跟我的比较更加简洁,同样实现了功能。
缺点:
1. 出题的方法在初中出题的基础上进行增加,即多了随机是否增加三角函数。问题也类似,一道题最多一个三角函数,随机的模式比较固定。
-
Main类
public class Main {
public static void main(String[] args) {
System.out.println("----中小学数学卷子自动生成程序----");
System.out.println("----请尽可能按照提示输入操作,所有输入前请不要加空格或回车----");
System.out.println();
User user = new User();
user.logIn();
}
}
优点:
1. 主函数十分简洁,各个功能经过封装,然后被调用。
2. 功能清晰。
个人建议: 这里新建了一个空的user对象,然后调用login方法,其实如果能将login方法设为静态方法,返回一个有意义的user对象更好。
三、结果测试
用户出完题后,无法继续切换类型
所出题目符合规范
边界条件处理到位
四、总结
蒋同学的代码充分里使用了面向对象的思想,将很多功能进行了封装。功能模块划分清晰,使得代码整体看上去容易理解。程序中文字提示清晰,对用户体验良好。在部分细节上,处理的与我的想法略有出入,但是也完美了实现了项目需求,在结对项目中我们会继续探讨整合问题。
整体上,较高程度实现了个人项目的需求,有很多值得我学习的地方。