ActionListener风格-好还是不好

3

我有一个简单的GUI,其中包含:

  • 一个按钮。
  • 两个单选按钮

现在我想监听这些按钮中的每一个。我会做类似以下的事情:

public class TestApp implements ActionListener {

    private JFrame frame;
    private JButton btn;
    private JRadioButton rdb1;
    private JRadioButton rdb2; 

    public static void main(String[] args) { /*....*/ }

    private void initialize() {
       //Each time I add a button, I add it to the listener:
       btn = new JButton("Button");
       btn.addActionListener(this);
       //..
       rdb1 = new JRadioButton("Value1");
       rdb1.addActionListener(this);
       //And so on...
    }

    //The ActionEvents  
    public void actionPerformed(ActionEvent e) {
       if(e.getSource()==btn)
       //...
       if(e.getSource()==rdb1)
       //...        
    }
}

现在我想知道这种风格算是好的还是不好的?

1
我想了解一般情况下这是好事还是坏事,而不是什么都没有。 - Maroun
3
通过让主公共类实现ActionListener来暴露你的ActionListener是不好的风格,最好切换为内部类或匿名类。 - Robin
我只是想知道...为什么你要让生活变得复杂?只需要使用匿名类即可。 - Branislav Lazic
我对匿名类不是很熟悉,我会去了解一下它的优点的 :) 谢谢 - Maroun
一个好问题。我见过一些学院要求学生只使用一个监听器来处理完全不同性质的多个按钮。我很惊讶学校会这样做,因为我认为这种事件处理方式是一个非常糟糕的习惯。 - user3437460
现在使用Java 8,您可以使用lambda表达式:rdb1.addActionListener(e -> {}); - Maroun
5个回答

5

除非监听器是一个非常长的方法,我个人更喜欢匿名类模式:

        final JButton btn = new JButton("Button");
        final JRadioButton rdb1 = new JRadioButton("Value1");
        final ActionListener listener = new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                if (e.getSource() == btn) {
                    //...
                } else if (e.getSource() == rdb1) {
                    //...        
                }
            }
        };
        btn.addActionListener(listener);
        rdb1.addActionListener(listener);

甚至更好的是:
    btn.addActionListener(new ActionListener (){
         public void actionPerformed(ActionEvent e) {      
             // btn handling code
             }
    });
    rdb1.addActionListener(new ActionListener (){
         public void actionPerformed(ActionEvent e) {      
             // rdb1 handling code
             }
    });

您正在使用的模式允许其他类将TestApp类设置为其他类的监听器 - 除非这是有意的,否则这不是一种好的做法。

如果我的监听器很长,我应该像上面展示的那样保持它吗?还是使用匿名类更好? - Maroun
1
将长时间的侦听器放在方法中会使方法变得冗长和混乱,但非匿名类会污染您的命名空间。 - thedayofcondor
@Maroun85 如果你的监听器很长,你可能想将一些逻辑重构到另一个类中,比如我不希望看到监听器本身打开一个网络连接并下载网页。 - Martin
3
@Strawberry 是的,使用 if-else 来确定事件是由哪个控件生成的很麻烦,但不幸的是这是许多教程中都出现的一种模式。 - thedayofcondor
@thedayofcondor 同意。我发现平均教程通常忽略了好的面向对象实践。我已经数不清有多少个教程充斥着空值检查。 - Martin
显示剩余3条评论

4
很多时候取决于动作监听器所要执行的复杂程度。如果你想要小型、单一使用的操作,则匿名类是适合的选择。
使用这种监听器的主要优点是它将隔离动作正在执行的内容以及执行者。缺点在于,当监听器包含超过10行或更多代码时,阅读和确定监听器的实际结束位置就会变得困难。
在这种情况下,内部类可能更加适合。它具有匿名类的优点(与使用它的类相关联),但更易于阅读。
如果您想要可重复使用的操作(例如打开、新建、保存等),那么最好使用Action API,它提供了自我配置以及自包含的动作监听器。
依我之见。

