【问题标题】:How to implement and mantain multiple actionListener如何实现和维护多个 actionListener
【发布时间】:2012-04-23 05:27:17
【问题描述】:

好的,我有一个包含多个 Menu 和 MenuItem 的类(我们称之为:MenuBarClass)。 我想为每个 MenuItem 分配一个动作监听器,但是.. 而不是做类似的事情:

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 类中出现很多“如果”,例如:

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

如果可能的话,我该怎么做? 有人可以帮忙吗?

【问题讨论】:

  • 我想你也许可以在这里使用命令模式。
  • 我不知道命令模式是什么,也不知道如何在这种情况下(或任何其他情况)使用它。你能帮帮我吗?
  • 不,抱歉,Java 适合你。假设在各个菜单项的处理程序之间没有可重用的通用代码,您的第一个代码示例中的语法是您可以获得的最佳语法。
  • 我不敢相信,如果我有 100 个不同的按钮,那么在 Java 中,没有一种单一的解决方案可以用来提高我的代码的可维护性。
  • command pattern 可能在这里使用。但是,您必须为每个按钮创建一个命令对象,它的用处可能可以忽略不计。

标签: java actionlistener code-maintainability


【解决方案1】:

您可以使用reflection API 创建实用方法来减少冗长:

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) 方法是否存在。

性能成本可以忽略不计。

【讨论】:

    【解决方案2】:

    实际上,那些ActionListener对象是命令设计模式下的命令对象。您可以创建自定义子类而不是匿名子类,并获得更多的优雅。

    现在,如果困扰您的是如何将动作侦听器与命令对象连接起来,我会使用反射来做这样的事情:

    • 创建一个类似于@MenuAction 的自定义注释,它可能采用正确命令对象的类。
    • 创建一个通用动作侦听器,用于读取、实例化和执行此命令。
    • 向所有菜单项添加通用动作监听器。

    如果您认为不错,您可以创建一个框架并在多个项目中使用这种通用方法,但这比简单地手动将几个菜单项与正确的ActionListener 实现联系起来要多得多。

    【讨论】:

      【解决方案3】:

      如果您想要为每个菜单项做类似的事情,您可以创建一个实现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));
      

      【讨论】:

        【解决方案4】:

        扩展 siegi 的答案。如果要执行的动作有共同点(跳下悬崖、跳探戈、喝杯咖啡),你真的只想为每个项目添加单独的听众。如果是这种情况,您不能指望 Java 为您执行任何可维护性魔法。

        更常见的情况是动作确实有一些共同点(跳探戈、跳狐步等)。如果是这种情况,您可以按照 siegi 的建议或将侦听器附加到菜单(而不是项目)。该事件应该告诉您选择了哪个项目,您可以在侦听器中使用它:

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

        【讨论】:

        • 如果我没记错的话,e.getSource() 返回一个Object,所以你必须强制转换才能使用getSelectedValue() 方法。 ;-)
        • 你可能没看错。我的观点是事件对象应该包含所需的信息。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-10
        • 1970-01-01
        相关资源
        最近更新 更多