做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
1package com.bzwm.testp.listener;
2
3import com.bzwm.testp.event.BaseEvent;
4
5/** *//**
6 * @author bzwm
7 *
8 */
9public interface IListener {
10
11 /** *//**
12 * 处理事件结束
13 * 这里将回调到画面上,方便对画面进行重画
14 * @param event
15 */
16 public void eventCompleted(BaseEvent event);
17}
2.IService.java
1package com.bzwm.testp.listener;
2
3import com.bzwm.testp.event.BaseEvent;
4
5/** *//**
6 * @author bzwm
7 *
8 */
9public 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接口
1package com.bzwm.testp.frame;
2
3import com.bzwm.testp.common.EventMgr;
4import com.bzwm.testp.event.BaseEvent;
5import com.bzwm.testp.event.LogonEvent;
6import com.bzwm.testp.listener.IListener;
7import com.bzwm.testp.listener.LogonService;
8import java.awt.BorderLayout;
9import java.awt.Color;
10import java.awt.FlowLayout;
11import java.awt.event.ActionEvent;
12import java.awt.event.ActionListener;
13import javax.swing.JButton;
14import javax.swing.JFrame;
15import javax.swing.JLabel;
16import javax.swing.JPanel;
17import javax.swing.JPasswordField;
18import javax.swing.JTextField;
19
20/** *//**
21 * @author bzwm
22 *
23 */
24public 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接口
1package com.bzwm.testp.listener;
2
3import com.bzwm.testp.event.BaseEvent;
4import com.bzwm.testp.event.LogonEvent;
5
6/** *//**
7 * @author bzwm
8 *
9 */
10public 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
管理事件
1package com.bzwm.testp.common;
2
3import com.bzwm.testp.event.BaseEvent;
4import com.bzwm.testp.listener.IListener;
5import com.bzwm.testp.listener.IService;
6import java.util.ArrayList;
7import java.util.List;
8
9/** *//**
10 * @author bzwm
11 *
12 */
13public 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
抽象类,抽象的事件
这其中的设计还很有待商榷
1package com.bzwm.testp.event;
2
3/** *//**
4 * @author bzwm
5 *
6 */
7public 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
用户登录事件
1package com.bzwm.testp.event;
2
3/** *//**
4 * @author bzwm
5 *
6 */
7public 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
1package com.bzwm.testp.listener;
2
3import com.bzwm.testp.event.BaseEvent;
4import com.bzwm.testp.event.LogonEvent;
5
6/** *//**
7 * @author bzwm
8 * 关于输出log类的设计,还可以改进,比如LogService可以是个抽象类,下面是具体的LogonLogService,
9 * 在父类中做一些共同的操作,比如IO操作等。
10 * 因为很多操作都要有log,可以试着给每个画面定义一个输出log的类,
11 * 专门来管理log信息。
12 * 这里为简单起见,也就不做那么多了,并且只将信息打印在控制台。
13 */
14public 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类。
只写出改的部分,它的构造方法:
1public 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日