Basic MDAS Java Swing Calculator

Oct 28 2020

ฉันเพิ่งเริ่มเรียนรู้ Java และตัดสินใจสร้างเครื่องคำนวณ MDAS พื้นฐานใน Swing ฉันไม่ใช่มือใหม่ในการเขียนโปรแกรม แต่ฉันอาจทำผิดพลาดทั่วไปหรือไม่ได้เขียนโค้ดที่มีประสิทธิภาพสูงสุด

ฉันต้องการสร้างเครื่องคิดเลขที่สามารถใช้ตัวเลขและการดำเนินการหลาย ๆ ตัวก่อนที่จะหาคำตอบโดยใช้ MDAS แทนที่จะส่งคืนคำตอบหลังจากการดำเนินการทุกครั้งและใช้ในครั้งต่อไป

เช่น 2 * 3 + 4 - 5 / 5 = 9แทนที่จะเป็น1

รหัสของฉันประกอบด้วยคลาสเดียว มีรหัสไม่มากนักดังนั้นฉันจึงไม่รู้ว่ามีเหตุผลที่ดีที่จะแบ่งมันออกเป็นหลายชั้นเรียนหรือไม่ แต่ฉันไม่เคยเขียนอะไรแบบนี้ดังนั้นโปรดแก้ไขให้ถูกต้อง

Repo พร้อมตัวอย่าง gif และ jar ที่รันได้


