作业基本信息…
这个作业属于哪个课程 | FZU-CS-SE |
---|---|
这个作业要求在哪里 | https://bbs.csdn.net/forums/ssynkqtd-05?spm=1001.2014.3001.6685 |
这个作业的目标 | 完成一个具有可视化界面的计算器 |
其他参考文献 |
IDE: IDEA 2022.5
OS: macOS Ventura 13.3.1
JDK: 1.8
项目依赖管理:Maven
mac上前端界面(windows上似乎没有这么美观…)
FZU-CS-SE-Calculator
运行方式:
- 直接exe启动,需要jre1.8以上的java环境配置
- 在项目目录下运行如下命令(需要有maven环境配置和jdk1.8以上配置)
mvn clean test
java -jar Calculator-1.0-SNAPSHOT.jar
Gitcode项目地址
我没有GitCode账号,就使用了我的github账号仓库地址
BlackBear2003/FZU-CS-SE-Calculator (github.com)
PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 20 |
• Estimate | • 估计这个任务需要多少时间 | 240 | 300 |
Development | 开发 | 200 | 200 |
• Analysis | • 需求分析 (包括学习新技术) | 0 | 0 |
• Design Spec | • 生成设计文档 | 20 | 20 |
• Design Review | • 设计复审 | 20 | 20 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 5 | 5 |
• Design | • 具体设计 | 30 | 30 |
• Coding | • 具体编码 | 80 | 80 |
• Code Review | • 代码复审 | 25 | 25 |
• Test | • 测试(自我测试,修改代码,提交修改) | 20 | 20 |
Reporting | 报告 | 20 | 20 |
• Test Repor | • 测试报告 | 5 | 5 |
• Size Measurement | • 计算工作量 | 5 | 5 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 10 | 10 |
合计 | 280 | 350 |
解题思路描述
因为个人比较熟悉Java语言,所以打算使用Java语言尝试一下开发这样的软件,然后了解到Java可以方便的调用其他脚本语言的引擎API,所以一开始的思路是:
使用Java AWT包作为前端UI界面面向用户使用,计算部分代码是将用户输入生成的字符串作为一个表达式,利用java对这个表达式做正则运算,让这个表达式符合javaScript的语法规范,使用JavaScript脚本引擎运行eval函数,利用脚本语言的特性方便实现我的功能。
但是后面发现我使用的Jdk1.8并不支持ES6以上的特性,所以没办法支持" ** "
这个方便计算幂运算,所以我更换了我的脚本引擎,使用Jython这个开源代码库,将表达式放到python语句中执行。然后成功实现了题目中提到的幂运算和三角函数运算,并且也实现了阶乘运算。并且支持用户自由切换三角函数是要以角度制(dgr)还是弧度制(rad)来计算。
最后引入了JTest框架对本项目书写了单元测试和覆盖率评估。
问题1
- Jdk1.8自带的javax.script包中的JS引擎并不支持ES6以上的特性
Solution:
更换为Python脚本执行。
问题2
- 用户输入的表达式并不符合脚本语言的规范
Solution:
使用Java正则表达式,将’sin(‘替换为’math.sin(’, 将 x!替换为 math.factorial(x) , 将x ^ y 替换为 x ** y
问题3
- 希望实现三角函数是要以角度制(dgr)还是弧度制(rad)来计算的切换。
Solution:
维护一个变量isRad,通过这个变量来判断当前的计算方式是什么,然后通过事件监听机制,获取到产生事件的按钮变量的引用,修改它在堆中的text属性,让前端页面刷新。同时计算部分只需要一个分支判断就可以判断是使用哪一种计算方式。
接口设计和实现过程
/*
* 把前端Frame初始化好,同时让脚本执行器引入必要的python库(比如math库)
*/
Calculator.initFrame();
/*
* 利用了Java AWT的消息机制特性,在用户点击按钮时接受对应事件,在这个函数中实现针对不同事件的不同逻辑处理
*/
ActionListener.actionPerformed(ActionEvent e);
关键代码展示
public class Calculator {
private final PythonInterpreter interpreter;
private JFrame frame;
private JTextField textField;
private String lastResult = "";
private boolean isRad = false;
private JButton evalBtn;
private JButton clearBtn;
private JButton switchRadDgrBtn;
public Calculator() {
initFrame();
System.setProperty("python.import.site", "false");
interpreter = new PythonInterpreter();
interpreter.exec("import math");
}
void initFrame() {
this.frame = new JFrame("计算器");
...
}
String toEvaluableFactorialExpression(String expression) {
String pattern = "(\\d+)!";
Pattern regex = Pattern.compile(pattern);
Matcher matcher = regex.matcher(expression);
StringBuffer output = new StringBuffer();
while (matcher.find()) {
int num = Integer.parseInt(matcher.group(1));
String replacement = "math.factorial(" + num + ")";
matcher.appendReplacement(output, replacement);
}
matcher.appendTail(output);
return output.toString();
}
public class ButtonClickListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
Object source = e.getSource();
if (command.equals("=")) {
String expression = textField.getText();
// 在用户输入的表达式中添加 "Math."
expression = expression.replaceAll("\\^", " ** ");
if (isRad) {
expression = expression.replaceAll("(sin|cos|tan)\\(", "math.$1(");
} else {
expression = expression.replaceAll("(sin|cos|tan)\\(", "math.$1( math.pi / 180.0 *");
}
expression = toEvaluableFactorialExpression(expression);
System.out.println(expression);
Object result = null;
try {
result = interpreter.eval(expression);
} catch (PyException ex) {
result = ex.getMessage().split(":")[0];
}
lastResult = result.toString();
if (lastResult.endsWith("L")) {
lastResult = lastResult.substring(0, lastResult.length()-1);
}
textField.setText(lastResult);
} else if (command.equals("AC")) {
textField.setText("");
lastResult = "";
} else if (command.equals("rad") || command.equals("dgr")) {
isRad = !isRad;
JButton clickedButton = (JButton) source;
clickedButton.setText(isRad ? "rad" : "dgr");
} else {
if (!lastResult.isEmpty()) {
textField.setText(lastResult + command);
lastResult = "";
} else {
textField.setText(textField.getText() + command);
}
}
}
}
}
性能改进
也许可以通过自己手写表达式判断,利用数据结构,将中缀表达式转为后缀表达式,利用Java的Math库,完成原生计算,不必依赖脚本语言的引擎,可以大大减小打包文件的大小。
单元测试
使用JTest框架对本项目书写了单元测试和覆盖率评估。
例如:
@Test
public void testAddition() {
calculator.getTextField().setText("2+3");
calculator.getEvalBtn().doClick();
assertEquals("5", calculator.getLastResult());
}
对每一种计算都书写了类似逻辑的单元测试:
覆盖率报告如下: