基于MVC模式开发带算式显示的Java Swing计算器

基于MVC模式开发带算式显示的Java Swing计算器

在Java桌面应用开发中,Swing是构建图形界面的常用工具,而MVC(Model-View-Controller)架构模式则能让代码结构更清晰、可维护性更强。本文将详细介绍如何使用MVC模式开发一款支持算式实时显示的简易计算器,解决基础计算器无法直观查看输入表达式的问题。

一、开发背景与核心需求

日常使用的简易计算器常存在一个痛点:只能显示当前输入的单个数字,无法查看完整的计算表达式(如“12×3+45÷5”),导致用户容易输入错误且难以核对。基于此,我们确定以下核心需求:

  1. 图形界面支持数字、运算符(+、-、×、÷)、括号(())输入
  2. 实时显示完整计算表达式
  3. 支持运算符优先级计算(先乘除后加减,括号优先)
  4. 提供清空(C)、退格(←)等便捷操作
  5. 结果显示自动优化(整数去除小数点后多余的0)

二、MVC架构设计思路

MVC模式通过将应用拆分为三个独立模块,实现“关注点分离”,具体分工如下:

模块 职责 对应计算器功能
Model(模型) 存储数据(计算表达式)、实现业务逻辑(表达式解析与计算) 管理输入的算式字符串、处理加减乘除运算、解决运算符优先级
View(视图) 负责界面展示与用户交互 绘制计算器窗口、按钮、表达式显示框、结果显示框
Controller(控制器) 协调Model与View,传递用户操作与数据更新 监听按钮点击事件、调用Model处理数据、通知View更新显示

这种架构的优势在于:后续修改界面(如更换按钮样式)无需改动计算逻辑,优化运算规则也无需调整界面代码,极大提升了代码可维护性。

三、完整实现代码与模块解析

1. 项目结构

整个计算器项目仅需一个Java文件(Calculator.java),内部包含4个核心类:

  • CalculatorModel:模型类,处理数据与计算
  • CalculatorView:视图类,构建图形界面
  • CalculatorController:控制器类,协调交互
  • Calculator:主类,程序入口

2. 模块详细实现

(1)Model:核心计算逻辑

模型类是计算器的“大脑”,负责存储表达式和实现计算算法。这里采用逆波兰表达式(后缀表达式) 解决运算符优先级问题,支持括号、小数运算。

class CalculatorModel {
    private String currentExpression = ""; // 存储完整计算表达式

    // 添加内容到表达式(数字、运算符、括号)
    public void appendToExpression(String s) {
        currentExpression += s;
    }

    // 清空表达式
    public void clearExpression() {
        currentExpression = "";
    }

    // 退格:删除最后一个字符
    public void backspace() {
        if (!currentExpression.isEmpty()) {
            currentExpression = currentExpression.substring(0, currentExpression.length() - 1);
        }
    }

    // 获取当前表达式(供View显示)
    public String getExpression() {
        return currentExpression;
    }

    // 计算表达式结果(核心方法)
    public String calculate() {
        if (currentExpression.isEmpty()) return "0";
        try {
            // 替换中文运算符为Java支持的符号(×→*,÷→/)
            String expr = currentExpression.replace("×", "*").replace("÷", "/");
            // 调用逆波兰表达式算法计算结果
            double result = evaluateExpression(expr);
            // 格式化结果:整数显示为long型(如15.0→15),小数正常显示
            return result % 1 == 0 ? String.valueOf((long) result) : String.valueOf(result);
        } catch (Exception e) {
            return "错误"; // 处理计算异常(如除数为0、表达式语法错误)
        }
    }

    // 逆波兰表达式计算(处理运算符优先级)
    private double evaluateExpression(String expr) {
        Stack<Double> numStack = new Stack<>(); // 存储数字的栈
        Stack<Character> opStack = new Stack<>(); // 存储运算符的栈
        expr = expr.replaceAll("\\s+", ""); // 去除表达式中的空格

        for (int i = 0; i < expr.length(); i++) {
            char c = expr.charAt(i);
            // 1. 处理数字和小数点(支持多位数、小数)
            if (Character.isDigit(c) || c == '.') {
                StringBuilder num = new StringBuilder();
                // 读取连续的数字和小数点
                while (i < expr.length() && (Character.isDigit(expr.charAt(i)) || expr.charAt(i) == '.')) {
                    num.append(expr.charAt(i++));
                }
                i--; // 回退一位,避免跳过下一个字符
                numStack.push(Double.parseDouble(num.toString()));
            }
            // 2. 处理左括号:直接入栈
            else if (c == '(') {
                opStack.push(c);
            }
            // 3. 处理右括号:计算到左括号为止
            else if (c == ')') {
                while (opStack.peek() != '(') {
                    // 弹出两个数字和一个运算符计算,结果入栈
                    numStack.push(calculateSingleOp(numStack.pop(), numStack.pop(), opStack.pop()));
                }
                opStack.pop(); // 弹出左括号(不参与计算)
            }
            // 4. 处理运算符:按优先级入栈(乘除优先级高于加减)
            else {
                // 若栈顶运算符优先级更高,先计算栈内表达式
                while (!opStack.isEmpty() && getOpPriority(opStack.peek()) >= getOpPriority(c)) {
                    numStack.push(calculateSingleOp(numStack.pop(), numStack.pop(), opStack.pop()));
                }
                opStack.push(c); // 当前运算符入栈
            }
        }

        // 5. 处理栈中剩余的运算符
        while (!opStack.isEmpty()) {
            numStack.push(calculateSingleOp(numStack.pop(), numStack.pop(), opStack.pop()));
        }

        return numStack.pop(); // 最终结果
    }

