简体   繁体   中英

How to design an ActionListener class in Java?

NOTE: I'm learning about Clean Code , Design Patterns and Object Oriented Programming so please keep that in mind when answering.

I've got a window with bunch of JButtons and a TextField on the top. Here's the Window class in its separate file:

// Window.java
public class Window {
    JTextField textField;
    JButton button1;
    JButton button2;
    ...
}

Here's what I'd like:

When I press button1 I want the textField to display "1", when I press button2 to display "2", etc. So here's the ActionListener class in a separate file and this is sort of what I want it to do:

//TextInputActionListener.java
public class TextInputActionListener implements ActionListener{
    public void actionPerformed(ActionEvent e) {
        if(e.getSource() == button1) {
            textField.setText("1");
        }
        else if (e.getSource() == button 2) {
            textField.setText("2");
        }
    }
}

Now obviously this wont work, so my question is how should I define this class?

  • Should I declare it as an inner class of Window ?
  • Should I not bother to create a separate class for it? (I could just make the Window class implement ActionListener and that would solve the problem)

NOTE: As you can see the question is not how to do it but rather, how to do it in a way that supports Object Oriented Design.

With your current structure, the Window class can implement ActionListener because it is responsible for listening to events on its view. An inner class would also be acceptable but could lead to more cluttered code. You should be watchful of excessive separation of concerns. You should only really separate your code in terms of the Model, the View, and the Controller.

Definitely check out the MVC design pattern.

Since listener classes often need access to fields of GUI classes (like your Window ) using inner classes for listeners is a good idea.

Of course it is not forbidden that Window implements ActionListener but you then expose implementation details in the public API and you should think if you want that.

Note that lambdas and method handles of Java 8 give you even more possibilities to write listener code:

class Window {
    JTextField textField;
    JButton button1;
    JButton button2;


    Window()
    {
        button1.addActionListener(event -> textField.setText("1"));
        ...
    }
}

Here is my take on this problem :

  • Should ActionListener implementation be declared as an inner class of Window? - I would not. This is because inner classes are used when there is a piece of functionality that is closely associated with the state of the containing class. For eg an Iterator<E> implementation can be written as an inner class of Collection<E> implementation. In this case the Iterator implementation has access to the private data members of the Collection implementation. Another example is the Builder pattern .Inner classes have access to the private members of the parent class and hence should be used with care.
  • Should you create a separate class for it - Yes, and you should declare it in a separate file at minimum required access levels. You would not want to make the Window class implement ActionListener - simply because this would make the Window class responsible to handle events for all it's containing controls - violation of the separation of concerns (Single Responsibility Principle). So imagine the code you would be writing - it would be fraught with a long list of if conditions or a switch case to identify the source of the event. This obviously implies that if you add a new control to the window class you increase the length of the if or switch block. Declaring the action listener in a separate class allows for separations of concerns and also helps testability.

Hope this helps

First of all I'd like to thank everybody who answered the question. Without you I would've not figured it out. Each one of you gave me a piece of the puzzle.

My initial problems were:

  1. TextInputListener class needs access to buttonX .
  2. TextInputListener class needs access to textField .

In the TextInputListener class I figured that it doesn't really need access to the buttons, it only has to know if e.getSource() equals button . So I created methods in the Window class that takes an ActionEvent as a parameter (like e ), compares it to buttonX and returns returns the answer.

//Window.java
...
boolean isButton0(ActionEvent e) {
    return e.getSource() == buttons[0];
}
boolean isButton1(ActionEvent e) {
    return e.getSource() == buttons[1];
}
...

PROBLEM 1 SOLVED:

So now I'm one step closer. I can determine whether button1 , button2 .. was pressed, without declaring the buttons public or without returning it by a "getter" method like getButton1() .

// TextInputListener.java
public class TextInputListener implements ActionListener {
    Window window;
    @Override
    public void actionPerformed(ActionEvent e) {
        if (window.isButton0(e)) {
            //textField.setText("0")
        } else if (window.isButton1(e)) {
            //textField.setText("1")
        }
        ...
    }
}

( TextInputListener.java and Window.java are in the same package so I was able to declare the methods package-private. )

PROBLEM 2 SOLVED:

Once again TextInputListener didn't really need textField (as a variable) it just needed to set its text. So I created another package-private method setOutputText(String text) for setting the text. Here's the code:

// Window.java
public class Window {
    TextField textField;
    JButton button1;
    JButton button2;
    ...
    void setText(String text) {
        textField.setText(text);
    }
}

// TextInputListener.java
public class TextInputListener implements ActionListener {
    Window window;
    @Override
    public void actionPerformed(ActionEvent e) {
        if (window.isButton0(e)) {
            window.setText("0");
        } else if (window.isButton1(e)) {
            window.setText("1");
        }
        ...
    }
}

PUTTING EVERYTHING TOGETHER:

The only thing that's left now is to let each instance of the class to know about each other. In TextInputListener class I added the following code:

public void listenTo(Window window) {
        this.window = window;
    }

In the Window class I had to add the ActionListener to each button, so I added the following code:

public void setActionListener(ActionListener l) {
        for (int i = 0; i < buttons.length; i++) {
            buttons[i].addActionListener(l);
        }
    }

That's basically it! In main set everything up and it's working. Here's the (almost) full final code:

// MyApp.java
public class MyApp {
    public static void main(String[] args) {
        Window myWindow = new Window();
        TextInputListener myTextInputListener = new TextInputListener();

        myWindow.setActionListener(myTextInputListener);
        myTextInputListener.listenTo(myWindow);
    }
}

// Window.java
public class Window {
    TextField textField;
    JButton button1;
    JButton button2;
    ...
    void setText(String text) {
        textField.setText(text);
    }
    public void setActionListener(ActionListener l) {
        for (int i = 0; i < buttons.length; i++) {
            buttons[i].addActionListener(l);
        }
    }
}

// TextInputListener.java
public class TextInputListener implements ActionListener {
    Window window;

    public void listenTo(Window window) {
        this.window = window;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        if (window.isButton0(e)) {
            window.setText("0");
        } else if (window.isButton1(e)) {
            window.setText("1");
        }
        ...
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM