Coding.net仓库地址:https://coding.net/u/zhh1011/p/QuestionMaker/git
克隆地址:https://git.coding.net/zhh1011/QuestionMaker.git
测试步骤:
1.进入src文件夹
2.在命令行输入javac -encoding utf-8 Main.java
3.回车再输入java Main 20
4.回车,将会在根目录下(与src同级)产生result.txt
说在前面:本篇博文用于提交本人课程作业,若读者有兴趣也可访问https://edu.cnblogs.com/campus/nenu/2016SE_NENU/homework/1656了解作业要求。
一、需求分析
从题目中的基本要求如下:
1、输入n,能输出n个四则运算题目在与main文件同目录的“result.txt”文件中;
2、输出的题目中,数字在0~100之间,运算符在3~5个之间;
3、输出的题目需含有答案,且答案不得包含负数与分数;
4、题目在运算中不得包含负数与分数;
二、功能设计
能够根据用户输入的参数n随机产生n道符合要求的练习题,自动算出答案,并将式子与答案以文档的形式呈现。
三、设计实现
在我的src文件下共有两个包以及Main类:
首先描述一下Main类:
Main类的main方法:
public static void main(String[] args) { //用于规范用户输入的部分--------- try { NumberException exception = new NumberException(); times = Integer.valueOf(args[0]); times = exception.testNumber(times); }catch (Exception e){ System.out.println("请输入1~1000的数字"); times = inPut(); } //--------------------------- Outer outer = new Outer(times); }
以及用于递归来规范用户输入的inPut方法:
//递归方法,用于规范用户输出 private static int inPut(){ try { //自定义的异常,用于判断输入是否处于1~1000 NumberException exception = new NumberException(); Scanner input = new Scanner(System.in); times = input.nextInt(); times = exception.testNumber(times); }catch (Exception e){ System.out.println("请输入1~1000的数字"); inPut(); } return times; }
conifg包下放着静态的数据如学号,姓名等,以及自定义的用于限制用户输入的异常类,并不复杂,在这里就不加赘述了。
main包下放着entity包与worker包:
entity包内放着题目的实体类Question类:
在这里我要展示一下toString()方法:
//规范化输出题目字符串
@Override
public String toString() {
Object[] box = new Object[nums*2+4];
box[nums*2+1] = '=';
box[nums*2+2] = results;
box[nums*2+3] = "\r\n";
for(int i = 0; i < nums*2+1;i=i+2){
box[i] = number[i/2];
}
for(int i = 1; i < nums*2;i=i+2){
box[i] = chars[i/2];
}
return Arrays.toString(box).replace('[',' ').replace(']',' ').replace(',',' ');
}
worker包内有这三个类:
Maker类用于制造完整的题目并调用Tester类的对象对题目进行测试(并在测试过程中获取答案):
在这里就简单的展示一下它的结构(因为真的没啥说的)
Tester类是整个项目里相对来讲最复杂的模块,我会放在代码展示部分解释运算部分,在这里介绍我用到的构建方式:
下面是Test类的数据域、构造器以及方法:
以及运算模块(我在这里练习了一下Java里的“策略模式”)
最后是Outer类:
最后在Outer的构造器里直接完成输出:
//Outer构造器
public Outer(int times){
this.times = times;
this.boxs = new String[times+1];
Question question;
boxs[0] = ID;
for (int i = 1;i <= times;){
maker.makeQuestion();
maker.testQuestion();
if(!maker.getQuestion().isUseful())
continue;
String box = maker.getQuestion().toString();
boxs[i] = box;
i++;
}
//在这里写入"result.txt"文件
outQuestion(boxs);
}
四、算法实现
算法很简单(甚至可以说没有),只是运用java自己的链表(ArrayList与List)自带的方法和递归完成简单的运算,以及if-else判断算符的优先级,使用策略模式优化代码结构,并没有使用所谓的调度场算法或者其他一些算法来实现。
五、测试运行
首先编译源文件(需转换为UTF-8进行编码,否则会中文报错),然后进行非法输入测试:
进行正确输入后检查输出是否正确:
六、展示部分代码
我本次项目不像其他人或拥有高超的算法或实现了更多的功能,我最满意的地方在于自己在这次项目里对Java程序设计的一种实践。通过继承、组合,用很多包、类,甚至采用了策略设计模式去模块化自己的代码,更简易的去修改去改进,添加更多的注释去帮助别人理解自己的代码并进行再开发。
举个例子比如有关运算部分的代码,使用了策略设计模式去模块化运算操作:
1 //这一部分属于Test类中的方法-----------------------------------------
2 //策略模式的方法
3 private Box function(Function f, Box box){
4 return f.run(box);
5 }
6
7 //计算题目的答案
8 private int getResult(Box box){
9 //用于判断优先级的部分
10 if(box.listC.indexOf('*')>box.listC.indexOf('÷'))
11 box = function(new Multiplication(),box);
12 else if(!(box.listC.indexOf('÷')==(-1)))
13 box = function(new Division(),box);
14 else if(box.listC.get(0) == '+')
15 box = function(new Plus(),box);
16 else if(box.listC.get(0) == '-'){
17 box = function(new Subtraction(),box);
18 }
19 //实现递归的部分
20 if(box.listC.isEmpty()){
21 if(box.listN.get(0)==-1)
22 questionTest.setUseful(false);
23 return box.listN.get(0);
24 }
25 else {
26 return getResult(box);
27 }
28 }
29 //这一部分属于Test类中的方法-----------------------------------------
30 //策略部分开始--------------------------------------------|
31 class Function{
32 Box run(Box box){
33 return box;
34 }
35 }
36
37 //乘法运算部分
38 class Multiplication extends Function{
39 @Override
40 Box run(Box box){
41 box.listN.set(box.listC.indexOf('*'),box.listN.get(box.listC.indexOf('*'))*box.listN.get(box.listC.indexOf('*')+1));
42 box.listN.remove(box.listC.indexOf('*')+1);
43 box.listC.remove(box.listC.indexOf('*'));
44 return box;
45 }
46 }
47
48 //除法运算部分
49 class Division extends Function{
50 @Override
51 Box run(Box box) {
52 if(!(box.listN.get(box.listC.indexOf('÷')+1)==0)&&box.listN.get(box.listC.indexOf('÷'))%box.listN.get(box.listC.indexOf('÷')+1) == 0){
53 box.listN.set(box.listC.indexOf('÷'),box.listN.get(box.listC.indexOf('÷'))/box.listN.get(box.listC.indexOf('÷')+1));
54 box.listN.remove(box.listC.indexOf('÷')+1);
55 box.listC.remove(box.listC.indexOf('÷'));
56 }
57 else{
58 //答案非法,直接停止递归
59 box.listC.clear();
60 box.listN.set(0,-1);
61 return box;
62 }
63 return box;
64 }
65 }
66
67 //加法部分
68 class Plus extends Function{
69 @Override
70 Box run(Box box) {
71 box.listN.set(box.listC.indexOf('+'),box.listN.get(box.listC.indexOf('+'))+box.listN.get(box.listC.indexOf('+')+1));
72 box.listN.remove(box.listC.indexOf('+')+1);
73 box.listC.remove(box.listC.indexOf('+'));
74 return box;
75 }
76 }
77
78 //减法运算部分
79 class Subtraction extends Function{
80 @Override
81 Box run(Box box) {
82 if(box.listN.get(box.listC.indexOf('-'))-box.listN.get(box.listC.indexOf('-')+1) > 0) {
83 box.listN.set(box.listC.indexOf('-'), box.listN.get(box.listC.indexOf('-')) - box.listN.get(box.listC.indexOf('-') + 1));
84 box.listN.remove(box.listC.indexOf('-') + 1);
85 box.listC.remove(box.listC.indexOf('-'));
86 }
87 else {
88 //答案非法,直接停止递归
89 box.listC.clear();
90 box.listN.set(0,-1);
91 return box;
92 }
93 return box;
94 }
95 }
96 //策略部分结束----------------------------------------|
97
98 //封装数字与运算符的盒子(Box...)
99 class Box{
100 List listN = new ArrayList<>();
101 List listC = new ArrayList<>();
102
103 Box(List listN,List listC){
104 this.listC = listC;
105 this.listN = listN;
106 }
107 }
这些都让我感觉更...更舒适,不去直观的面对真正的实现与细节,而是通过组合,继承,创建一个对象去调用它的方法去实现功能。让我自己思路清晰,明白自己需要什么,明白自己要干什么。我可以随时完成一个小模块的实现,也可以在日常的各种活动中,去思考一个小模块如何去实现,利用起碎片化的时间,甚至有时候灵机一动解决一个模块里的小问题。在后期修改错误阶段也可以很简单就改变一个模块的行为,因为它并不会牵扯很多东西,只需要修改调用它的模块就行了,它处理的数值它内部的变化丝毫不会影响其他东西。
七、总结
为了让自己的代码模块化,首先我给问题建立了实体类来封装有关问题的信息(包含的数字,字符,字符数量等等),随后将功能模块化,生成与测试分离,输出与生成分离。且有关问题字符串形式的输出也是在Question类的toString()方法中实现的。输出(Outer)模块仅仅是负责调用生成模块生成符合的问题对象并规范输出,生成模块(Maker)也只是负责随机生成题目对象然后调用测试模块去测试并同时生成答案,测试(Tester)模块内部Tester类自己只负责测试,运算部分由整个Function类及其子类完成。
PSP2.1 |
任务内容 |
计划共完成需要的时间(h) |
实际完成需要的时间(h) |
Planning |
计划 |
1.5 |
3 |
· Estimate |
· 估计这个任务需要多少时间,并规划大致工作步骤 |
1.5 |
3 |
Development |
开发 |
20 |
22 |
· Analysis |
· 需求分析 (包括学习新技术) |
2 |
3 |
· Design Spec |
· 生成设计文档 |
0 |
0.5 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
0 |
0.5 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
0 |
0 |
· Design |
· 具体设计 |
2 |
3 |
· Coding |
· 具体编码 |
15 |
11
|
· Code Review |
· 代码复审 |
0 |
1 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
1 |
4 |
Reporting |
报告 |
3 |
4 |
· Test Report |
· 测试报告 |
0.5 |
0 |
· Size Measurement |
· 计算工作量 |
0.5 |
0.5 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
2 |
3.5
|
最令我意外的当属开发最后的测试阶段了,错误层出不断,许多地方和自己想象的不一样。对很多东西进行了大改,对最初的架构做了很大的改变(最初的架构可以在我之前博客:http://www.cnblogs.com/zanghh/p/8604724.html),设计和现实的差距真的很大,只有在运行和测试的时候才能发现(测试并没有很规矩的去单元测试,只是写了一些很简单的方法自己去看输出是否符合预期,相关代码可以在test文件下看到)。其次就是计划阶段,断断续续一直在想那些,以为一个小时就能解决的东西,大概想了有两三个小时,只能怪自己一直想着如何优化了(实际最后都没去优化)。总的来说这次的实践让我受益匪浅,有机会去实践一些自己以前学到的一些设计方式和思想,也看了很多新的设计理念。感受一项目从无到有的痛苦与愉悦(写不出来的痛苦、项目完成的那一刻的激动真的无语言表...后来的测试又是当头一棒...)。真的是学会了很多复习了很多实践了很多(甚至打开了有半年多没看过的API文档),如果你是一名后来者,我真诚的建议你去自己敲一个从无到有的项目,并按正常的流程开发,绝对是一次不可多的体验(特别是第一次的话),最后也希望如果你也这样做得话,也请把自己的经验也写成一篇博客与人分享,让这种体验传播的更为广泛。