package calculator;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.ArrayList;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class GUI extends JFrame {
    
    private static final long serialVersionUID = 1L;
    
    private String title = "Basic MDAS Calculator";
    private int currentNumber;
    private JLabel displayLabel = new JLabel(String.valueOf(currentNumber), JLabel.RIGHT);
    private JPanel panel = new JPanel();
    
    private boolean isClear = true;
    
    final String[] ops = new String[] {"+", "-", "x", "/"};
    
    private ArrayList<Integer> numHistory = new ArrayList<Integer>();
    private ArrayList<String> opHistory = new ArrayList<String>();
    

    public GUI() {
        setPanel();
        setFrame();
    }
    
    private void setFrame() {
        this.setTitle(title);
        this.add(panel, BorderLayout.CENTER);
        this.setBounds(10,10,300,700); 
        this.setResizable(false);
        this.setVisible(true);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
    
    private void setPanel() {
        panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        panel.setLayout(new GridLayout(0, 1));
        
        displayLabel.setFont(new Font("Verdana", Font.PLAIN, 42));
        panel.add(displayLabel);
        panel.add(Box.createRigidArea(new Dimension(0, 0)));
        
        createButtons();
    }
    
    private void createButtons() {
        
        // 0-9
        for (int i = 0; i < 10; i++) {
            final int num = i;
            
            JButton button = new JButton( new AbstractAction(String.valueOf(i)) { 
                private static final long serialVersionUID = 1L;

                @Override
                public void actionPerformed(ActionEvent e) {
                    
                    // If somebody presses "=" and then types a number, start a new equation instead
                    // of adding that number to the end like usual
                    if (!isClear) {
                        currentNumber = 0;
                        isClear = true;
                    }
                    
                    if (currentNumber == 0) {
                        currentNumber = num;
                    } else {
                        currentNumber = currentNumber * 10 + num;
                    }
                    
                    displayLabel.setText(String.valueOf(currentNumber));
                }
            });
            
            panel.add(button);
        }
        
        // +, -, x, /
        for (String op : ops) {
            
            JButton button = new JButton( new AbstractAction(op) { 
                private static final long serialVersionUID = 1L;

                @Override
                public void actionPerformed(ActionEvent e) {
                    numHistory.add(currentNumber);
                    currentNumber = 0;
                    
                    opHistory.add(op);
                    displayLabel.setText(op);
                }
            });
            
            panel.add(button);
        }
        
        // =
        JButton button = new JButton( new AbstractAction("=") { 
            private static final long serialVersionUID = 1L;
            private int i;

            @Override
            public void actionPerformed(ActionEvent e) {    
                // Display result
                numHistory.add(currentNumber);
                
                while (opHistory.size() > 0) {
                    
                    if (opHistory.contains("x")) {
                        i = opHistory.indexOf("x");
                        numHistory.set(i, numHistory.get(i) * numHistory.get(i+1));
                    } else if (opHistory.contains("/")) {
                        i = opHistory.indexOf("/");
                        numHistory.set(i, numHistory.get(i) / numHistory.get(i+1));
                    } else if (opHistory.contains("+")) {
                        i = opHistory.indexOf("+");
                        numHistory.set(i, numHistory.get(i) + numHistory.get(i+1));
                    } else if (opHistory.contains("-")) {
                        i = opHistory.indexOf("-");
                        numHistory.set(i, numHistory.get(i) - numHistory.get(i+1));
                    }
                    
                    opHistory.remove(i);
                    numHistory.remove(i+1);
                }
                
                displayLabel.setText(String.valueOf(numHistory.get(0)));
                
                currentNumber = numHistory.get(0);
                numHistory.clear();
                
                if (isClear) {
                    isClear = false;
                }
            }
        });
        
        panel.add(button);
    }

    public static void main(String[] args) {
        new GUI();
    }

}

ฉันจะขอบคุณเคล็ดลับใด ๆ

คำตอบ

5 Bobby Oct 28 2020 at 23:41
package calculator;

com.github.razemoon.basicmdasjavacaluclatorชื่อแพ็กเกจควรเชื่อมโยงซอฟต์แวร์ที่มีผู้เขียนเช่น


public class GUI extends JFrame {

สำหรับรูปแบบ Java nming ปกติคุณจะใช้ UpperCamelCase และใช้ตัวพิมพ์เล็กแม้กระทั่งสำหรับตัวย่อเช่น "Gui", "HtmlWidgetToolkit" หรือ "HtmlCssParser"


private static final long serialVersionUID = 1L;

คุณต้องการฟิลด์นี้ก็ต่อเมื่อมีโอกาสสูงที่คลาสจะถูกทำให้เป็นอนุกรม ... ในกรณีนี้ส่วนใหญ่จะไม่


final String[] ops = new String[] {"+", "-", "x", "/"};

ทำไมถึงเป็นแบบนี้package-private?

นอกจากนี้finalอาร์เรย์ไม่ได้เป็นfinalอย่างที่คุณคิดค่าต่างๆยังสามารถเปลี่ยนแปลงได้ คุณมักจะต้องการ Enum .... ที่จริงคุณต้องการอินเทอร์เฟซ แต่ในตัวอย่างนี้ Enum น่าจะทำได้ดีพอ


    private ArrayList<Integer> numHistory = new ArrayList<Integer>();
    private ArrayList<String> opHistory = new ArrayList<String>();

พยายามใช้อินเทอร์เฟซทั่วไปที่ต่ำที่สุดสำหรับการประกาศเสมอในกรณีListนี้


    private void setFrame() {
        this.setTitle(title);
        this.add(panel, BorderLayout.CENTER);
        this.setBounds(10,10,300,700); 
        this.setResizable(false);
        this.setVisible(true);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

ทำไมคุณถึงใช้thisที่นี่ แต่ไม่มีที่ไหนเลย?


        this.setResizable(false);

ทำไม? เฟรมของคุณปรับขนาดได้อย่างสมบูรณ์แบบเท่าที่ฉันเห็น การตั้งค่าให้ไม่สามารถปรับขนาดได้คุณจะต้องแน่ใจว่าแอปพลิเคชันของคุณไม่สามารถใช้งานได้ภายใต้ LaF และขนาดตัวอักษรที่แตกต่างกัน


for (int i = 0; i < 10; i++) {

ฉันสนับสนุนอย่างต่อเนื่องว่าคุณได้รับอนุญาตให้ใช้ชื่อตัวแปรอักษรตัวเดียวเท่านั้นหากคุณเกี่ยวข้องกับมิติข้อมูล

for (int number = 0; number <= 9; number++) {
// Or
for (int digit = 0; digit <= 9; digit++) {

final int num = i;

อย่าย่อชื่อตัวแปรเพียงเพราะคุณทำได้ปริมาณการพิมพ์ที่ลดลงไม่คุ้มกับความสามารถในการอ่านที่ลดลง


เกี่ยวกับการสร้างปุ่มฉันต้องการสร้างวิธีการและคลาสตัวช่วยซึ่งทำให้โค้ดอ่านง่ายขึ้นในกรณีนี้ฉันจะใช้ lambdas เช่นนี้:

private JButton createButton(String text, Runnable action) {
    return new JButton(new AbstractButton(text) {
        @Override
        public void actionPerformed(ActionEvent e) {
            action.run();
        }
    })
}

// In createButtons:

panel.add(createButton(Integer.toString(number), () -> {
    // Code for the number button goes here.
}));

อีกทางเลือกหนึ่งคือการสร้างprivate class NumberActionซึ่งยอมรับตัวเลขในตัวสร้างและดำเนินการที่เกี่ยวข้อง นอกจากนี้ยังช่วยให้คุณสามารถกำจัดการประกาศครั้งสุดท้ายได้


private int i;

นั่นคือชื่อตัวแปรที่แย่มาก


    public GUI() {
        setPanel();
        setFrame();
    }
    
    private void setFrame() {
        this.setTitle(title);
        this.add(panel, BorderLayout.CENTER);
        this.setBounds(10,10,300,700); 
        this.setResizable(false);
        this.setVisible(true);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

// ...

    public static void main(String[] args) {
        new GUI();
    }

จะดีกว่าถ้าแบ่งความรับผิดชอบตรงนี้ เฟรมมีหน้าที่เพียงแค่ทำให้เค้าโครงของตัวเองดำเนินต่อไปในขณะที่วิธีการหลักควรรับผิดชอบในการแสดงเฟรม

    public GUI() {
        setPanel();
        setFrame();
    }
    
    private void setFrame() {
        this.setTitle(title);
        this.add(panel, BorderLayout.CENTER);
        this.setBounds(10,10,300,700); 
        this.setResizable(false);
    }

// ...

    public static void main(String[] args) {
        GUI gui = new GUI();
        gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        gui.setVisible(true);
    }

ดูเหมือนว่าตรรกะของคุณจะไม่มีการจัดการข้อผิดพลาดใด ๆ ฉันเชื่อว่าการกดปุ่มตัวดำเนินการสองครั้งติดต่อกันจะทำให้เกิดข้อผิดพลาด


บางทีวิธีที่ดีกว่าคือการพิมพ์นิพจน์ทั้งหมดไปยังหน้าจอตามที่ป้อนจากนั้นใช้อัลกอริธึม Shunting Yardเพื่อประมวลผลนิพจน์นั้น


ตรรกะของคุณไม่ได้สร้างทศนิยมและไม่จัดการกับการล้นอย่างสง่างาม ด้วยการเปลี่ยนตรรกะBigDecimalของคุณคุณสามารถจัดการทั้งสองอย่างได้อย่างง่ายดาย โปรดทราบว่าคุณต้องสร้างBigDecimalด้วยความเหมาะสมMathContextเพื่อให้มีความถูกต้องและพฤติกรรมที่เหมาะสม


หากคุณต้องการอ่านการนำไปใช้งานที่มีอยู่แล้วฉันสามารถแนะนำexp4jสำหรับไลบรารีนิพจน์ทางคณิตศาสตร์โดยใช้ float, EvalExสำหรับหนึ่งโดยใช้BigDecimalและjMathPaperโครงการของฉันเองสำหรับเครื่องคิดเลขซึ่งมี GUI ที่แตกต่างกัน (เกี่ยวกับนามธรรม)

4 Doi9t Oct 29 2020 at 04:33

คุณสามารถใช้การประกาศอาร์เรย์

เมื่อคุณต้องการอาร์เรย์ที่มีค่าที่กำหนดไว้ล่วงหน้าซึ่งอาจเป็นค่าคงที่คุณสามารถประกาศอาร์เรย์โดยไม่ระบุตัวตนได้

  final String[] ops = {"+", "-", "x", "/"};

ใช้ Enums สำหรับการดำเนินการ

แทนที่จะมีอาร์เรย์ของการดำเนินการฉันขอแนะนำให้คุณสร้าง Enum แทน

public enum Operators {
   PLUS("+"), MINUS("-"), MUL("x"), DIV("/");
   private final String operator;
   Operators(String operator) {
      this.operator = operator;
   }
   public String getOperator() {
      return operator;
   }
}

สิ่งนี้จะทำให้คุณได้เปรียบมากกว่าอาร์เรย์เนื่องจากคุณจะสามารถลบการทำซ้ำได้

//[...]
for (Operators op : Operators.values()) {
   JButton button = new JButton( new AbstractAction(op.getOperator()) {
      private static final long serialVersionUID = 1L;

      @Override
      public void actionPerformed(ActionEvent e) {
         numHistory.add(currentNumber);
         currentNumber = 0;

         opHistory.add(op);
         displayLabel.setText(String.valueOf(op.getOperator()));
      }
   });
   panel.add(button);
}
//[...]
//[...]
if (opHistory.contains(Operators.MUL)) {
   i = opHistory.indexOf(Operators.MUL);
   numHistory.set(i, numHistory.get(i) * numHistory.get(i + 1));
} else if (opHistory.contains(Operators.DIV)) {
   i = opHistory.indexOf(Operators.DIV);
   numHistory.set(i, numHistory.get(i) / numHistory.get(i + 1));
} else if (opHistory.contains(Operators.PLUS)) {
   i = opHistory.indexOf(Operators.PLUS);
   numHistory.set(i, numHistory.get(i) + numHistory.get(i + 1));
} else if (opHistory.contains(Operators.MINUS)) {
   i = opHistory.indexOf(Operators.MINUS);
   numHistory.set(i, numHistory.get(i) - numHistory.get(i + 1));
}
//[...]

นอกจากนี้ในความคิดของฉันสิ่งนี้จะทำให้โค้ดทำงานและ refactor ได้ง่ายขึ้น

เมื่อแบ่งให้ตรวจสอบdivisorก่อนทำการหารเสมอ

เมื่อหารด้วยศูนย์จะมีการjava.lang.ArithmeticExceptionโยนด้วยจาวา ฉันขอแนะนำให้คุณเพิ่มการตรวจสอบ :)

ใช้QueueแทนListเพื่อเก็บประวัติ

โดยใช้Listคุณต้องใช้ดัชนีQueueเพื่อลบรายการแรก ( java.util.Queue#poll); ข้อเสียเปรียบเพียงประการเดียวคุณจะต้อง refactor รหัสจริงเพื่อลบไฟล์indexOf.

private Queue<String> opHistory = new ArrayDeque<>();

การทำเช่นนี้จะทำให้โค้ดสั้นลง

while (opHistory.size() > 0) {
   Operators currentOperator = opHistory.poll();

   switch (currentOperator) { //Java 14+ Switch, you can use if or the older version of the switch.
       case MUL -> numHistory.set(i, numHistory.get(i) * numHistory.get(i+1));
       case DIV -> numHistory.set(i, numHistory.get(i) / numHistory.get(i+1));
       case PLUS -> numHistory.set(i, numHistory.get(i) + numHistory.get(i+1));
       case MINUS -> numHistory.set(i, numHistory.get(i) - numHistory.get(i+1));
   }

   numHistory.remove(i + 1);
}

แยกนิพจน์ไปยังตัวแปรเมื่อใช้หลายครั้ง

ในโค้ดของคุณคุณสามารถแยกนิพจน์ที่คล้ายกันออกเป็นตัวแปรได้ ซึ่งจะทำให้โค้ดสั้นลงและอ่านง่ายขึ้น

final Integer first = numHistory.get(i);
final Integer second = numHistory.get(i + 1);
switch (currentOperator) {
    case MUL -> numHistory.set(i, first * second);
    case DIV -> numHistory.set(i, first / second);
    case PLUS -> numHistory.set(i, first + second);
    case MINUS -> numHistory.set(i, first - second);
}