做Swing桌面程序,该怎样将组件与业务逻辑分离?
这是一个问题。
因为没有深入学习过这方面的知识,所以自己也没有想过如何能实现这种分离。
今天有个朋友用Swing做了一个小的桌面程序,是一个简单的管理系统。
代码很复杂,主要是写的很复杂,没有逻辑和层次感,
到处是组件,到处是判断,每追加一个功能,代码就要翻来翻去找很久。
于是今天晚上自己闲来无事,简单的写了一些代码,
看是否可以将组件与业务逻辑分离开呢?
如果大侠看到了不要见笑啊~
首先,我要的是一个登录界面,
界面很简单,上面留一个JLabel的位置,输出错误提示信息用。
然后下面就是 用户名和密码 的输入框。
见下图:

待用户输入用户名和密码后,点击登录按钮,根据用户名和密码进行验证。
如果用户名和密码为空,则报错。
如果填写了用户名和密码,则认为登录成功,由于只是演示,就没有真正的去实现如何验证。
现在代码的结构是这样的。
|--com
|--bzwm
|--testp
|--common
|--event
|--frame
|--listener
如上图,主要由4个包。
common包,放一些通用的类,可以有一些工具类等等。
这次我将EventMgr.java放在common包下,用来管理事件。
event包,放入每个动作的event,
我先定义了一个抽象类BaseEvent.java
然后是本次要实现的功能的事件:LogonEvent.java
frame包,是一些画面的实现类。
本次登录用的LoadFrame.java放在这个包下。
listener包,是放具体的业务逻辑实现类的。
首先定义接口 IListener.java
画面实现 IListener 接口,方便回调回主画面,方便根据业务逻辑处理结果来重画组件。
然后定义接口 IService.java
业务逻辑类要实现 IService 接口,
我在这个接口中定义了两个方法,
第一个方法,检查用户的输入数据是否正确,或者说操作是否正确,
第二个方法,根据用户的输入和事件,来完成业务逻辑处理。
本次登录用的逻辑类 LogonService.java 实现了IService 接口。
好,结构介绍完了,
来看代码。
1.IListener.java
1
package com.bzwm.testp.listener;
2
3
import com.bzwm.testp.event.BaseEvent;
4
5
/** *//**
6
* @author bzwm
7
*
8
*/
9
public interface IListener
{
10
11
/** *//**
12
* 处理事件结束
13
* 这里将回调到画面上,方便对画面进行重画
14
* @param event
15
*/
16
public void eventCompleted(BaseEvent event);
17
}
2.IService.java
1
package com.bzwm.testp.listener;
2
3
import com.bzwm.testp.event.BaseEvent;
4
5
/** *//**
6
* @author bzwm
7
*
8
*/
9
public interface IService
{
10
/** *//**
11
* 开始处理事件
12
* 对用户的输入,操作做初步的检查,如果检查失败,则没有必要继续向下进行
13
* @param event
14
*/
15
public void eventStarted(BaseEvent event);
16
17
/** *//**
18
* 处理事件
19
* 进行一些逻辑处理
20
* 得到单纯的数据,不用考虑画面上的组件
21
* @param event
22
*/
23
public void service(BaseEvent event);
24
}
3.LoadFrame.java
主画面,程序的入口,实现了IListener接口
1
package com.bzwm.testp.frame;
2
3
import com.bzwm.testp.common.EventMgr;
4
import com.bzwm.testp.event.BaseEvent;
5
import com.bzwm.testp.event.LogonEvent;
6
import com.bzwm.testp.listener.IListener;
7
import com.bzwm.testp.listener.LogonService;
8
import java.awt.BorderLayout;
9
import java.awt.Color;
10
import java.awt.FlowLayout;
11
import java.awt.event.ActionEvent;
12
import java.awt.event.ActionListener;
13
import javax.swing.JButton;
14
import javax.swing.JFrame;
15
import javax.swing.JLabel;
16
import javax.swing.JPanel;
17
import javax.swing.JPasswordField;
18
import javax.swing.JTextField;
19
20
/** *//**
21
* @author bzwm
22
*
23
*/
24
public class LoadFrame extends JFrame implements IListener
{
25
/** *//** 标题:用户ID */
26
private JLabel userIdLbl = null;
27
28
/** *//** 输入框:用户ID */
29
private JTextField userIdTxt = null;
30
31
/** *//** 标题:密码 */
32
private JLabel passwordLbl = null;
33
34
/** *//** 输入框:密码 */
35
private JPasswordField passwordTxt = null;
36
37
/** *//** 登录按钮 */
38
private JButton logonBtn = null;
39
40
/** *//** 重置按钮 */
41
private JButton resetBtn = null;
42
43
/** *//** 新用户注册按钮 */
44
private JButton registerBtn = null;
45
46
/** *//** 错误提示信息 */
47
private JLabel errorLbl = null;
48
49
EventMgr mrg = null;
50
51
/** *//**
52
* 构造方法
53
*
54
* @param title
55
* 标题
56
*/
57
public LoadFrame(String title)
{
58
super(title);
59
initComponents();
60
layoutComponents();
61
mrg = new EventMgr();
62
// 追加监听器
63
mrg.addListener(this);
64
mrg.addService(new LogonService());
65
}
66
67
/** *//**
68
* 对组件进行初始化
69
*
70
*/
71
private void initComponents()
{
72
userIdLbl = new JLabel("用户名");
73
userIdTxt = new JTextField(10);
74
75
passwordLbl = new JLabel("密码");
76
passwordTxt = new JPasswordField(10);
77
78
logonBtn = new JButton("登录");
79
resetBtn = new JButton("重置");
80
registerBtn = new JButton("注册");
81
82
errorLbl = new JLabel();
83
errorLbl.setForeground(Color.RED);
84
}
85
86
/** *//**
87
* 进行布局,给组件添加事件监听
88
*
89
*/
90
private void layoutComponents()
{
91
JPanel topPnl = new JPanel();
92
topPnl.add(errorLbl);
93
94
JPanel mainPnl = new JPanel(new BorderLayout());
95
JPanel inputPnl = new JPanel(new java.awt.GridLayout(2, 2, 2, 2));
96
inputPnl.add(userIdLbl);
97
inputPnl.add(userIdTxt);
98
inputPnl.add(passwordLbl);
99
inputPnl.add(passwordTxt);
100
inputPnl.setSize(280, 100);
101
mainPnl.add(inputPnl, BorderLayout.NORTH);
102
103
JPanel footPnl = new JPanel(new FlowLayout(FlowLayout.RIGHT));
104
logonBtn.addActionListener(new ActionListener()
{
105
public void actionPerformed(ActionEvent e)
{
106
logonBtnClick();
107
}
108
});
109
resetBtn.addActionListener(new ActionListener()
{
110
public void actionPerformed(ActionEvent e)
{
111
resetBtnClick();
112
}
113
});
114
115
registerBtn.addActionListener(new ActionListener()
{
116
public void actionPerformed(ActionEvent e)
{
117
regBtnClick();
118
}
119
});
120
121
footPnl.add(logonBtn);
122
footPnl.add(resetBtn);
123
footPnl.add(registerBtn);
124
mainPnl.add(footPnl, BorderLayout.SOUTH);
125
this.add(topPnl, BorderLayout.NORTH);
126
this.add(mainPnl, BorderLayout.CENTER);
127
this.setLocation(200, 200);
128
this.setSize(300, 300);
129
this.setVisible(true);
130
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
131
}
132
133
/** *//**
134
*
135
*/
136
private void logonBtnClick()
{
137
new Thread()
{
138
public void run()
{
139
mrg.dispatch(new LogonEvent(userIdTxt.getText(), passwordTxt.getText()));
140
}
141
}.start();
142
}
143
144
/** *//**
145
*
146
*/
147
private void resetBtnClick()
{
148
userIdTxt.setText("");
149
passwordTxt.setText("");
150
}
151
152
/** *//**
153
*
154
*/
155
private void regBtnClick()
{
156
}
157
158
/**//*
159
* (non-Javadoc)
160
*
161
* @see com.bzwm.testp.listener.IListener#eventCompleted(com.bzwm.testp.event.BaseEvent)
162
*/
163
public void eventCompleted(BaseEvent event)
{
164
String error = event.getError();
165
if (error.equals(""))
{
166
errorLbl.setText("登录成功");
167
} else
{
168
errorLbl.setText(error);
169
}
170
}
171
172
public static void main(String args[])
{
173
new LoadFrame("LogonFrame");
174
}
175
}
4.LogonService.java
登录事件的逻辑处理类
实现了IService接口
1
package com.bzwm.testp.listener;
2
3
import com.bzwm.testp.event.BaseEvent;
4
import com.bzwm.testp.event.LogonEvent;
5
6
/** *//**
7
* @author bzwm
8
*
9
*/
10
public class LogonService implements IService
{
11
12
/**//*
13
* (non-Javadoc)
14
*
15
* @see com.bzwm.testp.listener.IService#eventStarted(com.bzwm.testp.event.BaseEvent)
16
*/
17
public void eventStarted(BaseEvent event)
{
18
LogonEvent logonEvent = (LogonEvent) event;
19
String id = logonEvent.getId();
20
String password = logonEvent.getPassword();
21
// 对用户的输入进行初步check
22
if (id == null || "".equals(id))
{
23
event.setError("用户名为空");
24
}
25
if (password == null || "".equals(password))
{
26
event.setError("密码为空");
27
}
28
}
29
30
/**//*
31
* (non-Javadoc)
32
*
33
* @see com.bzwm.testp.listener.IService#service(com.bzwm.testp.event.BaseEvent)
34
*/
35
public void service(BaseEvent event)
{
36
if (!event.isError())
{
37
LogonEvent logonEvent = (LogonEvent) event;
38
// 用户id和密码已经经过的初步check,现在去登录,检查是否已经注册
39
logon(logonEvent);
40
// 这里停顿秒,算是去服务器验证了
41
try
{
42
Thread.sleep(5000);
43
} catch (InterruptedException e)
{
44
e.printStackTrace();
45
}
46
}
47
}
48
49
private void logon(LogonEvent e)
{
50
// 验证DB,用户ID是否存在,密码是否正确
51
// 如果不存在,则setError(),密码不正确,setError()
52
//
53
}
54
}
5.EventMgr.java
管理事件
1
package com.bzwm.testp.common;
2
3
import com.bzwm.testp.event.BaseEvent;
4
import com.bzwm.testp.listener.IListener;
5
import com.bzwm.testp.listener.IService;
6
import java.util.ArrayList;
7
import java.util.List;
8
9
/** *//**
10
* @author bzwm
11
*
12
*/
13
public class EventMgr
{
14
/** *//** 回调回主画面的监听集合 */
15
private List<IListener> listeners = new ArrayList<IListener>();
16
17
/** *//** 业务逻辑的监听集合 */
18
private List<IService> services = new ArrayList<IService>();
19
20
/** *//**
21
*
22
* @param event
23
*/
24
public void dispatch(BaseEvent event)
{
25
26
fireEventStarted(event);
27
28
fireEventService(event);
29
30
fireEventCompleted(event);
31
}
32
33
/** *//**
34
*
35
* @param listener
36
*/
37
public void addListener(IListener listener)
{
38
listeners.add(listener);
39
}
40
41
/** *//**
42
*
43
* @param listener
44
*/
45
public void removeListener(IListener listener)
{
46
listeners.remove(listener);
47
}
48
49
/** *//**
50
*
51
* @param service
52
*/
53
public void addService(IService service)
{
54
services.add(service);
55
}
56
57
/** *//**
58
*
59
* @param service
60
*/
61
public void removeService(IService service)
{
62
services.remove(service);
63
}
64
65
/** *//**
66
*
67
* @param event
68
*/
69
private void fireEventStarted(BaseEvent event)
{
70
for (IService service : services)
{
71
service.eventStarted(event);
72
}
73
}
74
75
/** *//**
76
*
77
* @param event
78
*/
79
private void fireEventService(BaseEvent event)
{
80
for (IService service : services)
{
81
service.service(event);
82
}
83
}
84
85
/** *//**
86
*
87
* @param event
88
*/
89
private void fireEventCompleted(BaseEvent event)
{
90
for (IListener listener : listeners)
{
91
listener.eventCompleted(event);
92
}
93
}
94
}
6.BaseEvent.java
抽象类,抽象的事件
这其中的设计还很有待商榷
1
package com.bzwm.testp.event;
2
3
/** *//**
4
* @author bzwm
5
*
6
*/
7
public abstract class BaseEvent
{
8
9
/** *//**
10
* 处理过程中的错误信息 这里可以选择自定义一个Error类来完成 由于只是示范,就不写了 另外,这种保存error信息的方法确实很笨
11
*/
12
private String error = "";
13
14
/** *//**
15
* 保存处理返回的结果 这里可以自定义一个Data来管理 由于是示范,也就不写了 用一个Object返回处理结果,有很多局限性
16
*/
17
private Object result = null;
18
19
/** *//**
20
* @return the result
21
*/
22
public Object getResult()
{
23
return result;
24
}
25
26
/** *//**
27
* @param result
28
* the result to set
29
*/
30
public void setResult(Object result)
{
31
this.result = result;
32
}
33
34
/** *//**
35
* @return the error
36
*/
37
public String getError()
{
38
return error;
39
}
40
41
/** *//**
42
* @param error
43
* the error to set
44
*/
45
public void setError(String error)
{
46
this.error = this.error + " " + error;
47
}
48
49
public boolean isError()
{
50
return !error.equals("");
51
}
52
}
7.LogonEvent.java
用户登录事件
1
package com.bzwm.testp.event;
2
3
/** *//**
4
* @author bzwm
5
*
6
*/
7
public class LogonEvent extends BaseEvent
{
8
9
/** *//** 用户ID */
10
private String id = null;
11
12
/** *//** 密码 */
13
private String password = null;
14
15
public LogonEvent(String i, String p)
{
16
setId(i);
17
setPassword(p);
18
}
19
20
/** *//**
21
* @return the id
22
*/
23
public String getId()
{
24
return id;
25
}
26
27
/** *//**
28
* @param id
29
* the id to set
30
*/
31
public void setId(String id)
{
32
this.id = id;
33
}
34
35
/** *//**
36
* @return the password
37
*/
38
public String getPassword()
{
39
return password;
40
}
41
42
/** *//**
43
* @param password
44
* the password to set
45
*/
46
public void setPassword(String password)
{
47
this.password = password;
48
}
49
}
根据上述代码,基本功能完成了。
也许会感觉为这一点功能写这么多类不值得,
但代码会清晰一点,虽然设计上,比如类的构造上还不够合理,
我也没思考太多,想到这些就写出来了。
不过这样还有一个好处,比如说,你想给登录事件加输出log的功能,
那么比较简单,
只要写一个类,实现了IService即可。(因为log写在本地文件里,所以不用回调回主画面)
然后在LoadFrame.java中的构造方法改一下就OK了。
下面是输出log用的service
8.LogService.java
1
package com.bzwm.testp.listener;
2
3
import com.bzwm.testp.event.BaseEvent;
4
import com.bzwm.testp.event.LogonEvent;
5
6
/** *//**
7
* @author bzwm
8
* 关于输出log类的设计,还可以改进,比如LogService可以是个抽象类,下面是具体的LogonLogService,
9
* 在父类中做一些共同的操作,比如IO操作等。
10
* 因为很多操作都要有log,可以试着给每个画面定义一个输出log的类,
11
* 专门来管理log信息。
12
* 这里为简单起见,也就不做那么多了,并且只将信息打印在控制台。
13
*/
14
public class LogService implements IService
{
15
16
/**//*
17
* (non-Javadoc)
18
*
19
* @see com.bzwm.testp.listener.IService#eventStarted(com.bzwm.testp.event.BaseEvent)
20
*/
21
public void eventStarted(BaseEvent event)
{
22
// TODO Auto-generated method stub
23
if (event instanceof LogonEvent)
{
24
LogonEvent e = (LogonEvent) event;
25
System.out.println(e.getId() + " 准备登录了");
26
}
27
}
28
29
/**//*
30
* (non-Javadoc)
31
*
32
* @see com.bzwm.testp.listener.IService#service(com.bzwm.testp.event.BaseEvent)
33
*/
34
public void service(BaseEvent event)
{
35
// TODO Auto-generated method stub
36
if (event instanceof LogonEvent)
{
37
LogonEvent e = (LogonEvent) event;
38
String error = e.getError();
39
if (error.equals(""))
{
40
System.out.println(e.getId() + " 登录了");
41
} else
{
42
System.out.println(e.getId() + " 登录失败了 " + error);
43
}
44
}
45
}
46
}
47
接着,改 LoadFrame.java类。
只写出改的部分,它的构造方法:
1
public LoadFrame(String title)
{
2
super(title);
3
initComponents();
4
layoutComponents();
5
mrg = new EventMgr();
6
// 追加监听器
7
mrg.addListener(this);
8
mrg.addService(new LogonService());
9
mrg.addService(new LogService());
10
}
这样,把代码拷贝下来,
重新运行下,就出现log了。
这只是登录的功能,
如果还要完成注册的功能,
则还是一样的步骤,
写一个Dialog类,继承JDialog,实现IListener接口,
定义一个RegEvent.java继承BaseEvent.java,
写一个RegService类,实现IService接口,
总之把相应包下面,加入自己的实现,
然后将监听还是注册到EventMgr里就可以实现了。
----2009年01月22日