Basic MDAS Java Swing Calculator
ฉันเพิ่งเริ่มเรียนรู้ 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();
}
}
ฉันจะขอบคุณเคล็ดลับใด ๆ
คำตอบ
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 ที่แตกต่างกัน (เกี่ยวกับนามธรรม)
คุณสามารถใช้การประกาศอาร์เรย์
เมื่อคุณต้องการอาร์เรย์ที่มีค่าที่กำหนดไว้ล่วงหน้าซึ่งอาจเป็นค่าคงที่คุณสามารถประกาศอาร์เรย์โดยไม่ระบุตัวตนได้
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);
}