1
如果您想要可重复使用的操作...请注意,每个“操作”都可以用于按钮(或主窗口中的保存按钮以及几个对话框)和菜单项。对我来说,这就解决了问题。 - Andrew Thompson
1
@AndrewThompson 和键绑定 ;) - MadProgrammer
"键绑定" - Andrew Thompson

3
更加面向对象的方式是创建一个匿名类来实现每个监听器。创建仅一个监听器来开关事件源组件并不易读,而且当监听器数量增加时容易出错。你很容易忘记在开关块(或一系列if-else块)中处理所有可能的事件源,这将导致静默错误行为(对于该情况什么也不会发生)。为每个组件添加单独的监听器将为您提供编译时检查,以确保您没有忘记处理它们所有。
public class TestApp {

    // you can initialize fields inline to make thing shorter and safer
    private JButton btn = new JButton("Button");
    private JRadioButton rdb1 = new JRadioButton("Value1");

    private void initialize() {
        btn.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // something
            }
        });
        rdb1.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // something else
            }
        });
    }
}

由于匿名类语法非常冗长,您可以通过将侦听器移动到私有字段中来缩短initialize方法的代码。
public class TestApp {

    private JButton btn = new JButton("Button");
    private JRadioButton rdb1 = new JRadioButton("Value1");

    private void initialize() {
        btn.addActionListener(btnListener);
        rdb1.addActionListener(rb1Listener);
    }

    private final ActionListener btnListener = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            // something
        }
    };

    private final ActionListener rb1Listener = new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
            // something else
        }
    };
}

2
“最面向对象”的说法我持有异议。您的回答没有考虑到实现类的继承。更面向对象的方法是使用一个专门的类来实现ActionListener或Action API - 在我看来 - 请注意,我并不质疑您的答案,在给定的用例中是正确的。 - MadProgrammer
1
也许不是“最面向对象的”,但肯定比一个会根据源代码切换的方法更加面向对象。在实现不同条件下的不同行为时,面向对象和过程化方法通常使用多态性和条件/开关之间的不同。 - Natix

2

这在一定程度上取决于您想在 actionPerformed 方法中做什么。如果没有其他类可能需要调用此方法,那么我可能会想通过创建内部类来减小 actionPerformed 方法的范围,例如:-

public class TestApp {

    private JFrame frame;
    private JButton btn;
    private JRadioButton rdb1;
    private JRadioButton rdb2; 

    private class CombinedActionListener implements ActionListener {
         public void actionPerformed(ActionEvent e) {
             if(e.getSource()==btn)
             //...
             if(e.getSource()==rdb1)
             //...        
             }
    }

    public static void main(String[] args) { /*....*/ }

    private void initialize() {
       ActionListener listener = new CombinedActionListener()

       //Each time I add a button, I add it to the listener:
       btn = new JButton("Button");
       btn.addActionListener(listener);
       //..
       rdb1 = new JRadioButton("Value1");
       rdb1.addActionListener(listener);
       //And so on...
    }
}

你甚至可以将监听器类作为静态内部类或顶层类,通过将按钮实例传递到构造函数中 - 这将使监听器类更容易进行测试。
正如我上面所说的,这在很大程度上取决于i)是否有其他人可能调用此方法和ii)方法内部逻辑的复杂性。

1
但是根据其他答案,每个按钮的匿名类可能是最好的方法。 - Martin
@Strawberry 这是有上下文的。在所提供的情况下可能是这样,但匿名的使用并不总是合适的。 - MadProgrammer
@MadProgrammer 您是正确的。我通常只在最简单的情况下使用匿名类(我在这里假设了这一点)。如果有任何复杂或协作逻辑,我通常会拆分为静态内部类或包私有顶级类。我的重点是能够测试逻辑。 - Martin

2

你可以考虑另外两个想法:

  1. 为每个UI元素提供自己的监听器;这样它们就完全独立了。
  2. 将监听器注入到你的Swing UI中,而不是调用new。这样用户就有机会根据自己的需要改变行为。让你的Swing UI做它应该做的事情:显示结果。我认为监听器是控制器逻辑的一部分。

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