    // 计算单个运算符(注意:栈弹出顺序是“后入的数字在前”)
    private double calculateSingleOp(double b, double a, char op) {
        switch (op) {
            case '+': return a + b;
            case '-': return a - b;
            case '*': return a * b;
            case '/': 
                if (b == 0) throw new ArithmeticException("除数不能为0");
                return a / b;
            default: throw new IllegalArgumentException("无效运算符:" + op);
        }
    }

    // 定义运算符优先级:乘除(2)> 加减(1)> 其他(0)
    private int getOpPriority(char op) {
        return switch (op) {
            case '+', '-' -> 1;
            case '*', '/' -> 2;
            default -> 0;
        };
    }
}

(2)View:图形界面实现

视图类使用Swing组件构建计算器界面,包含两个核心显示区域(表达式框、结果框)和一个4×5的按钮面板,布局采用BorderLayoutGridLayout组合,确保界面美观且自适应。

class CalculatorView extends JFrame {
    private JTextField expressionField; // 显示完整表达式(上方)
    private JTextField resultField;     // 显示计算结果(下方)
    private JPanel buttonPanel;         // 按钮面板

    // 构造方法:初始化界面
    public CalculatorView() {
        setTitle("带算式显示的计算器"); // 窗口标题
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // 关闭窗口时退出程序
        setLayout(new BorderLayout(5, 5)); // 边框布局,组件间距5px
        initComponents(); // 初始化组件
        pack(); // 自动调整窗口大小以适应组件
        setLocationRelativeTo(null); // 窗口居中显示
        setResizable(false); // 禁止调整窗口大小
    }

    // 初始化所有组件
    private void initComponents() {
        // 1. 表达式显示框(只读,右对齐,小字体)
        expressionField = new JTextField();
        expressionField.setEditable(false); // 禁止用户手动编辑
        expressionField.setHorizontalAlignment(JTextField.RIGHT); // 文本右对齐
        expressionField.setFont(new Font("Arial", Font.PLAIN, 16)); // 字体样式
        expressionField.setPreferredSize(new Dimension(350, 40)); // 固定尺寸

        // 2. 结果显示框(只读,右对齐,大字体)
        resultField = new JTextField("0"); // 默认显示0
        resultField.setEditable(false);
        resultField.setHorizontalAlignment(JTextField.RIGHT);
        resultField.setFont(new Font("Arial", Font.BOLD, 24)); // 加粗字体,更醒目
        resultField.setPreferredSize(new Dimension(350, 50));

        // 3. 显示区域面板(组合表达式框和结果框)
        JPanel displayPanel = new JPanel(new BorderLayout());
        displayPanel.add(expressionField, BorderLayout.NORTH);
        displayPanel.add(resultField, BorderLayout.SOUTH);
        add(displayPanel, BorderLayout.NORTH); // 显示区域放在窗口顶部

        // 4. 按钮面板(4行5列网格布局)
        buttonPanel = new JPanel(new GridLayout(4, 5, 3, 3)); // 行列间距3px
        // 按钮文本顺序(按计算器常用布局排列)
        String[] buttonTexts = {
            "7", "8", "9", "÷", "C",
            "4", "5", "6", "×", "←",
            "1", "2", "3", "-", "=",
            "0", ".", "+", "(", ")"
        };
        // 循环创建按钮并添加到面板
        for (String text : buttonTexts) {
            JButton btn = new JButton(text);
            btn.setFont(new Font("Arial", Font.PLAIN, 18)); // 按钮字体
            buttonPanel.add(btn);
        }
        add(buttonPanel, BorderLayout.CENTER); // 按钮面板放在窗口中间
    }

