2
\$\begingroup\$

I have an Glob object which represents a glob of data. It has several parameters like size, the date it was created, who created it, and so on. I'm building a GUI to modify these Globs of data, and in my GUI is a JTextField for each of these parameters. Each JTextField has a FocusListener attached to it, which looks like:

public class GlobUpdaterGUI {

    HashMap<String, Glob> globs;

    // I know it's not great style to call main like this, but
    // it makes this snippet runnable. 
    public static void main(String[] args) {
        new GlobUpdaterGUI();
    }

    public GlobUpdaterGUI() {
        initialize();
    }

    public void initialize() {
        JFrame frame = new JFrame("Glob Updater");
        frame.setMinimumSize(new Dimension(250,150));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel panel = new JPanel(new GridLayout(4, 2));

        JTextField globNumber = new JTextField(10);

        JTextField dateTextField = new JTextField(10);
        dateTextField.addFocusListener(new FocusListener() {

            @Override public void focusGained(FocusEvent arg0) {}
            @Override
            public void focusLost(FocusEvent e) {

                /* "globNumber" lets the user select a glob from "globs". */
                Glob glob = globs.get(globNumber.getText());

                glob.date = dateTextField.getText();
            }
        });

        JTextField sizeTextField = new JTextField(10);
        sizeTextField.addFocusListener(new FocusListener() {

            @Override public void focusGained(FocusEvent arg0) {}
            @Override
            public void focusLost(FocusEvent e) {
                Glob glob = globs.get(globNumber.getText());

                glob.size = dateTextField.getText();
            }
        });

        JTextField creatorTextField = new JTextField(10);
        creatorTextField.addFocusListener(new FocusListener() {

            @Override public void focusGained(FocusEvent arg0) {}
            @Override
            public void focusLost(FocusEvent e) {
                Glob glob = globs.get(globNumber.getText());

                glob.creator = dateTextField.getText();
            }
        });

        panel.add(new JLabel("Glob Number:"));
        panel.add(globNumber);
        panel.add(new JLabel("Glob Date:"));
        panel.add(dateTextField);
        panel.add(new JLabel("Glob Size:"));
        panel.add(sizeTextField);
        panel.add(new JLabel("Glob Creator:"));
        panel.add(creatorTextField);

        frame.add(panel);
        frame.setVisible(true);
    }
}

My problem is that every FocusListener block is nearly identical to the others, but for a specific parameter.

This practice doesn't support readability, writeability, maintainability, or really any style guideline. Though there are only three blocks now, which isn't too bad, a Glob is going to end up with about a dozen parameters, so I'd like to solve this problem before heading too far down the wrong path.

I'd much rather create a new class which implements FocusListener, which takes in a JTextField (and/or whatever else it may need) and modifies its respective Glob parameter, but I don't know the proper practice if I need to modify a different variable in each one. Is there a way to generalize a Listener so that I don't need nearly identical code littered throughout my project?

\$\endgroup\$
3
  • \$\begingroup\$ IMO the whole interaction model of your application currently is flawed. This makes it difficult to actually review the code instead of the interaction model. I'd expect this UpdaterGUI to only write values when I "commit" them, instead of on an unfocus event. That makes the whole mess of Focus listeners unnecessary... \$\endgroup\$
    – Vogel612
    Jun 20 '17 at 18:12
  • \$\begingroup\$ I understand, but I've been asked to hook it up to a loseFocus event. The users would like to be able to enter a value then tab away to update their field. This also lets them change the glob number and just tab through the other 3 textboxes to update a glob with the same info as the last (which they'd do a lot). \$\endgroup\$ Jun 20 '17 at 18:16
  • \$\begingroup\$ Just as an FYI Swing is really old and discouraged for new applications. You're recommended to use JavaFX which is the new thing since Java 6(?). \$\endgroup\$
    – Emily L.
    Jun 20 '17 at 23:12
2
\$\begingroup\$

I see a couple potential issues with the code beside the duplication of code in focus listeners that you mention:

  • you treat the text the user typed into a text field as a key of map that contains the Gobs without checking if such key exists - what will happen when the user types a nonexisting key?
  • the name of the texfield that identifies the Gob (gobNumber) indicates that the gob is identified by a numerical value, yet the key of the map is a String. Either a numerical value should be used as the key of the map or the textfield could be renamed to avoid the confusion

To deal with the duplication - a basic way of generalizing the focus listeners would be to use the Template method pattern. You could extract the code that would be duplicated for each listener to an abstract class and then delegate the parts that would be different to a specific subclass.

public abstract class UpdateGobFocusListener implements FocusListener
{
    private final Map<String, Glob> globs;
    private final JTextField globNumberTextField;

    public UpdateGobFocusListener(Map<String, Glob> globs, JTextField globNumberTextField)
    {
        this.globs = globs;
        this.globNumberTextField = globNumberTextField;
    }

    public void focusGained(FocusEvent e)
    {

    }

    public void focusLost(FocusEvent e)
    {
        Glob glob = globs.get(globNumberTextField.getText());
        updateGlob(glob);
    }

    protected abstract void updateGlob(Glob glob);

}

And an example of a subclass:

public class DateGlobUpdater extends UpdateGobFocusListener
{

    private final JTextField dateTextField;

    public DateGlobUpdater(JTextField dateTextField, Map<String, Glob> globs, JTextField globNumberTextField)
    {
        super(globs, globNumberTextField);
        this.dateTextField = dateTextField;
    }

    @Override
    protected void updateGlob(Glob glob)
    {
        glob.date = dateTextField.getText();
    }

}

Alternatively, if you prefer Composition over inheritance, you could extract the duplicated code to a general focus listener, create a GobUpdater interface and then move the different parts to a separate implementation of the interface each. The general focus listener could be injected with the specific implementation through a constructor.

\$\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.