免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 3080 | 回复: 1
打印 上一主题 下一主题

[转帖]JFC:With listener design, OO matters (best way to desi [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2002-12-03 11:53 |只看该作者 |倒序浏览
不知大家英文水平如何,此篇介绍,在界面编程里如何用面向对象的方法写 ActionListener

With listener design, OO matters
Find out the best way to design your listeners

By Tony Sintes

March 22, 2002

In "It Might Be Efficient, But It Ain't OO," I presented an alternative way to implement the java.awt.ActionListener interface. In my opinion, creating one large actionPerformed() method and simply switching between the various action commands does not represent the best approach. I said such an approach doesn't follow good object-oriented (OO) design.

Such statements always cause controversy, and, needless to say, my comments did! In fact, my answer generated quite a bit of reader feedback. In this Java Q&A, I present concrete examples I hope will justify my position and help you implement this practice in your programs. As an added bonus, my approach applies to any interface implementation you might write.

Let's start with a simple GUI (graphical user interface).

Figure 1's GUI comprises a panel containing three buttons and a label. When you press a button, the GUI writes a message to the label indicating which button you pressed.

To display the panel, I embedded it within a frame. However, for the purposes of today's Java Q&A, I'll simply focus on the panel code. (Please see Resources for the full source code.)

Implementation
Following the discussion in the original Java Q&A, you can implement the buttons' listeners in two ways. First, the panel can implement the ActionListener interface directly, or, second, you can create three listener classes whose instances listen to the buttons directly. Let's look at the first choice in which the panel implements one large listener.

Wire the buttons directly to the panel
The following class shows how to wire the buttons directly to the panel:


import java.awt.BorderLayout&#59;
import java.awt.event.ActionEvent&#59;
import java.awt.event.ActionListener&#59;

import javax.swing.JButton&#59;
import javax.swing.JLabel&#59;
import javax.swing.JPanel&#59;

/**
* The ActionListenerGUI is an example of a simple GUI that directly implements
* the ActionListener Interface.
* @author  Tony Sintes
*/
public class ActionListenerPanel extends JPanel implements ActionListener {

    public ActionListenerPanel() {
        setup()&#59;
    }

    private void setup() {
        setLayout( new BorderLayout() )&#59;
        
        JButton button1 = new JButton( BUTTON_1 )&#59;
        button1.setActionCommand( BUTTON_1_ACTION )&#59;
        button1.addActionListener( this )&#59;
        
        JButton button2 =  new JButton( BUTTON_2 )&#59;
        button2.setActionCommand( BUTTON_2_ACTION )&#59;
        button2.addActionListener( this )&#59;
        
        JButton button3 =  new JButton( BUTTON_3 )&#59;
        button3.setActionCommand( BUTTON_3_ACTION )&#59;
        button3.addActionListener( this )&#59;
        
        JPanel buttons = new JPanel()&#59;
        buttons.add( button1 )&#59;
        buttons.add( button2 )&#59;
        buttons.add( button3 )&#59;
        
        add( display, BorderLayout.NORTH )&#59;
        add( buttons, BorderLayout.SOUTH )&#59;
    }
   
    public void actionPerformed( ActionEvent actionEvent ) {
        String actionName = actionEvent.getActionCommand().trim()&#59;
        
        if( actionName.equals( BUTTON_1_ACTION ) ) {
            display.setText( BUTTON_1 )&#59;
        }
        else if( actionName.equals( BUTTON_2_ACTION ) ) {
            display.setText( BUTTON_2 )&#59;
        }
        else if( actionName.equals( BUTTON_3_ACTION ) ) {
            display.setText( BUTTON_3 )&#59;
        }   
    }
   
    private JLabel display = new JLabel(&quotress a button&quot&#59;
   
    private static String BUTTON_1 = "Button 1"&#59;
    private static String BUTTON_2 = "Button 2"&#59;
    private static String BUTTON_3 = "Button 3"&#59;
    private static String BUTTON_1_ACTION = "B1"&#59;
    private static String BUTTON_2_ACTION = "B2"&#59;
    private static String BUTTON_3_ACTION = "B3"&#59;
}


In the code above, notice the setup() and actionPerformed() methods. In setup(), I create three buttons. Each button creation takes the following form:



JButton buttonN = new JButton( BUTTON_N )&#59;
buttonN.setActionCommand( BUTTON_N_ACTION )&#59;
buttonN.addActionListener( this )&#59;


Each time I add a button, I give it an action command and set the panel directly as the listener, a design choice whose outcome becomes apparent in actionPerformed()'s implementation:



public void actionPerformed( ActionEvent actionEvent ) {
    String actionName = actionEvent.getActionCommand().trim()&#59;
        
    if( actionName.equals( BUTTON_1_ACTION ) ) {
        display.setText( BUTTON_1 )&#59;
    }
    else if( actionName.equals( BUTTON_2_ACTION ) ) {
        display.setText( BUTTON_2 )&#59;
    }
    else if( actionName.equals( BUTTON_3_ACTION ) ) {
        display.setText( BUTTON_3 )&#59;
    }   
}


Every time I call actionPerformed(), it must first switch through the action command names to determine which button I pressed, and then take the proper action.

Next, let's look at an approach that completely removes the if/else mapping.

Wire the buttons to their own listeners
The following class wires each button directly to its own listener:



import java.awt.BorderLayout&#59;
import java.awt.event.ActionEvent&#59;
import java.awt.event.ActionListener&#59;

import javax.swing.JButton&#59;
import javax.swing.JLabel&#59;
import javax.swing.JPanel&#59;

/**
* ModularGUI presents an alternative to having the main GUI implement the
* ActionListener interface directly.
* @author  Tony Sintes
*/
public class ModularPanel extends JPanel {

    public ModularPanel() {
        setup()&#59;
    }

    private void setup() {
        setLayout( new BorderLayout() )&#59;
        
        JButton button1 = new JButton( BUTTON_1 )&#59;
        button1.addActionListener( new Button1Action () )&#59;
        
        JButton button2 = new JButton( BUTTON_2 )&#59;
        button2.addActionListener( new Button2Action() )&#59;
        
        JButton button3 = new JButton( BUTTON_3 )&#59;
        button3.addActionListener( new Button3Action() )&#59;
        
        JPanel buttons = new JPanel()&#59;
        buttons.add( button1 )&#59;
        buttons.add( button2 )&#59;
        buttons.add( button3 )&#59;
        
        add( display, BorderLayout.NORTH )&#59;
        add( buttons, BorderLayout.SOUTH )&#59;
    }
   
    private JLabel display = new JLabel(&quotress a button&quot&#59;
   
    private static String BUTTON_1 = "Button 1"&#59;
    private static String BUTTON_2 = "Button 2"&#59;
    private static String BUTTON_3 = "Button 3"&#59;
   
    private class Button1Action implements ActionListener {
        public void actionPerformed( ActionEvent actionEvent ) {
            display.setText( BUTTON_1 )&#59;
        }
    }
   
    private class Button2Action implements ActionListener {
        public void actionPerformed( ActionEvent actionEvent ) {
            display.setText( BUTTON_2 )&#59;
        }
    }
   
    private class Button3Action implements ActionListener {
        public void actionPerformed( ActionEvent actionEvent ) {
            display.setText( BUTTON_3 )&#59;
        }
    }
}


Again, notice the setup() and actionPerformed() methods. In setup(), note the button creation takes a slightly different pattern:



JButton buttonN = new JButton( BUTTON_N )&#59;
buttonN.addActionListener( new ButtonNAction () )&#59;


Now, whenever I create a button, I instantiate a new listener and directly associate it with the button. I need not assign an action command name. You can easily see that design choice's outcome in the actionPerformed() implementation. The panel doesn't implement the method any longer!

Instead of the panel directly implementing a large actionPerformed() method, each button should be associated in a one-to-one relationship with an object customized for listening to the specific button. Here are the ActionListener implementations:



private class Button1Action implements ActionListener {
    public void actionPerformed( ActionEvent actionEvent ) {
        display.setText( BUTTON_1 )&#59;
    }
}
   
private class Button2Action implements ActionListener {
    public void actionPerformed( ActionEvent actionEvent ) {
        display.setText( BUTTON_2 )&#59;
    }
}
   
private class Button3Action implements ActionListener {
    public void actionPerformed( ActionEvent actionEvent ) {
        display.setText( BUTTON_3 )&#59;
    }
}


Notice how I can completely remove the if/else mess present in the first implementation. Each case now becomes its own object. When I wire the buttons this way, the proper actionPerformed() method will get called directly when I press the button -- no need to switch through action command names. A clean design does the mapping work for you.

Here, I chose to implement the listeners as inner classes. Depending upon your application and tastes, you can also implement listeners as anonymous classes, as standalone classes, or as a combination of inner/anonymous classes and standalone classes. For example, an ActionListener could delegate the actual response to some other object. Such an approach helps keep the response code independent of the GUI, thus letting you reuse your actions elsewhere and easily change your GUI's behavior.

How the approaches differ
The two approaches differ in several ways.

Object responsibility
Whether or not you like the second approach depends on how you view responsibilities.

The first approach assumes that the panel's responsibilities include:


Building the display
Wiring the buttons to the listeners
Mapping the action events to the proper response
Performing the proper response (or perhaps delegating the response to another object or to a method)
The second approach assumes that the panel's responsibilities include:


Building the display
Wiring the buttons to the listeners
The second approach responds to the action events out of the panel and places that responsibility into a separate object. It also does not assume the panel maps a button's action to the action to perform. Instead, that responsibility lies in the button itself. Instead of a large if/else switch, the button can call the object that knows how to perform the action directly.

Yes, nothing stops you from delegating to a second object from within the if/else block, but why insist on the if/else switch block if you don't need it? Why embed that responsibility in the GUI when objects can do it for you?

Good OO design tells us that an object should have only one responsibility and that behavior should be grouped. With that in mind, the second approach better embodies good OO design: the GUI builds the GUI (which includes hooking up the listeners), while the individual ActionListeners perform one action. In the first approach, the GUI does everything.

I like most how the second approach encourages the Command design pattern. The Command pattern handily turns actions into objects. Once your actions are objects, you can benefit from the advantages objects give, such as reuse and plugability.

Maintenance
From a maintenance perspective, I think that the first approach demands more work. That is, to add a new button, you must create a new button, associate a new action command name with it, then add another switch to the monolithic actionPerformed() method (keeping the action name straight).

In contrast, the second approach lets you simply create a new button, define an object that responds to the button, and wire the two together. Since we're programming in objects, I find it easier to read and understand small objects. I don't like wading through pages of if/else statements to find and debug actions.

One reader suggested putting the action into a method to increase readability. While that might help, it forces me to ask what we are trying to accomplish. Certainly we're not trying to perform procedural programming. Placing the action into a proc--- method smacks of procedural programming. The action doesn't belong inside of a method&#59; it belongs inside an object.

For me, if we're using Java so we can program in an object-oriented way, we should take full advantage of objects, which includes taking the intellectual leap and turning our actions into objects in their own right.

By using objects, I hope to write software that is flexible, easy to understand, and reusable -- goals the second approach accomplishes, while the first approach does not.

A final word on efficiency
This article arose as a response to the argument that creating so many objects somehow proves inefficient. However, at worst you pay the small memory overhead from a few extra listener objects. From a performance point of view, eliminating the long if/else switch can improve performance -- you can avoid all those checks.

From a developer's point of view, I think the object-based approach represents an effective tradeoff given how it will help future maintenance and extension. As a user, I'd rather give up a little memory for a better performing application. Finally, in large applications, I have seen dramatic responsiveness improvements by moving to the object-based approach.

论坛徽章:
0
2 [报告]
发表于 2002-12-03 11:56 |只看该作者

[转帖]JFC:With listener design, OO matters (best way to desi

An Advanced topic:

JFC actions
Use the powerful Command pattern with the JFC Action interface to build a reusable GUI-command library

http://www.javaworld.com/javaworld/jw-04-2000/jw-0414-action.html

用 Command 设计模式 和 javax.swing.AbstractAction 设计界面是一种很好的设计。

您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP