2
\$\begingroup\$

My Java is very rusty and I have been unlucky finding a better way to accomplish this task. I have two different methods which get called based on which radio button is selected. Is there a cleaner way to store/determine which radio button is currently selected in order to call the appropriate method? Here is the segment I am trying to streamline. (Full source code below)

public void actionPerformed(ActionEvent e){
    if(e.getSource() == iterativeRadio){
        calcMethod = "iterative";
    }else if(e.getSource() == recursiveRadio){
        calcMethod = "recursive";
    }else if(calcMethod == null){
        resultBox.setText("Select a calculation method");
    }else{
        try{
            int n = Integer.parseInt(inputBox.getText());
            if (e.getSource() == calcButton){
                if(calcMethod == "iterative"){
                    resultBox.setText(String.valueOf(Sequence.computeIterative(n)));
                    efficiencyBox.setText(String.valueOf(Sequence.getEfficiency()));
                }else if(calcMethod == "recursive"){
                    resultBox.setText(String.valueOf(Sequence.computeRecursive(n)));
                    efficiencyBox.setText(String.valueOf(Sequence.getEfficiency()));
                }
            }
        }catch (NumberFormatException e2){
            resultBox.setText("Invalid entry");
        }catch (ArithmeticException e3){
            resultBox.setText(e3.getMessage());
        }catch (Exception e4){
            resultBox.setText("Something bad happened");
        }
    }
}

Sequence.java:

/**
 * @author Matthew Miller
 * Sequence utility class
 */

public final class Sequence {
    private static int efficiency;

    public static int getEfficiency(){
        return efficiency;
    }

    public static int computeIterative (int n){
        efficiency = 0;
        return iterativeWorker(0,1,n);
    }

    private static int iterativeWorker(int onePrev, int curr, int n){
        if (n == 0){
            return 0;
        }else{
            int twoPrev;
            for(int i = curr; i < n; i++){
                efficiency++;
                twoPrev = onePrev;
                onePrev = curr;
                curr = Math.addExact(Math.multiplyExact(2, onePrev), twoPrev);
            }
            return curr;
        }
    }

    public static int computeRecursive (int n){
        efficiency = 0;
        return recursiveWorker(n);
    }

    private static int recursiveWorker(int n){
        efficiency++;
        if (n == 0){
            return 0;
        }else if (n == 1){
            return 1;
        }else{
            return Math.addExact(Math.multiplyExact(2, recursiveWorker(n - 1)), recursiveWorker(n - 2));
        }
    }
}

SequenceCalc.java:

import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedWriter;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;

import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;

/**
 * @author Matthew Miller
 * SequenceCalc/GUI class
 */

public class SequenceCalc extends JPanel implements ActionListener {
    private static final long serialVersionUID = -5799806787577444436L;

    private String calcMethod = "iterative";

    private final JButton calcButton = new JButton("Calculate");
    private final JRadioButton iterativeRadio = new JRadioButton("Iterative",true);
    private final JRadioButton recursiveRadio = new JRadioButton("Recursive");
    private final ButtonGroup radioGroup = new ButtonGroup();
    private final JTextField inputBox = new JTextField();
    private final JTextField resultBox = new JTextField();
    private final JTextField efficiencyBox = new JTextField();

