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方法。主要是做了生成文件夹和项目所需格式的文件,然后遍历该文件夹下的文件进行查重,如果本次题目与之前不重复,那么将题目写入文件。代码不做具体呈现。
共同问题:
 蒋同学和我查重的思路一样,都是遍历整个文件夹的题目,相对来说效率低下,但目前没有好的解决办法。
个人建议:
  1. 与writeArith方法问题一样,每个实现Paper接口的类,该方法都一样,完全可以不在该类出现。
  2. 与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对象更好。

三、结果测试

用户出完题后,无法继续切换类型

所出题目符合规范

边界条件处理到位

四、总结

        蒋同学的代码充分里使用了面向对象的思想,将很多功能进行了封装。功能模块划分清晰,使得代码整体看上去容易理解。程序中文字提示清晰,对用户体验良好。在部分细节上,处理的与我的想法略有出入,但是也完美了实现了项目需求,在结对项目中我们会继续探讨整合问题。
        整体上,较高程度实现了个人项目的需求,有很多值得我学习的地方。

posted @ 2022-09-13 20:02  白羽···  阅读(254)  评论(0编辑  收藏  举报