    // 更新表达式显示(供Controller调用)
    public void setExpression(String expr) {
        expressionField.setText(expr);
    }

    // 更新结果显示(供Controller调用)
    public void setResult(String result) {
        resultField.setText(result);
    }

    // 为所有按钮添加点击监听器(供Controller绑定事件)
    public void addButtonListener(ActionListener listener) {
        Component[] components = buttonPanel.getComponents();
        for (Component comp : components) {
            if (comp instanceof JButton) {
                ((JButton) comp).addActionListener(listener);
            }
        }
    }
}

(3)Controller:交互逻辑协调

控制器类是Model与View之间的“桥梁”,通过监听View的按钮点击事件,调用Model的对应方法处理数据,再通知View更新显示,实现“解耦”。

class CalculatorController implements ActionListener {
    private CalculatorModel model; // 持有Model引用
    private CalculatorView view;   // 持有View引用

    // 构造方法:绑定Model和View
    public CalculatorController(CalculatorModel model, CalculatorView view) {
        this.model = model;
        this.view = view;
        this.view.addButtonListener(this); // 为View的按钮添加监听器
    }

    // 处理按钮点击事件
    @Override
    public void actionPerformed(ActionEvent e) {
        String command = e.getActionCommand(); // 获取点击按钮的文本

        switch (command) {
            // 1. 清空按钮(C):重置表达式和结果
            case "C":
                model.clearExpression();
                view.setExpression("");
                view.setResult("0");
                break;

            // 2. 等于按钮(=):计算结果并显示
            case "=":
                String result = model.calculate();
                view.setResult(result);
                break;

            // 3. 退格按钮(←):删除表达式最后一个字符
            case "←":
                model.backspace();
                view.setExpression(model.getExpression());
                break;

            // 4. 其他按钮(数字、运算符、小数点、括号):添加到表达式
            default:
                model.appendToExpression(command);
                view.setExpression(model.getExpression());
                break;
        }
    }
}

(4)主类:程序入口

主类通过SwingUtilities.invokeLater()在Swing事件调度线程中创建界面,避免多线程安全问题。

public class Calculator {
    public static void main(String[] args) {
        // 在Swing事件线程中创建界面(推荐写法,确保线程安全)
        SwingUtilities.invokeLater(() -> {
            CalculatorModel model = new CalculatorModel(); // 创建模型
            CalculatorView view = new CalculatorView();     // 创建视图
            new CalculatorController(model, view);          // 创建控制器(绑定Model和View)
            view.setVisible(true); // 显示窗口
        });
    }
}

四、功能测试与使用说明

1. 编译与运行

  • 编译:在命令行进入代码所在目录,执行javac Calculator.java
  • 运行:执行java Calculator,即可打开计算器窗口

2. 核心功能测试

屏幕截图 2025-10-30 135411

屏幕截图 2025-10-30 135423

屏幕截图 2025-10-30 135439

3. 使用技巧

  • 输入负数:可通过括号实现,如(3-5)(结果为-2)
  • 修正输入:若输错字符,点击“←”删除最后一个字符,无需重新输入全部表达式

五、代码优化与扩展方向

当前计算器已实现核心功能,若需进一步完善,可考虑以下方向:

  1. 添加历史记录:在Model中增加List<String>存储历史计算记录,View中添加滚动面板显示历史记录
  2. 支持更多运算:增加平方(x²)、平方根(√)、取余(%)等运算符,需在Model中扩展evaluateExpression方法
  3. 界面美化:使用UIManager设置系统外观(如Windows风格),或自定义按钮颜色、背景图片
  4. 键盘支持:添加键盘事件监听,支持用户通过键盘输入数字和运算符(如按Enter键计算结果)
  5. 表达式语法校验:在Model中增加输入校验逻辑(如避免连续输入运算符),提升用户体验

六、总结

本文通过MVC模式开发的Java Swing计算器,不仅实现了“算式实时显示”这一核心需求,还通过架构拆分让代码逻辑更清晰。MVC模式的优势在项目中体现得淋漓尽致:

  • Model专注于数据与计算,无需关心界面
  • View仅负责显示,不包含任何业务逻辑
  • Controller统一处理交互,避免Model与View直接耦合

这种设计思路不仅适用于计算器,也可推广到其他Java桌面应用开发中。希望本文能为刚接触Swing或MVC模式的开发者提供参考,帮助大家写出更易维护、可扩展的代码。

以上就是博客的全部文本内容啦。如果你对内容有修改需求,比如增减某些技术细节、调整语言风格,或者想补充代码运行截图的说明等,都可以随时告诉我。

posted @ 2025-10-30 14:05  救苦救难观世音  阅读(44)  评论(0)    收藏  举报