需求分析
使用Java 语言完成一个自动生成四则运算试题的程序,一个命令行软件,启动之后选择需要生成的试题类型,然后在一个循环中生成这些试题。
功能设计
- 基本功能
- 自动生成10道100以内的2个操作数的四则运算,包括加减乘除,运算的结果在100以内
- 删除重复模式 。
- 可以控制是否生成乘法和除法 。
- 可以控制数字范围,相关参数可以控制。
- 运算的数字可以为负数 。
- 生成的运算题目存储到外部文件
result.txt
中 。
- 扩展功能
- 题目生成后进入下一步,可以选择继续生成或者重新选择模式来生成。
- 生成的题目可以有多个数值参与运算 。
- 生成的结果可以包含负数 。
- 除法试题结果显示为余数形式,还是小数形式。
- 选择是否输出带有答案的题目。
设计
客户端只用来处理与用户的交互,获取随机数,计算数据等功能都通过其他类来实现
MathFunction
设置一个模型类,用来表示一个等式,包含有等式的数据,计算出来结果也要添加到MathFunction 中的result
中。获取结果的函数getResult
函数使用装饰者模式实现。MathValue
这是一个模型类,为了完成多个值参与运算,需要使用tree
数据模型来表示等式。RandomCreator
只是一个类,使用单例模式,通过getRandom
方法获取一个随机数。Calculator
计算类的接口,用来计算等式结果,因为需要判断最后的结果是否符合,而且还要展示计算的答案,计算类必不可少。CalculatFactory
工厂类,用于根据客户端提供的函数,选择相应的Calculator
来计算结果。AddCalculator
继承自Calculator
实现加法运算。SubCalculator
继承自Calculator
实现减法运算。MultiCalculator
继承自Calculator
实现乘法运算。DivCalculator
继承自Calculator
实现除法运算。MathMaster
用于管理生成函数的关键类。需要计算10个算式,那么就需要使用while
或者for
循环,循环的内容也移动到这个类中,计算出来的算式保存到这个类中的字段mathFunctions
。通过客户端的设定生成相应的算式,并通过客户端调用这里的函数printFunctions
打印出来。MathElement
MathFunction
和MathValue
都继承自MathElement
,拥有getValue 函数,并由MathFunction
和MathValue
实现。
模型相关类图
简单工厂类图
测试运行
普通的运行一遍
- 全部使用默认选项,实现默认功能
- 删除重复项目
- 输出结果
展示扩展功能
- 实现扩展功能中的使用当前配置再次生成不同的题目
- 可以继续选择配置,或者退出功能
展示参数可以控制功能
- 可以控制试题数量
- 控制题目类型,如乘除法
- 负数参与运算
展示产生负数的结果
- 结果可以为负数选项
粘贴代码
MathFunction
中获取结果的方法public MathFunction(MathElement leftElement, int opeator, MathElement rightElement) { super(); this.leftElement = leftElement; this.opeator = opeator; this.rightElement = rightElement; calculator = CalculatorFactory.getCalculator(opeator); op = calculator.getOpeator(); } public int getOpeator() { return opeator; } @Override public int getValue() { return calculator.calc(leftElement.getValue(), rightElement.getValue()); } public String getOutput(boolean result) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(leftElement.getValue() + " " + op + " " + rightElement.getValue()); if (result) { stringBuilder.append(" = " + getValue()); } return stringBuilder.toString(); }
CalculatorFactory
中获得相应的类的方法public static Calculator getCalculator(int op) { switch (op) { case 0: return new AddCalculator(); case 1: return new SubCalculator(); case 2: return new MultiCalculator(); case 3: return new DivCalculator(); default: throw new IllegalArgumentException("Unexpected value: " + op); } }
RandomCreator
类,使用单例模式public class RandomCreator { private static RandomCreator creator; private Random random; private RandomCreator() { random=new Random(System.currentTimeMillis()); } public int getRandom(int range) { return random.nextInt(range+1); } public static RandomCreator getCreator() { if (creator == null) { creator=new RandomCreator(); } return creator; } }
MathMaster
中获取试题的主要代码/** * 获取试题 */ public void get() { mathFunctions.clear(); for(int functionIndex=0;functionIndex<onceCreateCount;) { int left=RandomCreator.getCreator().getRandom(onceCreateValueRange); if (whetherHasNegativeNumber) { if (RandomCreator.getCreator().getRandom(1)==0) {//0 就添加负号 left=0-left; } } int right=RandomCreator.getCreator().getRandom(onceCreateValueRange); if (whetherHasNegativeNumber) { if (RandomCreator.getCreator().getRandom(1)==0) {//0 就添加负号 right=0-right; } } int op=getRandom();//根据当前是否含有乘除法获取不同的运算符 MathFunction mathFunction=new MathFunction(new MathValue(left),op,new MathValue(right)); int result=mathFunction.getValue(); if (checkResult(result)) {//如果结果不符合,忽略这次结果 mathFunctions.add(mathFunction); functionIndex++; }//忽略的结果不会增加 } }
客户端中用于控制是否按照当前配置重新生成
String getInputString = scanner.next(); char charAt = getInputString.charAt(0); MathMaster master = new MathMaster(); while (!needExit(charAt)) { //···省略部分代码 boolean exit=false;//退出下面的while 时是否还有退出整体的while while (true) { master.get(); master.printFunctions(false); FileWriter fileWriter=new FileWriter("result.txt");//打印到文件中 fileWriter.write(master.getFuncString()); fileWriter.flush(); fileWriter.close(); System.out.println("是否需要输出带有结果的题目,Y/e"); getInputString = scanner.next(); charAt = getInputString.charAt(0); if (needExit(charAt)) { exit=true; break; } else if (whetherCouldGoon(charAt)) { if (charAt == 'y' || charAt == 'Y') { master.printFunctions(true); } System.out.println("重新开始,还是再生成一遍,Y/n/e"); getInputString = scanner.next(); charAt = getInputString.charAt(0); if (needExit(charAt)) { exit=true; break; } else if (whetherCouldGoon(charAt)) { if (charAt == 'y' || charAt == 'Y') { //不做任何改变就可以在输出 }else { break;//重新开始 } } } else { System.out.println(input_error);//重新开始 break; } } if (exit) { break; } //···省略部分代码 }
AddCalculator
类的代码,其他运算与此类似public class AddCalculator implements Calculator { @Override public int calc(int left, int right) { return left+right; } /** * 从这里获取操作符,就不需要判断0 1 2 3 对应的+ - * (/) 了 */ @Override public char getOpeator() { return '+'; } }
总结
- 想要达成模块化最首选的就是函数,把一部分操作放到函数中,便于理解程序的运行,相同的操作还能复用代码,就像代码中的
needExit
和whetherCouldGoon
一样。 - 再进一步就是类,遵守“知识最少原则” 当前的类不需要了解的操作或者实体,那就把它提取到其他的类中,然后将相应的操作都放到这个类中,一是可以减少一个类的大小,也减少了模块间的依赖,当修改一部分代码时,只需要修改当前类就可以,不需要动其他的类,其他的类也不需要参与重新编译的过程了。
- 运用设计模式。在这个项目中使用了“单例模式”和“简单工程模式”,使用了单例模式,
MathMaster
就不需要管理随机数的生成。使用了简单工厂模式,这样MathMaster
只需要获取答案,而不需要管理当前执行的是什么操作,顺利进行解耦。 - 灵活运用接口和超类,达到代码复用,多态,这也是设计模式的基础。
- 用面向对象的思想,万事万物皆是对象。
- 单一对象原则,每个类只需要完成自己的操作就好了,如果一个类使用了很多的类,那就要考虑是不是要把这些内容都放到其他类中,然后从这里调用,而不是让一个类做太多的工作。
PSP
PSP2.1 | 计划任务 | 计划共完成需要的时间(min) | 实际完成需要的时间(min) |
---|---|---|---|
Planning | 计划 | 10 | 5 |
Estimate | 估计这个任务需要多少时间,并规划大致工作步骤 | 10 | 10 |
Development | 开发 | 95 | 253 |
Analysis | 需求分析(包括学习新技术) | 10 | 8 |
Design Spec | 生成设计文档 | 10 | 10 |
Design Review | 设计复审(和同事审核设计文档) | 10 | 10 |
Coding Standard | 代码规范(为目前的开发制定合适的规范) | 5 | 5 |
Design | 具体设计 | 10 | 20 |
Coding | 具体编码 | 30 | 160 |
Code Review | 代码复审 | 10 | 10 |
Test | 测试(自我测试,修改代码,提交代码) | 20 | 30 |
Reporting | 报告 | 25 | 25 |
Test Report | 测试报告 | 15 | 10 |
Size Measurement | 计算工作量 | 5 | 10 |
Postmortem & Process Improvement Plan | 事后总结,并提交过程改进计划 | 5 | 5 |