• 模块化一:提取模块


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

    第 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("答对了,恭喜
    ");
                    } else {
                        System.out.println("答错了,加油
    ");
                    }
                }
                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("答对了,恭喜
    ");
                    } else {
                        System.out.println("答错了,加油
    ");
                    }
                }
                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 返回结果。

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

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

  • 相关阅读:
    用于验证码图片识别的类(C#源码)
    一起学习Windows Phone7开发(十八. Windows Phone7 Toolkit)
    Microsoft Enterprise Library 5.0 系列(五) Data Access Application Block
    用C#生成随机中文汉字验证码的基本原理
    win7中安装vs2008的升级成正式版的方法
    sqlserver 常用存储过程集锦
    Windows Phone 7 Developer Tools & Training Kit 正式版发布!
    c# 图片存储方式
    C# winform 上传文件 (多种方案)
    一致性Hash算法(KetamaHash)的c#实现
  • 原文地址:https://www.cnblogs.com/schaepher/p/6527600.html
Copyright © 2020-2023  润新知