一种基于Java语言实现的简单计算器

软件工程实践2301-计算机学院-软件工程社区-CSDN社区云
作业要求软件工程实践第一次作业-CSDN社区
作业目标完成一个具有可视化界面的计算器。
其他参考文献

成品展示

Calculator演示

Gitcode项目地址

亦瑾z / SE_ex1_calculator · GitCode

PSP表格

PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划
• Estimate• 估计这个任务需要多少时间53
Development开发
• Analysis• 需求分析 (包括学习新技术)2030
• Design Spec• 生成设计文档1015
• Design Review• 设计复审55
• Coding Standard• 代码规范 (为目前的开发制定合适的规范)00
• Design• 具体设计3040
• Coding• 具体编码250300
• Code Review• 代码复审1020
• Test• 测试(自我测试,修改代码,提交修改)2040
Reporting报告
• Test Repor• 测试报告2030
• Size Measurement• 计算工作量35
• Postmortem & Process Improvement Plan• 事后总结, 并提出过程改进计划1030
合计383518

解题思路描述

问题1 图形化界面

Q: 如何使用Java实现图形化界面

  • 使用Swing框架, 用ActionListener来获取用户按下的按键

问题2 计算

Q: 如何实现计算器的底层计算

  • 使用中缀表达式转后缀表达式计算求解 ×
    • 编码时间过长, 有可能出现来不及提交的问题
  • 使用表达式计算库求解 ✓

接口设计和实现过程

计算器图形化框架搭建

  1. Calculator类中继承JFrame类, 使用两个JPanel(textPanel, 和 buttonPanel)搭建出UI界面
  2. Calculator类实现了ActionListener接口
    1. 为button添加了监视(buttons[i].addActionListener(this);)
    2. 重写了actionPerformed(ActionEvent e)方法, 当按下button后, 会调用handleEvent(String command)对事件进行处理
  3. 定义了内部类class MyKeyListener extends KeyAdapter, 监听键盘, 实现计算器的键盘输入

表达式计算

主要在private void handleEvent(String command)方法中实现

表达式处理

主要思路为: 针对不同command, 对当前表达式进行修改, 当"=".equals(command)时, 计算结果

  • < =: 进行退格操作
    • 为简化if else代码段, 将其具体实现定义到CalculatorMethod.back()
  • C: 清除表达式, 并且进行置零
  • π: 转化为3.1415……
  • 普通计算符: {"^2", "^", "/", "*", "-", "+"} 保存中间表达式
  • 复杂计算符: {"sin", "cos", "tan", "√"} 对表达式进行一些额外操作(如添加括号)
  • 普通数字: 直接加在表达式后
表达式计算

主要在CalculatorMethod类static void calculate(JTextField textField1, JTextField textField2)方法实现

  • 对两段表达式合并, 调用基于parsii库的.evaluate()方法, 对计算结果的显示效果进行处理
  • 此外, 对异常进行了catch

关键代码展示

  1. 图形界面搭建

    public Calculator() {
        setSize(new Dimension(panelWidth, panelHeight));
    
        setTitle("Calculator by zjj");
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
        // BorderLayout 是一种常用的布局管理器,将容器分为五个区域:北、南、东、西和中间。每个区域只能放置一个组件。
        setLayout(new BorderLayout());
    
        initFuncSet();
        initTextPanel();
        initButtonPanel();
    
        // 相对屏幕居中显示
        setLocationRelativeTo(null);
        setVisible(true);
    
        // 添加键盘监听
        addKeyListener(new MyKeyListener());
        // 获取焦点
        addWindowListener(new WindowAdapter() {
            @Override
            public void windowActivated(WindowEvent e) {
                // 在窗口激活时重新请求焦点
                requestFocusInWindow();
            }
        });
    }
    
  2. 对监听到的事件进行处理

    private void handleEvent(String command) {
        if (cleanTextFlag) {
            JTextFieldUtil.cleanTexts(textField1, textField2);
            cleanTextFlag = false;
        }
        String text1 = textField1.getText();
        String text2 = textField2.getText();
    
        if ("=".equals(command)) {
            // 得出答案 / 异常, 下一次按键后需要清空值
            cleanTextFlag = true;
            CalculatorMethod.calculate(textField1, textField2);
        } else if ("<=".equals(command)) {
            CalculatorMethod.back(textField2);
        } else if ("C".equals(command)) {
            if (text2.equals("0") || text2.isEmpty()) {
                JTextFieldUtil.cleanTexts(textField1, textField2);
            } else {
                CalculatorMethod.zeroing(textField2);
            }
        } else if ("π".equals(command)) {
            textField2.setText(String.valueOf(Math.PI));
        } else if (simpleFuncSet.contains(command)) {
            // 输入了普通计算符, 暂存中间的表达式
            textField1.setText(text1 + text2 + command);
            JTextFieldUtil.cleanText(textField2);
        } else if (complexFuncSet.contains(command)) {
            // 输入了复杂计算符, 暂存中间的表达式, 但需要补()
            if ("√".equals(command)) {
                command = "sqrt";
            }
            textField1.setText(text1 + command + "(" + text2 + ")");
            JTextFieldUtil.cleanText(textField2);
        } else {
            if ("0".equals(text2) && ! ".".equals(command)) {
                text2 = "";
            }
            textField2.setText(text2 + command);
        }
    }
    

