如何实现和维护多个actionListener

4

好的,我有一个类(我们称之为:MenuBarClass),它包含多个菜单和菜单项。 我希望为每个菜单项分配一个动作监听器,但是...与其像这样做:

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) {} });

我希望我的代码更易于维护,更重要的是,我不希望在一个巨大的ActionListener类中有很多“if”语句:

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

如果可能的话,我该如何做到这一点?有人能帮忙吗?


我认为你可能可以在这里使用命令模式。 - adarshr
我无法相信,如果我有像100个不同的按钮这样的东西,在Java中没有一个可以用来改善代码可维护性的单一解决方案。 - Andrea Rastelli
“命令模式”(http://en.wikipedia.org/wiki/Command_pattern)可能适用于此处。但是,您需要为每个按钮创建一个命令对象,其实用性可能微不足道。 - Jeffrey
@AndreaRastelli:来吧,有许多这样的可能解决方案,但你必须进行实验和尝试。关于命令模式的更多信息,请查看这里。您还可以为每个不同的操作创建AbstractActions,并将其传递给菜单。我有时也会创建一两个“控制”类,并使用匿名侦听器简单地调用正确的控制方法。 - Hovercraft Full Of Eels
你能详细解释一下吗?可以提供一些例子或链接吗?"Actions" 不是指 ActionListener 吧。 - Andrea Rastelli
显示剩余2条评论
4个回答

2
您可以使用反射API创建一个实用方法,以降低冗长度:

reflection

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);
    }
  }
}

这可以这样使用:
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");
  }
}

这样的缺点是缺乏编译时检查sayHello(ActionEvent)方法是否存在。
性能成本将是可以忽略不计的。

1
实际上,那些 ActionListener 对象是通过命令设计模式成为命令对象。您可以创建自定义子类而不是匿名子类并获得更多的优雅。
现在,如果困扰您的是如何将操作侦听器与命令对象连接起来,我会使用反射做如下处理:
  • 创建一个自定义注释,比如@MenuAction,它可以接受适当命令对象的类。
  • 创建一个通用操作侦听器,读取、实例化和执行此命令。
  • 将通用操作侦听器添加到所有菜单项中。
如果您认真思考,可以创建一个框架,并在多个项目中使用这种通用方法,但这需要比手动连接几个菜单项与适当的 ActionListener 实现要花费更多的工作量。

1
如果你想要做的事情对于每个菜单项都是相似的,你可以创建一个实现了ActionListener接口并带有构造函数参数的类。例如,如果每个菜单项都应该打开一个JFrame,你可以这样做:
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);
    }
}

然后对于每个菜单项:

menuitem_1.addActionListener(new OpenFrameAction(myFrameForMenuItem1));

0

在siegi的回答基础上进行拓展。如果要执行的操作没有任何共同点(跳崖、跳探戈、喝咖啡),那么你只需要为每个项目添加单独的监听器。如果是这种情况,你不能指望Java会为你执行任何可维护性的魔法。

更常见的情况是这些操作确实有某些共同之处(跳探戈、跳快步等)。如果是这种情况,你可以遵循siegi的建议或在菜单(而不是项目)上附加一个监听器。事件应告诉你选择了哪个项目,你可以在监听器中使用它:

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

如果我没记错,e.getSource() 返回一个 Object,所以你需要进行类型转换来调用 getSelectedValue() 方法。;-) - siegi
1
你可能没有错。我的观点是事件对象应该包含所需的信息。 - barry

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接