How to implement and mantain multiple actionListen

2019-04-10 00:08发布

问题:

Ok, I have one class (let's call it: MenuBarClass) that contain multiple Menu and MenuItem. I whant assign to every MenuItem an actionlistener, but.. instead of doing something like:

menuitem_1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {} });
menuitem_2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {} });
menuitem_3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {} });
// ...
menuitem_N.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) {} });

I whant my code more mantainable and.. more important.. I don't whant a lots of "if" in one huge ActionListener class like:

public void actionPerformed(ActionEvent e) {
  if (e.getSource().equals(menuitem_1)) {
    //do stuff..
  } else if (e.getSource().equals(menuitem_2)) {
    //do stuff..
  } else ...
}

how can i do this, if it is possible? Can anyone help?

回答1:

You can reduce verbosity using the reflection API to create a utility method:

package demo;    
import java.awt.event.*;
import java.lang.reflect.*;

public class ListenerProxies {    
  private static final Class<?>[] INTERFACES = { ActionListener.class };

  public static ActionListener actionListener(final Object target,
                                                    String method) {
    final Method proxied = method(target, method);
    InvocationHandler handler = new InvocationHandler() {
      @Override
      public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable {
        ActionEvent event = (ActionEvent) args[0];
        return proxied.invoke(target, event);
      }
    };
    return (ActionListener) Proxy.newProxyInstance(target.getClass()
        .getClassLoader(), INTERFACES, handler);
  }

  private static Method method(Object target, String method) {
    try {
      return target.getClass().getMethod(method, ActionEvent.class);
    } catch (NoSuchMethodException e) {
      throw new IllegalStateException(e);
    } catch (SecurityException e) {
      throw new IllegalStateException(e);
    }
  }
}

This can be utilized like this:

package demo;
import static demo.ListenerProxies.actionListener;
import java.awt.event.ActionEvent;
import javax.swing.*;

public class Demo {

  public static void main(String[] args) {
    Demo test = new Demo();
    JButton hello = new JButton("Say Hello");
    hello.addActionListener(actionListener(test, "sayHello"));
    JFrame frame = new JFrame();
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().add(hello);
    frame.pack();
    frame.setVisible(true);
  }

  public void sayHello(ActionEvent event) {
    System.out.println("Hello");
  }
}

The downside of this is the lack of compile-time checking that the sayHello(ActionEvent) method exists.

The performance costs will be negligible.



回答2:

Actually, those ActionListener objects are command objects by the means of the command design pattern. You may create custom subclasses instead of anonymous subclasses and gain a little bit more elegancy.

Now if the thing that bothers you is how you're wiring the action listeners with the command objects, I would do something like this using reflection:

  • Create a custom annotation something like @MenuAction which may take the class of the proper command object.
  • Create a generic action listener that reads, instantiate and execute this command.
  • Add to all the menu items the generic action listener.

If you think it well, you could create a framework and use this generic approach in multiple projects, but it would be a lot more work than simply wiring a couple of menu items with the proper ActionListener implementation by hand.



回答3:

If the stuff you want to do is similar for every menu item you can create a class implementing ActionListener that takes constructor arguments. For example if each menu item should open a JFrame you can do something like this:

public class OpenFrameAction implements ActionListener
{
    private final JFrame frame;

    public OpenFrameAction(final JFrame frameToOpen)
    {
        this.frame = frameToOpen;
    }

    public void actionPerformed(ActionEvent e)
    {
        this.frame.setVisible(true);
    }
}

And then for each menu item:

menuitem_1.addActionListener(new OpenFrameAction(myFrameForMenuItem1));


回答4:

Expanding on siegi's answer. You would only really want to add individual listeners to each item if the actions to be performed have noting in common (jump off a cliff, do the tango, have a cup of coffee). If this is the case you can't expect Java to perform any maintainability magic for you.

The more usual case is that the actions do have something in common (do the tango, do the foxtrot, etc). If this is the case you can follow siegi's advice or attach a listener to the menu (not the item). The event should tell you which item was selected and you can use that in your listener:

// something like this
actionPerformed(ActionEvent e)
{
    this.doDance(e.getSource().getSelectedValue());
}