    public SequenceCalc(){
        iterativeRadio.addActionListener(this);
        recursiveRadio.addActionListener(this);
        calcButton.addActionListener(this);

        resultBox.setEditable(false);
        efficiencyBox.setEditable(false);

        radioGroup.add(iterativeRadio);
        radioGroup.add(recursiveRadio);

        JFrame frame = new JFrame("Project 3");
        JPanel mainPanel = new JPanel(new GridLayout(4,1,1,1));
        mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
        JPanel nRow = new JPanel(new GridLayout(1,2,1,1));
        nRow.add(iterativeRadio);
        nRow.add(recursiveRadio);
        mainPanel.add(nRow);
        nRow = new JPanel(new GridLayout(1,2,1,1));
        nRow.add(inputBox);
        nRow.add(calcButton);
        mainPanel.add(nRow);
        nRow = new JPanel(new GridLayout(1,2,1,1));
        nRow.add(new JLabel("Result:"));
        nRow.add(resultBox);
        mainPanel.add(nRow);
        nRow = new JPanel(new GridLayout(1,2,1,1));
        nRow.add(new JLabel("Efficiency:"));
        nRow.add(efficiencyBox);
        mainPanel.add(nRow);
        frame.add(mainPanel);
        frame.pack();
        frame.setVisible(true);
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter(){
                @Override
                public void windowClosing(WindowEvent e){
                    writeFile(generateReport(0,10));
                    frame.dispose();
                }
            });
    }

    @Override
    public void actionPerformed(ActionEvent e){
        if(e.getSource() == iterativeRadio){
            calcMethod = "iterative";
        }else if(e.getSource() == recursiveRadio){
            calcMethod = "recursive";
        }else if(calcMethod == null){
            resultBox.setText("Select a calculation method");
        }else{
            try{
                int n = Integer.parseInt(inputBox.getText());
                if (e.getSource() == calcButton){
                    if(calcMethod == "iterative"){
                        resultBox.setText(String.valueOf(Sequence.computeIterative(n)));
                        efficiencyBox.setText(String.valueOf(Sequence.getEfficiency()));
                    }else if(calcMethod == "recursive"){
                        resultBox.setText(String.valueOf(Sequence.computeRecursive(n)));
                        efficiencyBox.setText(String.valueOf(Sequence.getEfficiency()));
                    }
                }
            }catch (NumberFormatException e2){
                resultBox.setText("Invalid entry");
            }catch (ArithmeticException e3){
                resultBox.setText(e3.getMessage());
            }catch (Exception e4){
                resultBox.setText("Something bad happened");
            }
        }
    }

    public static String generateReport(int start, int end){
        StringBuilder sb = new StringBuilder();
        sb.append("Value of n,").append("Iterative,").append("Recursive\n");
        for (int i=start;i<=end;i++){
            //sb.append(i).append(",");
            //Sequence.computeIterative(i);
            sb.append(Sequence.computeIterative(i)).append(",");
            sb.append(Sequence.getEfficiency()).append(",");
            Sequence.computeRecursive(i);
            sb.append(Sequence.getEfficiency()).append("\n");
        }
        return sb.toString();
    }

    private static void writeFile(String content){
        try{
            Writer fd = new BufferedWriter(new OutputStreamWriter(
                    new FileOutputStream("efficiency.csv"), "utf-8"));
            fd.write(content);
            fd.close();
        }catch(IOException e){
            System.out.println(e.getMessage());
        }
    }

    public static void main(String[] args){
        new SequenceCalc();
    }
}
\$\endgroup\$
5
\$\begingroup\$

Indeed, the conditional branching in actionPerformed(ActionEvent) is quite complex and prone to bugs. This implementation does too many things at the same time: sets the calcMethod value, determines which calculation method to use and executes the calculation. These concerns should be separated. Each GUI element should trigger only the changes that are relevant to its responsibility. JRadioButtons, when clicked, should define the calculation method to use; calcButton should trigger the calculation.

Without significant changes to the design of SequenceCalc class, the separation of these concerns can be achieved as follows.

In order to extract the selection of the calculation method, we will use a simple interface

interface CalculationRunner {

  int runCalculation(int times);

}

It has two ad-hoc anonymous implementations:

private static final CalculationRunner RUNNER_ITERATIVE = new CalculationRunner() {

  @Override
  public int runCalculation(int times) {
    return Sequence.computeIterative(times);
  }
};

private static final CalculationRunner RUNNER_RECURSIVE = new CalculationRunner() {

  @Override
  public int runCalculation(int times) {
    return Sequence.computeRecursive(times);
  }
};

The reference to the currently chosen method will be stored in a dedicated field:

// let's say that it will be initialiazed to "iterative", since the 
// iterativeRadio is selected by default
private CalculationRunner runner = RUNNER_ITERATIVE;

We do not need String calcMethod field in this class any more.

The events handling will be separate for each element of the GUI. In the constructor:

iterativeRadio.addActionListener(new ActionListener() {

  @Override
  public void actionPerformed(ActionEvent e) {
    SequenceCalc.this.runner = RUNNER_ITERATIVE;
  }
});

recursiveRadio.addActionListener(new ActionListener() {

  @Override
  public void actionPerformed(ActionEvent e) {
    SequenceCalc.this.runner = RUNNER_RECURSIVE;
  }
});

The main logic of the action will now be reduced to the following:

calcButton.addActionListener(new ActionListener() {

  @Override
  public void actionPerformed(ActionEvent e) {
    try {
      int n = Integer.parseInt(inputBox.getText());
      final int result = SequenceCalc.this.runner.runCalculation(n);
      resultBox.setText(String.valueOf(result));
      efficiencyBox.setText(String.valueOf(Sequence.getEfficiency()));
    }
    catch (NumberFormatException e2) {
      resultBox.setText("Invalid entry");
    }
    catch (ArithmeticException e3) {
      resultBox.setText(e3.getMessage());
    }
  }
});

And there is no more need to implement ActionListener for SequenceCalc, actionPerformed should be deleted from it.

Other Stuff

  1. The solution above is just a quick ad-hoc tip. Personally, from the point of view of the style, I don't like in-line anonymous implementations. They can be extracted in dedicated entities.

  2. I doubt a lot about the static methods and static efficiency member in Sequence. This should be a normal object without statics.

  3. The constructor is too long. It should be split based on semantic parts (GUI elements, listeners, frame config etc).

  4. catch (Exception e4) is ugly, I removed it from the main action processing.

|improve this answer|||||
\$\endgroup\$
  • \$\begingroup\$ I would definitely go for enumerations instead of private static final implementations of interfaces. Note that interfaces can be implemented by enumerations. \$\endgroup\$ – Matthieu Nov 25 '15 at 12:10
  • \$\begingroup\$ Interesting how we ended up with the same kind of long answer ;-) \$\endgroup\$ – Matthieu Nov 25 '15 at 12:31
  • \$\begingroup\$ @Matthieu 1) I think that using statics for the implemented interface is optional here: they are just easier to access from the action listeners and underline the statelessness here. 2) yes, it's the same kind of long answer + diff of only 2 minutes for both the posts :) \$\endgroup\$ – Antot Nov 25 '15 at 12:43
  • \$\begingroup\$ took the words out of my mouth, especially the part about getting rid of the inline runners and going with separate entities. \$\endgroup\$ – Robert Snyder Nov 25 '15 at 15:24
  • \$\begingroup\$ Wow, thank you. I never truly understood the purpose of interfaces until reading all of this. \$\endgroup\$ – h3rrmiller Nov 25 '15 at 18:54
2
\$\begingroup\$

if(calcMethod == "iterative") will work in Java because of String caching, but in general you should be using if("iterative".equals(calcMethod))).

You could use anonymous classes to set your calcMethod member:

iterativeRadio.addActionListener(new ActionListener() {
    @Override public void actionPerformed(ActionEvent e) {
        calcMethod = "iterative";
    }
});

which will ease up the code in you "main" actionPerformed() method.

To take it one step further, if you have a fixed number of calculation methods, you'd be happier using Java enums. It basically is a fixed set of class instances (not only named numbers):

public enum Sequence {

    ITERATIVE {
        @Override
        public int compute(int n) {
            // TODO do the computation and compute efficiency
            return 0;
        }
    },

    RECURSIVE {
        @Override
        public int compute(int n) {
            // TODO do the computation and compute efficiency
            return 0;
        }
    };

    // Regular class definition with members and methods

    private int efficiency;

    public int getEfficency() {
        return efficiency;
    }

    // Abstract method to be implemented by all enum instances
    public abstract int compute(int n);

}

You can then map one or the other to a client property on your radio button:

iterativeRadio.putClientProperty("seq", Sequence.ITERATIVE);
recursiveRadio.putClientProperty("seq", Sequence.RECURSIVE);

and transform your String calcMethod into Sequence calcMethod. Then use the same ActionListener on all your radio buttons:

ActionListener al = new ActionListener() {
    @Override public void actionPerformed(ActionEvent e) {
        calcMethod = (Sequence)((JRadioButton)e.getSource()).getCLientProperty("seq");
    }
};
iterativeRadio.addActionListener(al);
recursiveRadio.addActionListener(al);

Your "main" actionPerformed() would then boil down to:

public void actionPerformed(ActionEvent e){
    try{
        int n = Integer.parseInt(inputBox.getText());
        resultBox.setText(String.valueOf(calcMethod.compute(n)));
        efficiencyBox.setText(String.valueOf(calcMethod.getEfficiency()));
    }catch (NumberFormatException e2){
        resultBox.setText("Invalid entry");
    }catch (ArithmeticException e3){
        resultBox.setText(e3.getMessage());
    }catch (Exception e4){
        resultBox.setText("Something bad happened");
    }
}

Neat, isn't it? :)

|improve this answer|||||
\$\endgroup\$

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.