1
\$\begingroup\$

I'm making my first simple project which I decided would be a calculator and I'm kind of stuck on implementing the operations. I have two classes one of which is the GUI part and the other one represents all the processes 'inside' of the calculator. I decided to use BigDecimals instead of doubles. The problem is I really like the idea of putting all the possible calculator's operations in an enum associating their names with the mathematical operations they perform and the signs they are represented by.

Here is the yet incomplete class:

    final class Workings {
        private BigDecimal operand1, operand2, memory;
        private int precision;
        private JTextField screen;

        Workings (int precision, JTextField screen) 
            ....
        }

        enum Operation {
            ADDITION("+", Ary.BINARY) {
                BigDecimal apply(BigDecimal op1, BigDecimal op2, int scale) {
                    return op1.add(op2);
                }
            },

            SUBTRACTION("-", Ary.BINARY) {
             BigDecimal apply(BigDecimal op1, BigDecimal op2, int scale) {
                    return op1.subtract(op2);
                }
            },

            MULTIPLICATION("*", Ary.BINARY) {
                BigDecimal apply(BigDecimal op1, BigDecimal op2, int scale) {
                    return op1.multiply(op2);
                }
            },

            DIVISION("/", Ary.BINARY) {
                BigDecimal apply(BigDecimal op1, BigDecimal op2, int scale)
                    throws DivideByZeroException {

                    if(op2.signum() == 0)
                        throw new DivideByZeroException();

                    return op1.divide(op2, scale, RoundingMode.HALF_UP);
                }
            },


            ...;

            abstract BigDecimal(BigDecimal op1, BigDecimal op2, int scale);

            private enum Ary {
                UNARY, BINARY
            }

            private final String symbol;
            private final Ary ary;

            Operation (String symbol, Ary ary) {
                this.symbol = symbol;
                this.ary = ary;
            }

            String getSymbol() { return symbol; }
        }
    }

At first I didn't even know such constant-specific method implementations in enums were possible but it turns out Joshua Bloch presents them also on the example on calculator in Effective Java Second Edition (item 30). Only it gets a little bit more complicated with BigDecimals as well as unary operations (like square or square root for instance). The hard part for me is that the methods need different parameters. For example I don't need the scale unless I want to divide and I don't need two operands if i want to compute the square root. I think it's not very elegant to take unnecessary parameters. On the other hand this approach automatizes things and minimizes the space for error when expanding the class. One more thing I keep in mind is that it would be nice to have all the buttons in one enum. It would complicate things even further if i tried to add, for instance, operations on the calculator's memory since they would again need different parameters.

I've come up with a few solutions but none of them is perfect. Here are two of them:

  • The instance variables (operand1, operand2, memory) could be made static so that the methods in enum can freely use them without passing and returning them
  • The calculator operations could simply be instance methods of the Workings class and the 'enum' would only hold the names and symbols of operations:

    final class Workings {
    private BigDecimal operand1, operand2, memory;
    private int precision;
    private JTextField screen;
    
    Workings (int precision, JTextField screen) 
        ...
    }
    
    void addition() {
        operand1 = operand1.add(operand2);
    }
    
    void subtraction() {
        operand1 = operand1.subtract(operand2);
    }
    
    ...
    
    enum Operation {  // new enum
        ADDITION("+"), SUBTRACTION("-"), MULTIPLICATION("*"),
        DIVISION("/"), SQUARE("x^2"), SQUARE_ROOT("√x"),
        MEMORY_RECALL("MR"), MEMORY_ADD("M+"), MEMORY_SUBTRACT("M-"),
        ZERO("0"), ONE("1"), TWO("2")
    
        ... ;
    
        private final String symbol;
    
        Operation (String symbol) {
            this.symbol = symbol;
        }
    
        String getSymbol() { return symbol; }
    }
    }
    

But then each of them would have to be connected manually with corresponding methods.

Is there a better approach to do that?