性能改进

handleEvent方法中使用HashSet对command进行分类判断, 将查询时间复杂度将至为O(1)

单元测试

使用parsii表达式计算库, 不需要对库本身做过多测试, 主要测试某些可能的command是否会出错

使用JUnit测试框架, 对计算机中的主要功能进行了测试

image-20230928192908837

public class CalculatorMethodTest {
    JTextField textField1 = new JTextField();
    JTextField textField2 = new JTextField();

    @Test
    public void testSin() {
        textField2.setText("sin(0.5*3.1415)");
        CalculatorMethod.calculate(textField1, textField2);
        System.out.println(textField2.getText());
    }

    @Test
    public void testCos() {
        textField2.setText("cos(0.5*3.1415)");
        CalculatorMethod.calculate(textField1, textField2);
        System.out.println(textField2.getText());
    }

    @Test
    void testTan() {
        textField2.setText("tan(0.5*3.1415)");
        CalculatorMethod.calculate(textField1, textField2);
        System.out.println(textField2.getText());
    }

    @Test
    public void testSqrt() {
        textField2.setText("sqrt(81)");
        CalculatorMethod.calculate(textField1, textField2);
        System.out.println(textField2.getText());
    }

    @Test
    public void testDivZero() {
        textField2.setText("9/0");
        CalculatorMethod.calculate(textField1, textField2);
        System.out.println(textField2.getText());
    }

    @Test
    public void testIllegalExpression1() {
        textField2.setText("9//");
        CalculatorMethod.calculate(textField1, textField2);
        System.out.println(textField2.getText());
    }

    @Test
    public void testIllegalExpression2() {
        textField2.setText("9+-*/");
        CalculatorMethod.calculate(textField1, textField2);
        System.out.println(textField2.getText());
    }
}
  • 关于覆盖率的优化: 针对边界值和极端情况进行测试,例如最大值、最小值、空值等。这有助于发现在边界条件下可能存在的错误和异常情况。并且针对无法计算的表达式进行异常的处理。
  • 但是图形化界面的计算器并不能够直接进行单元测试,在计算器的输入模块中,有可能依然存在bug

异常处理

调用evaluateExpression后, 对可能抛出的异常ParseException, EvaluationException进行catch

try {
    result = evaluateExpression(expression);
    textField1.setText(expression + "=");

    if (result.intValue() == result) {
        // 结果为整数,去掉.0
        textField2.setText(String.valueOf(result.intValue()));
    } else {
        textField2.setText(String.valueOf(result));
    }
} catch (Exception ex) {
    // 表达式错误, 将异常显示到屏幕
    textField2.setText("表达式非法,请重新输入");
}

心得体会

完成一个具有可视化界面的计算器项目是一次很有收获的软件工程实践经历。

在开始编写代码之前,我对项目需求的分析不够完成全面。这导致我在动手编码时反复地修改计算器的计算逻辑和展示结果方式,造成了很大的时间浪费。

在图形界面搭建阶段,我快速地学习Swing并上手搭建了一个直观、易于使用和美观的用户界面。

在编码过程中,我将整个项目划分为多个模块(方法),每个模块负责不同的功能。这种模块化的设计使得代码更具可读性和可维护性。我编写了简洁、高内聚的方法,并尽量避免了代码的冗长和重复。

新输入");
}




## 心得体会

 完成一个具有可视化界面的计算器项目是一次很有收获的软件工程实践经历。

在开始编写代码之前,我对项目需求的分析不够完成全面。这导致我在动手编码时反复地修改计算器的计算逻辑和展示结果方式,造成了很大的时间浪费。

在图形界面搭建阶段,我快速地学习Swing并上手搭建了一个直观、易于使用和美观的用户界面。

在编码过程中,我将整个项目划分为多个模块(方法),每个模块负责不同的功能。这种模块化的设计使得代码更具可读性和可维护性。我编写了简洁、高内聚的方法,并尽量避免了代码的冗长和重复。

这个项目是一个很好的学习机会,让我将课堂上学到的知识应用到实际项目中。我深入了解了Java编程语言、用户界面设计、软件开发流程等方面的知识。通过实践,我发现自己的编程能力和解决问题的能力有了明显的提高。
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值