模块化一:提取模块

目录请见:模块化(零):综述

第 1 步:修改变量名

如果你改的是自己的代码,可以暂时不做这一步,跳到第二步。(毕竟你 现在暂时 还能看得懂前几天写的代码)

从初始代码可以看出,很多变量名不合适。例如:

int useNo = 0;// 控制操作符

这里的 useNo 用于控制是否能生成乘号和除号。通过 useNo 你无法知道这个变量名表示什么,就算你看了这行后面的注释,你也不知道这个变量是用来干嘛的。

这一行一开始被我改成:

int baseNum = 0; // 控制操作符是否能为乘号和除号

控制符号是否能为乘除号的代码是这样的(三个点表示省略中间的代码):

char[] op = { ' ', '+', '-', '*', '÷' };
...
if (useMultiAndDiv == 'Y' || useMultiAndDiv == 'y') {
    baseNum = 4;
} else if (useMultiAndDiv == 'N' || useMultiAndDiv == 'n') {
    baseNum = 2;
}
...
opIndex[k] = (int) (Math.random() * baseNum + 1);
...
switch(op[opIndex[g]){
...

这里使用 baseNum 作为基数,Math.random() 生成 0.0 到 1.0 之间的数。如果 baseNum 等于2,那么随机出来的数乘以基数后最大为2,因此无法取到乘号和除号。

在不对源代码进行较大改动的情况下,我能想到的最合适命名就是 baseNum 了。搭配代码之后的注释,读者应该比较容易理解。

变量名不是主角,就不讲太多了。

变量名重新命名后的代码:


import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Scanner;
public class lastdemo {
    /**
     * 求两个整数的最大公约数
     *
     * @param num1
     * @param num2
     * @return num1 和 num2 的最大公约数
     */
    private static int Gcd(int num1, int num2) {
        num1 = Math.abs(num1);
        num2 = Math.abs(num2);
        int min = Math.min(num1, num2);
        int maxSubmultiple = 1;
        for (int i = min; i >= 1; i--) {
            if (num1 % i == 0 && num2 % i == 0) {
                maxSubmultiple = i;
                break;
            }
        }
        return maxSubmultiple;
    }
    public static void main(String[] args) {
        Scanner inputs = new Scanner(System.in);
        System.out.print("请输入你想要作答的题目数量:");
        int problemsCount = inputs.nextInt();
        System.out.print("请选择你要进行的运算:1.整数;2.真分数;");
        int choice = inputs.nextInt();
        if (choice == 1) {
            double rightCount = 0;
            for (int i = 1; i <= problemsCount; i++) {
                int leftNum = (int) (Math.random() * 10 + 1);/* 防止出现不好处理的0,很不严谨不可取 */
                int rightNum = (int) (Math.random() * 10 + 1);
                int result = 0;
                int operator = (int) (Math.random() * 4);
                switch (operator) {
                    case 0:
                        System.out.print("第" + i + "题" + ": ");
                        System.out.print(leftNum + " + " + rightNum + " = ");
                        result = leftNum + rightNum;
                        break;
                    case 1:
                        if (leftNum < rightNum) {
                            int t = leftNum;
                            leftNum = rightNum;
                            rightNum = t;
                        }
                        System.out.print("第" + i + "题" + ": ");
                        System.out.print(leftNum + " - " + rightNum + " = ");
                        result = leftNum - rightNum;
                        break;
                    case 2:
                        System.out.print("第" + i + "题" + ": ");
                        System.out.print(leftNum + " × " + rightNum + " = ");
                        result = leftNum * rightNum;
                        break;
                    case 3:
                        System.out.print("第" + i + "题" + ": ");
                        if (leftNum < rightNum) {
                            int t = leftNum;
                            leftNum = rightNum;
                            rightNum = t;
                        }
                        if (leftNum % rightNum != 0) {
                            leftNum = (int) (Math.random() * 10 + 1) * rightNum;/* 保证能整除 */
                        }
                        System.out.print(leftNum + " ÷ " + rightNum + " = ");
                        result = leftNum / rightNum;
                        break;
                }
                int answer = inputs.nextInt();
                if (answer == result) {
                    rightCount++;
                    System.out.println("答对了,恭喜\n");
                } else {
                    System.out.println("答错了,加油\n");
                }
            }
            System.out.println("True Rate:" + rightCount / problemsCount);
        } else if (choice == 2) {
            final int OP_MAX = 4;
            char[] op = {' ', '+', '-', '*', '÷'};
            int[] opIndex = new int[OP_MAX]; // 保存操作符下标
            int baseNum = 0; // 控制操作符是否能为乘号和除号
            boolean shouldOutputOp = true;
            int[] nums = new int[4];
            final int PROBLEMS_COUNT_MAX = 100;
            String[] userAnswer = new String[PROBLEMS_COUNT_MAX];
            String[] standardAnswer = new String[PROBLEMS_COUNT_MAX];
            DecimalFormat decimal = new DecimalFormat("#.##");
            decimal.setRoundingMode(RoundingMode.HALF_UP);
            int leftNumerator;
            int rightNumerator;
            int resultDenominator;
            int resultNumerator;
            int gcd;    // 最大公约数
            boolean isDividedByZero = false;
            boolean isMultipliedByZero = false;
            String simplestNumerator = "";
            String simplestDenominator = "";
            System.out.print("请选择是否需要乘除法算术题(Y or N):");
            char useMultiAndDiv = inputs.next().charAt(0);
            if (useMultiAndDiv == 'Y' || useMultiAndDiv == 'y') {
                baseNum = 4;
            } else if (useMultiAndDiv == 'N' || useMultiAndDiv == 'n') {
                baseNum = 2;
            }
            System.out.println("请计算下列真分数计算题。(若无法运算请填入null)");
            System.out.println("***************************************");
            final int MAX_NUM = 10;
            final int MIN_NUM = 1;
            for (int i = 0; i < problemsCount; i++) {
                System.out.print("(" + (i + 1) + ") ");
                // 第一个真分数。nums[0]为分子,nums[1]为分母
                for (int index = 0; index < 2; index++) {
                    nums[index] = (int) (Math.random()
                            * (MAX_NUM - MIN_NUM) + MIN_NUM);
                    if (index == 1) {
                        // 保证分子不大于分母,以及分母不为零
                        while (nums[index - 1] > nums[index] || nums[index] == 0) {
                            nums[index] = (int) (Math.random()
                                    * (MAX_NUM - MIN_NUM) + MIN_NUM);
                        }
                    }
                }
                // 第二个真分数。nums[2]为分子,nums[3]为分母
                for (int index = 2; index < 4; index++) {
                    nums[index] = (int) (Math.random()
                            * (MAX_NUM - MIN_NUM) + MIN_NUM);
                    if (index == 3) {
                        // 保证分子不大于分母,以及分母不为零
                        while (nums[index - 1] > nums[index] || nums[index] == 0) {
                            nums[index] = (int) (Math.random()
                                    * (MAX_NUM - MIN_NUM) + MIN_NUM);
                        }
                    }
                }
                // 产生两个操作符下标。乘以2最多得2
                for (int index = 0; index < 2; index++) {
                    opIndex[index] = (int) (Math.random() * baseNum + 1);
                }
                // 输出整个式子,index 表示正在输出的数字的位置
                for (int index = 0; index < 4; index++) {
                    if (index % 2 == 0) {
                        System.out.print("(" + nums[index] + "/");
                    } else if (index % 2 == 1) {
                        System.out.print(nums[index] + ")");
                        if (shouldOutputOp) {
                            System.out.print(op[opIndex[0]]);
                            shouldOutputOp = false;
                        } else {
                            System.out.println("=");
                        }
                    }
                }
                shouldOutputOp = true;
                // 求结果
                for (int index = 0; index < 1; index++) {
                    // 不求最大公倍数,直接乘对方分母
                    resultDenominator = nums[1] * nums[3];
                    leftNumerator = nums[0] * nums[3];
                    rightNumerator = nums[2] * nums[1];
                    isDividedByZero = false;
                    isMultipliedByZero = false;
                    switch (op[opIndex[index]]) {
                        case '+':
                            resultNumerator = leftNumerator + rightNumerator;
                            gcd = Gcd(resultNumerator, resultDenominator);
                            simplestNumerator = String.valueOf(resultNumerator / gcd);
                            simplestDenominator = String.valueOf(resultDenominator / gcd);
                            break;
                        case '-':
                            resultNumerator = leftNumerator - rightNumerator;
                            gcd = Gcd(resultNumerator, resultDenominator);
                            simplestNumerator = String.valueOf(resultNumerator / gcd);
                            simplestDenominator = String.valueOf(resultDenominator / gcd);
                            break;
                        case '*':
                            resultNumerator = nums[0] * nums[2];
                            gcd = Gcd(resultNumerator, resultDenominator);
                            // 分子有0则结果为0
                            if (nums[0] == 0 || nums[2] == 0) {
                                isMultipliedByZero = true;
                            }
                            simplestNumerator = String.valueOf(resultNumerator / gcd);
                            simplestDenominator = String.valueOf(resultDenominator / gcd);
                            break;
                        case '/':
                            // 除以一个数,等于乘以它的倒数
                            resultNumerator = nums[0] * nums[3];
                            resultDenominator = nums[1] * nums[2];
                            gcd = Gcd(resultNumerator, resultDenominator);
                            if (nums[0] == 0 || nums[2] == 0) {
                                isDividedByZero = true;
                            }
                            simplestNumerator = String.valueOf(resultNumerator / gcd);
                            simplestDenominator = String.valueOf(resultDenominator / gcd);
                            break;
                    }
                }
                if (isDividedByZero) {
                    standardAnswer[i] = "null"; // 当第二个数的分子为零时无法进行除法运算
                } else if (isMultipliedByZero) {
                    standardAnswer[i] = "0";
                } else if (simplestNumerator.equals(simplestDenominator)) {
                    standardAnswer[i] = "1";
                } else if (simplestDenominator.equalsIgnoreCase("1")) {
                    standardAnswer[i] = simplestNumerator;
                } else {
                    standardAnswer[i] = simplestNumerator + "/" + simplestDenominator;
                }
            }
            // 用户答题
            int rightCount = 0;
            System.out.println("请输入你的答案:");
            for (int i = 0; i < problemsCount; i++) {
                System.out.print((i + 1) + ":");
                userAnswer[i] = inputs.next();
                if (userAnswer[i].equals(standardAnswer[i])) {
                    rightCount++;
                }
            }
            System.out.println("标准答案是 :    ");
            for (int i = 0; i < problemsCount; i++) {
                System.out.println((i + 1) + ":" + standardAnswer[i]);
            }
            double trueRate = ((double) rightCount / (double) problemsCount) * 100;
            System.out.println("True rate:" + decimal.format(trueRate) + "%");
            System.out.println("**************************************");
        } else {
            System.out.println("请输入1或2:");
        }
    }
}

第 2 步:标记模块

在读懂代码的基础上,标出代码运行中每个步骤都做了什么。我在这里特别标出了各个分支的等级和分支号。等级从外到内递增,分支号从上到下递增。

(等级一)选择分支二 中有个 for 循环。它的代码太长,我在结尾处加上了一个注释:

// 输出题目并计算题目的答案
...
for (int i = 0; i < problemsCount; i++) {
...
} // 输出题目并计算答案结束

你可能要拉 3 - 4 个屏幕才能看到。

在一段很长的代码中,会有几个比较大的功能块,可以将其理解为一台机器的 "零件"。

还是以 (等级一)选择分支二 为例,标记结束并整理如下:

  1. 初始化用户答案和标准答案数组
  2. 让用户选择是否出乘除法的题目
  3. 生成题目并输出,还有计算答案 的 for 循环
    1. 生成第一个真分数
    2. 生成第二个真分数
    3. 随机获取两个运算符的坐标
    4. 输出整个式子
    5. 求式子的标准答案
      1. 加法的情况
      2. 减法的情况
      3. 乘法的情况
      4. 除法的情况
    6. 根据标准答案的计算情况选择标准答案的正确形式
  4. 获取用户输入的答案、与标准答案比较
  5. 输出标准答案
  6. 计算并输出正确率

这里的每一项基本都能抽成一个模块。

以下是标记完模块后的代码:


import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.util.Scanner;
public class LastDemo {
    /**
     * 求两个整数的最大公约数
     *
     * @param num1
     * @param num2
     * @return num1 和 num2 的最大公约数
     */
    private static int Gcd(int num1, int num2) {
        num1 = Math.abs(num1);
        num2 = Math.abs(num2);
        int min = Math.min(num1, num2);
        int maxSubmultiple = 1;
        for (int i = min; i >= 1; i--) {
            if (num1 % i == 0 && num2 % i == 0) {
                maxSubmultiple = i;
                break;
            }
        }
        return maxSubmultiple;
    }
    public static void main(String[] args) {
        // 入口控制,基本输入
        Scanner inputs = new Scanner(System.in);
        System.out.print("请输入你想要作答的题目数量:");
        int problemsCount = inputs.nextInt();
        System.out.print("请选择你要进行的运算:1.整数;2.真分数;");
        int choice = inputs.nextInt();
        // (等级一)选择分支一
        if (choice == 1) {
            int rightCount = 0;
            for (int i = 1; i <= problemsCount; i++) {
                int leftNum = (int) (Math.random() * 10 + 1);/* 防止出现不好处理的0,很不严谨不可取 */
                int rightNum = (int) (Math.random() * 10 + 1);
                int result = 0;
                // 根据随机的运算符进行相应的操作
                int operator = (int) (Math.random() * 4);
                switch (operator) {
                    case 0:
                        // (等级二)选择分支一
                        System.out.print("第" + i + "题" + ": ");
                        System.out.print(leftNum + " + " + rightNum + " = ");
                        result = leftNum + rightNum;
                        break;
                    case 1:
                        // (等级二)选择分支二
                        if (leftNum < rightNum) {
                            int t = leftNum;
                            leftNum = rightNum;
                            rightNum = t;
                        }
                        System.out.print("第" + i + "题" + ": ");
                        System.out.print(leftNum + " - " + rightNum + " = ");
                        result = leftNum - rightNum;
                        break;
                    case 2:
                        // (等级二)选择分支三
                        System.out.print("第" + i + "题" + ": ");
                        System.out.print(leftNum + " × " + rightNum + " = ");
                        result = leftNum * rightNum;
                        break;
                    case 3:
                        // (等级二)选择分支四
                        System.out.print("第" + i + "题" + ": ");
                        if (leftNum < rightNum) {
                            int t = leftNum;
                            leftNum = rightNum;
                            rightNum = t;
                        }
                        // 保证能整除
                        if (leftNum % rightNum != 0) {
                            leftNum = (int) (Math.random() * 10 + 1) * rightNum;
                        }
                        System.out.print(leftNum + " ÷ " + rightNum + " = ");
                        result = leftNum / rightNum;
                        break;
                }
                // 获取用户输入并判断
                int answer = inputs.nextInt();
                if (answer == result) {
                    rightCount++;
                    System.out.println("答对了,恭喜\n");
                } else {
                    System.out.println("答错了,加油\n");
                }
            }
            System.out.println("True Rate:" + (double) rightCount / problemsCount);
        } else if (choice == 2) {
            // (等级一)选择分支二
            final int PROBLEMS_COUNT_MAX = 100;
            String[] userAnswer = new String[PROBLEMS_COUNT_MAX];
            String[] standardAnswer = new String[PROBLEMS_COUNT_MAX];
            // 是否出现乘除法的题目
            System.out.print("请选择是否需要乘除法算术题(Y or N):");
            char useMultiAndDiv = inputs.next().charAt(0);
            int baseNum = 0; // 控制操作符是否能为乘号和除号
            if (useMultiAndDiv == 'Y' || useMultiAndDiv == 'y') {
                baseNum = 4;
            } else if (useMultiAndDiv == 'N' || useMultiAndDiv == 'n') {
                baseNum = 2;
            }
            System.out.println("请计算下列真分数计算题。(若无法运算请填入null)");
            System.out.println("***************************************");
            // 输出题目并计算题目的答案
            final int MAX_NUM = 10;
            final int MIN_NUM = 1;
            int[] nums = new int[4];
            for (int i = 0; i < problemsCount; i++) {
                System.out.print("(" + (i + 1) + ") ");
                // 第一个真分数。nums[0]为分子,nums[1]为分母
                for (int index = 0; index < 2; index++) {
                    nums[index] = (int) (Math.random()
                            * (MAX_NUM - MIN_NUM) + MIN_NUM);
                    if (index == 1) {
                        // 保证分子不大于分母,以及分母不为零
                        while (nums[index - 1] > nums[index] || nums[index] == 0) {
                            nums[index] = (int) (Math.random()
                                    * (MAX_NUM - MIN_NUM) + MIN_NUM);
                        }
                    }
                }
                // 第二个真分数。nums[2]为分子,nums[3]为分母
                for (int index = 2; index < 4; index++) {
                    nums[index] = (int) (Math.random()
                            * (MAX_NUM - MIN_NUM) + MIN_NUM);
                    if (index == 3) {
                        // 保证分子不大于分母,以及分母不为零
                        while (nums[index - 1] > nums[index] || nums[index] == 0) {
                            nums[index] = (int) (Math.random()
                                    * (MAX_NUM - MIN_NUM) + MIN_NUM);
                        }
                    }
                }
                final int OP_MAX = 4;
                int[] opIndex = new int[OP_MAX]; // 保存操作符下标
                // 产生两个操作符下标。乘以2最多得2
                for (int index = 0; index < 2; index++) {
                    opIndex[index] = (int) (Math.random() * baseNum + 1);
                }
                char[] op = {' ', '+', '-', '*', '÷'};
                // 输出整个式子,index 表示正在输出的数字的位置
                boolean shouldOutputOp = true;
                for (int index = 0; index < 4; index++) {
                    if (index % 2 == 0) {
                        System.out.print("(" + nums[index] + "/");
                    } else if (index % 2 == 1) {
                        System.out.print(nums[index] + ")");
                        if (shouldOutputOp) {
                            System.out.print(op[opIndex[0]]);
                            shouldOutputOp = false;
                        } else {
                            System.out.println("=");
                        }
                    }
                }
                // 求结果
                int leftNumerator;
                int rightNumerator;
                int resultDenominator;
                int resultNumerator;
                int gcd;    // 最大公约数
                boolean isDividedByZero = false;
                boolean isMultipliedByZero = false;
                String simplestNumerator = "";
                String simplestDenominator = "";
                // 不求最大公倍数,直接乘对方分母
                resultDenominator = nums[1] * nums[3];
                leftNumerator = nums[0] * nums[3];
                rightNumerator = nums[2] * nums[1];
                switch (op[opIndex[0]]) {
                    case '+':
                        // (等级二)选择分支一
                        resultNumerator = leftNumerator + rightNumerator;
                        gcd = Gcd(resultNumerator, resultDenominator);
                        simplestNumerator = String.valueOf(resultNumerator / gcd);
                        simplestDenominator = String.valueOf(resultDenominator / gcd);
                        break;
                    case '-':
                        // (等级二)选择分支二
                        resultNumerator = leftNumerator - rightNumerator;
                        gcd = Gcd(resultNumerator, resultDenominator);
                        simplestNumerator = String.valueOf(resultNumerator / gcd);
                        simplestDenominator = String.valueOf(resultDenominator / gcd);
                        break;
                    case '*':
                        // (等级二)选择分支三
                        resultNumerator = nums[0] * nums[2];
                        gcd = Gcd(resultNumerator, resultDenominator);
                        // 分子有0则结果为0
                        if (nums[0] == 0 || nums[2] == 0) {
                            isMultipliedByZero = true;
                        }
                        simplestNumerator = String.valueOf(resultNumerator / gcd);
                        simplestDenominator = String.valueOf(resultDenominator / gcd);
                        break;
                    case '/':
                        // (等级二)选择分支四
                        // 除以一个数,等于乘以它的倒数
                        resultNumerator = nums[0] * nums[3];
                        resultDenominator = nums[1] * nums[2];
                        gcd = Gcd(resultNumerator, resultDenominator);
                        if (nums[0] == 0 || nums[2] == 0) {
                            isDividedByZero = true;
                        }
                        simplestNumerator = String.valueOf(resultNumerator / gcd);
                        simplestDenominator = String.valueOf(resultDenominator / gcd);
                        break;
                }
                if (isDividedByZero) {
                    standardAnswer[i] = "null"; // 当第二个数的分子为零时无法进行除法运算
                } else if (isMultipliedByZero) {
                    standardAnswer[i] = "0";
                } else if (simplestNumerator.equals(simplestDenominator)) {
                    standardAnswer[i] = "1";
                } else if (simplestDenominator.equalsIgnoreCase("1")) {
                    standardAnswer[i] = simplestNumerator;
                } else {
                    standardAnswer[i] = simplestNumerator + "/" + simplestDenominator;
                }
            } // 输出题目并计算答案结束
            // 用户答题
            int rightCount = 0;
            System.out.println("请输入你的答案:");
            for (int i = 0; i < problemsCount; i++) {
                System.out.print((i + 1) + ":");
                userAnswer[i] = inputs.next();
                if (userAnswer[i].equals(standardAnswer[i])) {
                    rightCount++;
                }
            }
            System.out.println("标准答案是 :    ");
            for (int i = 0; i < problemsCount; i++) {
                System.out.println((i + 1) + ":" + standardAnswer[i]);
            }
            DecimalFormat decimal = new DecimalFormat("#.##");
            decimal.setRoundingMode(RoundingMode.HALF_UP);
            double trueRate = ((double) rightCount / (double) problemsCount) * 100;
            System.out.println("True rate:" + decimal.format(trueRate) + "%");
            System.out.println("**************************************");
        } else {
            // (等级一)判断分支三:输入非指定选项
            System.out.println("请输入1或2:");
        }
    }
}

第 3 步:把标记的每个部分抽取到新的方法里

新创建的方法要尽量满足以下两个条件:

  • 方法内不能直接使用全局变量。如果要使用外部的变量,使用参数传递;
  • 每个方法在处理完一件事之后,使用 return 返回结果。

也就是说,把新创建的方法放在任何一个地方都不会编译错误。

3.1 先干掉大家伙

标记 (等级一)选择分支一(等级一)选择分支二 分别表示整数的处理和真分数的处理。它们是在一块 if- else if - else 的分支结构中。

在某一个分支下,不应该有太多的代码。如果实在需要很多代码才能完成功能,那么就创建转发函数(方法)。

分析两个分支代码:

  • 输入部分
    仔细看一遍上面的代码,你会发现这两个分支只用到存在于分支外的一个变量 problemsCount 。换句话说,分支只依赖于一个输入 problemsCount
  • 输出部分
    在每一个选择分支结束后,程序就结束了,没有其他处理。这里就不需要返回任何值。

分析完输入输出之后,创建空方法:

private static void IntegerMode(int problemsCount) {

}

private static void FractionMode(int problemsCount) {

}

接着将标记为 (等级一)选择分支一(等级一)选择分支二 的两块代码分别放入以上的 IntegerMode(int)FractionMode(int) 里面。

这时候原来的分支就变成这样:

if (choice == 1) {

} else if (choice == 2) {

} else {
    // (等级一)判断分支三:输入非指定选项
    System.out.println("请输入1或2:");
}

此时在分支里面填上相应的转发函数。

    public static void main(String[] args) {
        // 入口控制,基本输入
        Scanner inputs = new Scanner(System.in);

        System.out.print("请输入你想要作答的题目数量:");
        int problemsCount = inputs.nextInt();

        System.out.print("请选择你要进行的运算:1.整数;2.真分数;");
        int choice = inputs.nextInt();

        if (choice == 1) {
            IntegerMode(problemsCount);
        } else if (choice == 2) {
            FractionMode(problemsCount);
        } else {
            // (等级一)判断分支三:输入非指定选项
            System.out.println("请输入1或2:");
        }

    }

至此,我们将 222 行的 main() 方法压缩到了 19 行。现在 main() 方法做了哪些事已经很清楚了。

但是还没结束,新创建的 IntegerMode(int)FractionMode(int) 代码行数分别为 63 行和 172 行。

软件工程师通常一次只能看到 30-80 行源代码(相当于显示器的一屏) —— 《构建之法》第二版p23

我的小分辨率屏幕只能显示 30 行左右的代码。一旦一块代码超出这个数,就会对阅读代码的人造成一定的影响。

3.2 开始处理真分数模式

既然 FractionMode(int) 的行数比较多(172行),那么就帮它减肥吧!

以下是它的完整代码:

private static void FractionMode(int problemsCount) {
        final int PROBLEMS_COUNT_MAX = 100;
        String[] userAnswer = new String[PROBLEMS_COUNT_MAX];
        String[] standardAnswer = new String[PROBLEMS_COUNT_MAX];

        // 是否出现乘除法的题目
        System.out.print("请选择是否需要乘除法算术题(Y or N):");
        Scanner inputs = new Scanner(System.in);
        char useMultiAndDiv = inputs.next().charAt(0);
        int baseNum = 0; // 控制操作符是否能为乘号和除号
        if (useMultiAndDiv == 'Y' || useMultiAndDiv == 'y') {
            baseNum = 4;
        } else if (useMultiAndDiv == 'N' || useMultiAndDiv == 'n') {
            baseNum = 2;
        }

        System.out.println("请计算下列真分数计算题。(若无法运算请填入null)");
        System.out.println("***************************************");

        // 输出题目并计算题目的答案
        final int MAX_NUM = 10;
        final int MIN_NUM = 1;
        int[] nums = new int[4];
        for (int i = 0; i < problemsCount; i++) {
            System.out.print("(" + (i + 1) + ") ");

            // 第一个真分数。nums[0]为分子,nums[1]为分母
            for (int index = 0; index < 2; index++) {
                nums[index] = (int) (Math.random()
                        * (MAX_NUM - MIN_NUM) + MIN_NUM);

                if (index == 1) {
                    // 保证分子不大于分母,以及分母不为零
                    while (nums[index - 1] > nums[index] || nums[index] == 0) {
                        nums[index] = (int) (Math.random()
                                * (MAX_NUM - MIN_NUM) + MIN_NUM);
                    }
                }
            }

            // 第二个真分数。nums[2]为分子,nums[3]为分母
            for (int index = 2; index < 4; index++) {
                nums[index] = (int) (Math.random()
                        * (MAX_NUM - MIN_NUM) + MIN_NUM);

                if (index == 3) {
                    // 保证分子不大于分母,以及分母不为零
                    while (nums[index - 1] > nums[index] || nums[index] == 0) {
                        nums[index] = (int) (Math.random()
                                * (MAX_NUM - MIN_NUM) + MIN_NUM);
                    }
                }
            }

            final int OP_MAX = 4;
            int[] opIndex = new int[OP_MAX]; // 保存操作符下标

            // 产生两个操作符下标。乘以2最多得2
            for (int index = 0; index < 2; index++) {
                opIndex[index] = (int) (Math.random() * baseNum + 1);
            }

            char[] op = {' ', '+', '-', '*', '÷'};
            // 输出整个式子,index 表示正在输出的数字的位置
            boolean shouldOutputOp = true;
            for (int index = 0; index < 4; index++) {
                if (index % 2 == 0) {
                    System.out.print("(" + nums[index] + "/");
                } else if (index % 2 == 1) {
                    System.out.print(nums[index] + ")");
                    if (shouldOutputOp) {
                        System.out.print(op[opIndex[0]]);
                        shouldOutputOp = false;
                    } else {
                        System.out.println("=");
                    }
                }
            }

            // 求结果
            int leftNumerator;
            int rightNumerator;
            int resultDenominator;
            int resultNumerator;
            int gcd;    // 最大公约数
            boolean isDividedByZero = false;
            boolean isMultipliedByZero = false;
            String simplestNumerator = "";
            String simplestDenominator = "";

            // 不求最大公倍数,直接乘对方分母
            resultDenominator = nums[1] * nums[3];
            leftNumerator = nums[0] * nums[3];
            rightNumerator = nums[2] * nums[1];

            switch (op[opIndex[0]]) {
                case '+':
                    // (等级二)选择分支一
                    resultNumerator = leftNumerator + rightNumerator;
                    gcd = Gcd(resultNumerator, resultDenominator);

                    simplestNumerator = String.valueOf(resultNumerator / gcd);
                    simplestDenominator = String.valueOf(resultDenominator / gcd);
                    break;
                case '-':
                    // (等级二)选择分支二
                    resultNumerator = leftNumerator - rightNumerator;
                    gcd = Gcd(resultNumerator, resultDenominator);

                    simplestNumerator = String.valueOf(resultNumerator / gcd);
                    simplestDenominator = String.valueOf(resultDenominator / gcd);
                    break;
                case '*':
                    // (等级二)选择分支三
                    resultNumerator = nums[0] * nums[2];
                    gcd = Gcd(resultNumerator, resultDenominator);
                    // 分子有0则结果为0
                    if (nums[0] == 0 || nums[2] == 0) {
                        isMultipliedByZero = true;
                    }
                    simplestNumerator = String.valueOf(resultNumerator / gcd);
                    simplestDenominator = String.valueOf(resultDenominator / gcd);
                    break;
                case '/':
                    // (等级二)选择分支四
                    // 除以一个数,等于乘以它的倒数
                    resultNumerator = nums[0] * nums[3];
                    resultDenominator = nums[1] * nums[2];
                    gcd = Gcd(resultNumerator, resultDenominator);

                    if (nums[0] == 0 || nums[2] == 0) {
                        isDividedByZero = true;
                    }
                    simplestNumerator = String.valueOf(resultNumerator / gcd);
                    simplestDenominator = String.valueOf(resultDenominator / gcd);
                    break;
            }
            
            if (isDividedByZero) {
                standardAnswer[i] = "null"; // 当第二个数的分子为零时无法进行除法运算
            } else if (isMultipliedByZero) {
                standardAnswer[i] = "0";
            } else if (simplestNumerator.equals(simplestDenominator)) {
                standardAnswer[i] = "1";
            } else if (simplestDenominator.equalsIgnoreCase("1")) {
                standardAnswer[i] = simplestNumerator;
            } else {
                standardAnswer[i] = simplestNumerator + "/" + simplestDenominator;
            }

        } // 输出题目并计算答案结束

        // 用户答题
        int rightCount = 0;
        System.out.println("请输入你的答案:");
        for (int i = 0; i < problemsCount; i++) {
            System.out.print((i + 1) + ":");
            userAnswer[i] = inputs.next();
            if (userAnswer[i].equals(standardAnswer[i])) {
                rightCount++;
            }
        }
        System.out.println("标准答案是 :    ");
        for (int i = 0; i < problemsCount; i++) {
            System.out.println((i + 1) + ":" + standardAnswer[i]);
        }

        DecimalFormat decimal = new DecimalFormat("#.##");
        decimal.setRoundingMode(RoundingMode.HALF_UP);
        double trueRate = ((double) rightCount / (double) problemsCount) * 100;
        System.out.println("True rate:" + decimal.format(trueRate) + "%");
        System.out.println("**************************************");
}

当看到 switch (op[opIndex[0]]) {...} 的时候,我的强迫症犯了,我想先从这里开始。其实应该先简化它外层的 for 循环,不过这次让强迫症赢了……

switch 这部分的完整代码如下:

int leftNumerator;
int rightNumerator;
int resultDenominator;
int resultNumerator;
int gcd;    // 最大公约数
boolean isDividedByZero = false;
boolean isMultipliedByZero = false;
String simplestNumerator = "";
String simplestDenominator = "";

// 不求最大公倍数,直接乘对方分母
resultDenominator = nums[1] * nums[3];
leftNumerator = nums[0] * nums[3];
rightNumerator = nums[2] * nums[1];

switch (op[opIndex[0]]) {
    case '+':
        // (等级二)选择分支一
        resultNumerator = leftNumerator + rightNumerator;
        gcd = Gcd(resultNumerator, resultDenominator);

        simplestNumerator = String.valueOf(resultNumerator / gcd);
        simplestDenominator = String.valueOf(resultDenominator / gcd);
        break;
    case '-':
        // (等级二)选择分支二
        resultNumerator = leftNumerator - rightNumerator;
        gcd = Gcd(resultNumerator, resultDenominator);

        simplestNumerator = String.valueOf(resultNumerator / gcd);
        simplestDenominator = String.valueOf(resultDenominator / gcd);
        break;
    case '*':
        // (等级二)选择分支三
        resultNumerator = nums[0] * nums[2];
        gcd = Gcd(resultNumerator, resultDenominator);
        // 分子有0则结果为0
        if (nums[0] == 0 || nums[2] == 0) {
            isMultipliedByZero = true;
        }
        simplestNumerator = String.valueOf(resultNumerator / gcd);
        simplestDenominator = String.valueOf(resultDenominator / gcd);
        break;
    case '/':
        // (等级二)选择分支四
        // 除以一个数,等于乘以它的倒数
        resultNumerator = nums[0] * nums[3];
        resultDenominator = nums[1] * nums[2];
        gcd = Gcd(resultNumerator, resultDenominator);

        if (nums[0] == 0 || nums[2] == 0) {
            isDividedByZero = true;
        }
        simplestNumerator = String.valueOf(resultNumerator / gcd);
        simplestDenominator = String.valueOf(resultDenominator / gcd);
        break;
}
  • 输入部分

    可以看出,每个分支都会依赖于三个数:resultDenominator 、 leftNumerator 、 rightNumerator 。

    我们希望转发函数的名字为: FracAdd()、FracSub()、FracMulti()、FracDiv()。如果将上面三个数作为参数,转发函数的命名就会变得奇怪。

    Frac 为 Fraction (分数) 的简写,这里不简写可能会更好

    往上面的代码看,这三个数跟 nums[] 的四个数有关。四个数分别是运算符左边的分子分母和运算符右边的分子分母。将这四个数作为参数比较合适。

  • 输出部分

    每个分支都会产生的结果: simplestNumerator 、 simplestDenominator 。这两个结果组成了一个分数,可以将它们放到一个数组中,然后返回。

    不过乘法和除法比较特殊,它们还会分别产生一个布尔变量: isMultipliedByZero 、 isDividedByZero 。可以让 simplestNumerator 为 0 表示 isMultipliedByZero ; 让 simplestDenominator 为 0 表示 isDividedByZero 。

老样子,在分析完输入输出之后,创建空方法:

private static int[] FracAdd(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {

}

private static int[] FracSub(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {

}

private static int[] FracMulti(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {

}

private static int[] FracDiv(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {

}

(等级二)选择分支一(等级二)选择分支二(等级二)选择分支三(等级二)选择分支四 的代码分别放进去。不过由于之前直接使用 resultDenominator 这些结果,而现在用的参数是 nums[] 来的数,因此要先做处理。

处理之后的代码如下:

// (等级二)选择分支一
private static int[] FracAdd(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {
    // 不求最大公倍数,直接乘对方分母
    int newLeftNumerator = leftNumerator * rightDenominator;
    int newRightNumerator = rightNumerator * leftDenominator;

    int resultDenominator = leftDenominator * rightDenominator;
    int resultNumerator = newLeftNumerator + newRightNumerator;

    int gcd = Gcd(resultNumerator, resultDenominator);

    int[] simplestFrac = new int[2];
    simplestFrac[0] = resultNumerator / gcd;
    simplestFrac[1] = resultDenominator / gcd;
    return simplestFrac;
}

// (等级二)选择分支二
private static int[] FracSub(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {
    // 不求最大公倍数,直接乘对方分母
    int newLeftNumerator = leftNumerator * rightDenominator;
    int newRightNumerator = rightNumerator * leftDenominator;

    int resultDenominator = leftDenominator * rightDenominator;
    int resultNumerator = newLeftNumerator - newRightNumerator;

    int gcd = Gcd(resultNumerator, resultDenominator);

    int[] simplestFrac = new int[2];
    simplestFrac[0] = resultNumerator / gcd;
    simplestFrac[1] = resultDenominator / gcd;
    return simplestFrac;
}

// (等级二)选择分支三
private static int[] FracMulti(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {
    int[] simplestFrac = new int[2];

    // 分子有0则结果为0
    if (leftNumerator == 0 || rightNumerator == 0) {
        simplestFrac[0] = 0;
    } else {
        int newLeftNumerator = leftNumerator * rightDenominator;
        int newRightNumerator = rightNumerator * leftDenominator;

        int resultDenominator = leftDenominator * rightDenominator;
        int resultNumerator = newLeftNumerator * newRightNumerator;

        int gcd = Gcd(resultNumerator, resultDenominator);
        simplestFrac[0] = resultNumerator / gcd;
        simplestFrac[1] = resultDenominator / gcd;
    }
    return simplestFrac;
}

// (等级二)选择分支四
private static int[] FracDiv(int leftNumerator, int leftDenominator, int rightNumerator, int rightDenominator) {
    int[] simplestFrac = new int[2];

    if (leftNumerator == 0 || rightNumerator == 0) {
        simplestFrac[1] = 0;
    } else {
        // 除以一个数,等于乘以它的倒数
        int resultNumerator = leftNumerator * rightDenominator;
        int resultDenominator = leftDenominator * rightNumerator;
        int gcd = Gcd(resultNumerator, resultDenominator);
        simplestFrac[0] = resultNumerator / gcd;
        simplestFrac[1] = resultDenominator / gcd;
    }

    return simplestFrac;
}

此时的 switch 变成了这样:

// 求结果
boolean isDividedByZero = false;
boolean isMultipliedByZero = false;

int[] fracResult = new int[2];

switch (op[opIndex[0]]) {
    case '+':

        break;
    case '-':

        break;
    case '*':

        if (fracResult[0] == 0){
            isMultipliedByZero = true;
        }
        break;
    case '/':

        if (fracResult[1] == 0){
            isDividedByZero = true;
        }
        break;
}

String simplestNumerator = String.valueOf(fracResult[0]);
String simplestDenominator = String.valueOf(fracResult[1]);

将转发函数填充进去:

// 求结果
boolean isDividedByZero = false;
boolean isMultipliedByZero = false;

int[] fracResult = new int[2];

switch (op[opIndex[0]]) {
    case '+':
        fracResult = FracAdd(nums[0], nums[1], nums[2], nums[3]);
        break;
    case '-':
        fracResult = FracSub(nums[0], nums[1], nums[2], nums[3]);
        break;
    case '*':
        fracResult = FracMulti(nums[0], nums[1], nums[2], nums[3]);
        if (fracResult[0] == 0){
            isMultipliedByZero = true;
        }
        break;
    case '/':
        fracResult = FracDiv(nums[0], nums[1], nums[2], nums[3]);
        if (fracResult[1] == 0){
            isDividedByZero = true;
        }
        break;
}

String simplestNumerator = String.valueOf(fracResult[0]);
String simplestDenominator = String.valueOf(fracResult[1]);

3.3 用户答题

刚解决掉一个,在统一屏幕又看到了另一个模块的标题 “用户答题”。

  • 输入部分

    这个就简单了,它只需要两个参数: problemsCount 和 standardAnswer 。甚至连 problemsCount 都可以去掉,只剩下一个。

  • 输出部分

    这个更简单:无。

老套路,创建空方法:

private static void fracHandleUserAnswer(int problemsCount, String[] standardAnswer) {

}

然后把代码转移进去。

...

实在不想再复制了,就把结果略了吧。

3.4 生成标准答案

在 3.2 这一节中,完成了对 switch 的简化。正如 switch 上面的模块注释所说:

// 求结果

switch 连同它下面的 if - else if - else 一起,是为了根据 nums[] 的内容生成答案。

  • 输入部分
    数值 nums[] 和运算符 op[opIndex[0]] 。这个运算符可以提前获取,因此不必将 op[] 和 opIndex[] 都传进去。
  • 输出部分
    String 类型的结果

创建空方法:

private static String fracGetStandardAnswer(int[] nums, char operator) {

}

这次的移入只需将 op[opIndex[0]] 替换成 operator 。

3.5 随机生成分数

再往上看,最明显的需要提取的部分就是随机生成真分数这部分了。

// 第一个真分数。nums[0]为分子,nums[1]为分母
for (int index = 0; index < 2; index++) {
    nums[index] = (int) (Math.random()
            * (MAX_NUM - MIN_NUM) + MIN_NUM);

    if (index == 1) {
        // 保证分子不大于分母,以及分母不为零
        while (nums[index - 1] > nums[index] || nums[index] == 0) {
            nums[index] = (int) (Math.random()
                    * (MAX_NUM - MIN_NUM) + MIN_NUM);
        }
    }
}

// 第二个真分数。nums[2]为分子,nums[3]为分母
for (int index = 2; index < 4; index++) {
    nums[index] = (int) (Math.random()
            * (MAX_NUM - MIN_NUM) + MIN_NUM);

    if (index == 3) {
        // 保证分子不大于分母,以及分母不为零
        while (nums[index - 1] > nums[index] || nums[index] == 0) {
            nums[index] = (int) (Math.random()
                    * (MAX_NUM - MIN_NUM) + MIN_NUM);
        }
    }
}

生成第一个真分数和生成第二个真分数的代码几乎完全一样,只有 index 不一样。

这部分的代码可以简化,不过还是先尽量保持原样,等以后再简化吧。

  • 输入部分
    只有 MAX_NUM 和 MIN_NUM 。不过这两个是我为了消灭 magic number 而创建的,完全可以放到新建的方法里面。
  • 输出部分
    第一个部分生成 nums[0] 和 nums[1] ,第二个部分生成 nums[2] 和 nums[3] 。它们共同的特征是:生成两个数。
    这个套路我们是不是见过?在 FracAdd() 那里就是生成两个数。那么这次我们也这么做。

创建一个空壳函数:

private static int[] getRandomFrac(){

}

转移代码:

private static int[] getRandomFrac(){
    final int MAX_NUM = 10;
    final int MIN_NUM = 1;
    int[] nums = new int[2];
    for (int index = 0; index < 2; index++) {
        nums[index] = (int) (Math.random()
                * (MAX_NUM - MIN_NUM) + MIN_NUM);

        if (index == 1) {
            // 保证分子不大于分母,以及分母不为零
            while (nums[index - 1] > nums[index] || nums[index] == 0) {
                nums[index] = (int) (Math.random()
                        * (MAX_NUM - MIN_NUM) + MIN_NUM);
            }
        }
    }
    return nums;
}

原来的地方变成:

int[] randomNums;
// 第一个真分数。nums[0]为分子,nums[1]为分母
randomNums = getRandomFrac();
nums[0] = randomNums[0];
nums[1] = randomNums[1];

// 第二个真分数。nums[2]为分子,nums[3]为分母
randomNums = getRandomFrac();
nums[2] = randomNums[0];
nums[3] = randomNums[1];

现在 for 循环已经从 131 行缩减到 40 行了,还能更少吗?能!

3.6 随机产生操作符

在原来的代码中,先随机生成运算符的下标,当用到运算符的时候再去 op[] 里取出字符。有点儿绕。

final int OP_MAX = 4;
int[] opIndex = new int[OP_MAX]; // 保存操作符下标

// 产生两个操作符下标。乘以2最多得2
for (int index = 0; index < 2; index++) {
    opIndex[index] = (int) (Math.random() * baseNum + 1);
}

char[] op = {' ', '+', '-', '*', '÷'};
...
char operator = op[opIndex[0]];

可以把这部分的代码抽取出来,直接做成返回运算符的模块。

  • 输入部分
    只有一个: baseNum 。当产生操作符的时候,需要它的值来控制是否能生成乘除运算符。不过它的值是由更上面的代码决定的,能否延迟决定它的值呢?因为这样可以让看代码的人不必知道 baseNum 的值。
  • 输出部分
    字符型的运算符。

先进入支线任务:解决 baseNum 的问题。哪些代码决定 baseNum 的值呢?

Scanner inputs = new Scanner(System.in);
char useMultiAndDiv = inputs.next().charAt(0);
int baseNum = 0; // 控制操作符是否能为乘号和除号
if (useMultiAndDiv == 'Y' || useMultiAndDiv == 'y') {
    baseNum = 4;
} else if (useMultiAndDiv == 'N' || useMultiAndDiv == 'n') {
    baseNum = 2;
}

如果延迟 baseNum 的赋值,如何保证其效果仍然按照用户的输入来决定?可以用布尔值来代替 baseNum 作为是否使用乘除的状态。

char choice = inputs.next().charAt(0);

boolean useMultiAndDiv = false;
if (choice == 'Y' || choice == 'y') {
    useMultiAndDiv = true;
} else if (choice == 'N' || choice == 'n') {
    useMultiAndDiv =false;
}

那么在后面的代码中,只需判断 useMultiAndDiv 的值就知道该给 baseNum 赋什么值了。

回到提取代码的主线任务来,创建空壳新方法:

private static char getRandomOp(boolean useMultiAndDiv){

}

转移代码:

private static char getRandomOp(boolean useMultiAndDiv){
    int baseNum = useMultiAndDiv ? 4:2;

    char[] ops = {' ', '+', '-', '*', '÷'};
    int opIndex = (int) (Math.random() * baseNum + 1);
    return ops[opIndex];
}

在原来的地方,只需这样调用:

char operator = getRandomOp(useMultiAndDiv);

麻烦的随机运算符终于没了。继续?

3.7 输出真分数式子

for 循环里面剩下的比较大块的代码就剩下面这部分了:

// 输出整个式子,index 表示正在输出的数字的位置
boolean shouldOutputOp = true;
for (int index = 0; index < 4; index++) {
    if (index % 2 == 0) {
        System.out.print("(" + nums[index] + "/");
    } else if (index % 2 == 1) {
        System.out.print(nums[index] + ")");
        if (shouldOutputOp) {
            System.out.print(operator]);
            shouldOutputOp = false;
        } else {
            System.out.println("=");
        }
    }
}

这部分其实可以简化得很简单的,不过还是按照 “尽量不修改原来代码” 的原则来,把优化放到后面。

  • 输入部分
    由于是输出式子,因此只需两样: nums[] 、 operator 。
  • 输出部分
    这个部分只是将式子 print 出来,不需要传出什么结果。

创建空方法:

private static void printFrac(int[] nums,char operator){

}

代码迁移:

// 输出整个式子,index 表示正在输出的数字的位置
private static void printFrac(int[] nums,char operator){
    boolean shouldOutputOp = true;
    for (int index = 0; index < 4; index++) {
        if (index % 2 == 0) {
            System.out.print("(" + nums[index] + "/");
        } else if (index % 2 == 1) {
            System.out.print(nums[index] + ")");
            if (shouldOutputOp) {
                System.out.print(operator);
                shouldOutputOp = false;
            } else {
                System.out.println("=");
            }
        }
    }
}

原来的地方就剩下一句话:

printFrac(nums,operator);

3.8 真分数模式最后的处理

猜猜 for 循环剩下几行?

// 输出题目并计算题目的答案
int[] nums = new int[4];
for (int i = 0; i < problemsCount; i++) {
    System.out.print("(" + (i + 1) + ") ");

    int[] randomNums;
    randomNums = getRandomFrac();
    nums[0] = randomNums[0];
    nums[1] = randomNums[1];

    randomNums = getRandomFrac();
    nums[2] = randomNums[0];
    nums[3] = randomNums[1];

    char operator = getRandomOp(useMultiAndDiv);

    printFrac(nums,operator);

    standardAnswer[i] = fracGetStandardAnswer(nums, operator);

} // 输出题目并计算答案结束

只剩下 19 行啦!

赶紧运行一下试试,看能否正常运行。居然没什么问题!因为一开始就坚持 “尽量不修改原来代码” 的原则,只要原来的代码不出现问题,提取之后也不会出问题。

等等!原来的代码有问题怎么办?经过一番测试之后……果然有!

来看看原来的分数乘法部分(...三个点表示省略中间代码):

// 不求最大公倍数,直接乘对方分母
resultDenominator = nums[1] * nums[3];
leftNumerator = nums[0] * nums[3];
rightNumerator = nums[2] * nums[1];
...
// (等级二)选择分支三
resultNumerator = nums[0] * nums[2];
gcd = Gcd(resultNumerator, resultDenominator);
// 分子有0则结果为0
if (nums[0] == 0 || nums[2] == 0) {
    isMultipliedByZero = true;
}
simplestNumerator = String.valueOf(resultNumerator / gcd);
simplestDenominator = String.valueOf(resultDenominator / gcd);

在统一分母时,分子也与分母同乘一个数。两分母相乘得到新的统一的分母。这时候的状态是产生了新的表达式,还没开始乘法。接下去应该是两个新分子相乘,两个新分母相乘。

问题来了!上面的代码中,分母没有乘,分子相乘时用的是旧的值。

不过神奇的是,在当前版本的代码中,我们只需往 FracMulti() 里添加:

resultDenominator = resultDenominator * resultDenominator;

这是因为之前往里面填充代码的时候,看到加减乘除这四个操作基本相同,就从上面的减法里复制代码过来做少量的修改。

3.9 轮到整数模式了

不知道你发现没,整数模式和分数模式的用户输入处理方式不一样!整数模式下是出一题回答一题,分数模式下是题目全部出来再回答。为何不统一起来呢?这样就可以把这两部分合并起来了。

3.3 用户答题 中,已经将分数的处理封装起来了。它接收两个参数: problemsCount 和 standardAnswer[] 。

problemsCount 很容易,在进入 IntegerMode 的时候就有了。现在需要创建 standardAnswer[] 数组,在原来处理输入的地方换上存储答案到 standardAnswer 。接着在 for 循环外面使用 handleUserAnswer() 。

其他没什么说的,比真分数模式的处理简单很多。修改后的代码如下:

// (等级一)选择分支一
private static void IntegerMode(int problemsCount) {
    String[] standardAnswer = new String[PROBLEMS_COUNT_MAX];
    for (int i = 0; i < problemsCount; i++) {
        System.out.print("(" + (i + 1) + ") ");

        // 防止出现不好处理的0,很不严谨不可取
        int[] nums = new int[2];
        nums[0] = (int) (Math.random() * 10 + 1);
        nums[1] = (int) (Math.random() * 10 + 1);

        // 根据随机的运算符进行相应的操作
        int standardResult;
        char operator = getRandomOp(true);
        switch (operator) {
            case '+':
                standardResult = addInteger(nums);
                break;
            case '-':
                standardResult = subInteger(nums);
                break;
            case '×':
                standardResult = multiInteger(nums);
                break;
            case '÷':
                standardResult = divInteger(nums);
                break;
            default:
                standardResult = 0;
                break;
        }
        System.out.println(nums[0] + " " + operator + " " + nums[1] + " = ");
        standardAnswer[i] = String.valueOf(standardResult);
    }
    handleUserAnswer(problemsCount,standardAnswer);
}

// (等级二)选择分支一
private static int addInteger(int[] nums) {
    int result = 0;
    for (int num : nums) {
        result += num;
    }
    return result;
}

// (等级二)选择分支二
private static int subInteger(int[] nums) {
    if (nums[0] < nums[1]) {
        int t = nums[0];
        nums[0] = nums[1];
        nums[1] = t;
    }
    return nums[0] - nums[1];
}

// (等级二)选择分支三
private static int multiInteger(int[] nums) {
    int result = 1;
    for (int num : nums) {
        result *= num;
    }
    return result;
}

// (等级二)选择分支四
private static int divInteger(int[] nums) {
    if (nums[0] < nums[1]) {
        int t = nums[0];
        nums[0] = nums[1];
        nums[1] = t;
    }

    // 保证能整除
    if (nums[0] % nums[1] != 0) {
        nums[0] = (int) (Math.random() * 10 + 1) * nums[1];
    }
    return nums[0] / nums[1];
}

3.10 接下去是另一个世界

可算是把大部分代码都模块化了!真是不容易啊!

要结束了么?

We still have a long way to go.

为什么?尽管进行了一定的模块化,但所有的代码仍然在一个 .java 文件里面。并且随着模块化的进行,方法越来越多,又杂又乱。

我们似乎可以将这些方法划分开来,并且放到不同的 .java (或者说不同的类) 里面。好像有点麻烦,不过所幸之前为此做了准备。

还记得 第 3 步 刚开始说的两点吗?

  • 方法内不能直接使用全局变量。如果要使用外部的变量,使用参数传递;
  • 每个方法在处理完一件事之后,使用 return 返回结果。

这为之后的工作提供了便利。

接下去就是另一个世界,你准备好了吗?

posted @ 2017-03-09 20:22  schaepher  阅读(997)  评论(1编辑  收藏  举报