\$\endgroup\$
2
\$\begingroup\$

But then each of them would have to be connected manually with corresponding methods.

When you look closer on that statement you have to do this manual connection between the enum and the actual code that performs the operation anyway. When you write that code in the enums implementation of the abstract method it is exactly that: manually connecting the enum and the code.

This still leaves us with the question where we should create this connection.

While you use Swing I'd suggest to do it in the View/Controller (since swing does not support strict separation of them...)

public class CalulatorViewController{
  private final JTextComponent precisionInput = new JTextField(); 
  private final JTextComponent operandInput = new JTextField();
  private final JTextComponent calculationDisplay  = new JTextArea(30,5);
  private BigDecimal accumulator = BigDecimal.ZERO;

  public CalulatorViewController(Container mainPanel){
    mainPanel.setLayout(new BorderLayout());
    mainPanel.add(createTextInputAndOutputPanel(),BorderLayout.CENTER);
    mainPanel.add(createButtonPanel(),BordrLayout.BOTTOM);
  } 

  private void createTextInputAndOutputPanel(){
     // not important for now
  } 

  private void createButtonPanel(){
    JPanel buttonPanel= new JPanel(new GridLayout(0,7)); // 7 columns, rows as needed...
    buttonPanel.add(new JButton(new AbstractAction("1"){
        public void actionPerformed(ActionEvent ae){
           operandInput.setText(operandInput.getText()+"1");
        }
      });
    buttonPanel.add(new JButton(new AbstractAction("2"){
        public void actionPerformed(ActionEvent ae){
           operandInput.setText(operandInput.getText()+"2");
        }
      });
    buttonPanel.add(new JButton(new AbstractAction("3"){
        public void actionPerformed(ActionEvent ae){
           operandInput.setText(operandInput.getText()+"3");
        }
      });
      });
    buttonPanel.add(new JButton(new AbstractAction("+"){
        public void actionPerformed(ActionEvent ae){
           accumulator= accumulator.add(new BigDecimal(operandInput.getText()));
        }
      });        
    // continue for all the buttons you need
    return buttonPanel;
  }

This might look like lots of duplicated code. And you're right.

Good thing is that you can collect things with same behavior in classes:

// this could be in a file of its own...
class NumberButtonAction extends AbstractAction{ 
   private final JTextComponent operandInput;
   private int number;
   NumberButtonAction(JTextComponent operandInput, String number){
     super(number);
     this.operandInput = operandInput;     
   }    
   public void actionPerformed(ActionEvent ae){
      operandInput.setText(operandInput.getText()+getValue(Action.NAME).toString());
   }
} 

private void createButtonPanel(){
    JPanel buttonPanel= new JPanel(new GridLayout(0,7)); // 7 columns, rows as needed...
    buttonPanel.add(new JButton(new NumberButtonAction(operandInput, "1")));
    buttonPanel.add(new JButton(new NumberButtonAction(operandInput, "2")));
// got the idea?

The OperatorButtonActions must have an individual implementation tough because each has a different behavior. But never the less they could be placed as top level custom classes in their own files.

And this is what OOP is all about: separate concerns and limit the responsibilities of the individual parts of your code.

First make your code run without thinking to much about the design (which does not men that you should not think about design at all).

Afterwards reduce code duplication as much as possible (aka refactoring). Here you might introduce new classes and/or create parameterized methods.

Writing UnitTest (and doing it before the production code) will support the refactoring.

\$\endgroup\$
  • \$\begingroup\$ I think you have a typo in the second code block. operantInput (throughout the code) vs operandInput (inside actionPerformed). Since I don't know much about java, I wouldn't dare editing your answer to avoid messing up the code \$\endgroup\$ – Victor Moraes Feb 22 '17 at 18:22
  • \$\begingroup\$ @VictorMoraes thanks for supporting the answer. I fixed that typo. \$\endgroup\$ – Timothy Truckle Feb 22 '17 at 20:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.