做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
package com.bzwm.testp.listener; import com.bzwm.testp.event.BaseEvent; /** * @author bzwm * */ public interface IListener { /** * 处理事件结束 * 这里将回调到画面上,方便对画面进行重画 * @param event */ public void eventCompleted(BaseEvent event); }
2.IService.java
package com.bzwm.testp.listener; import com.bzwm.testp.event.BaseEvent; /** * @author bzwm * */ public interface IService { /** * 开始处理事件 * 对用户的输入,操作做初步的检查,如果检查失败,则没有必要继续向下进行 * @param event */ public void eventStarted(BaseEvent event); /** * 处理事件 * 进行一些逻辑处理 * 得到单纯的数据,不用考虑画面上的组件 * @param event */ public void service(BaseEvent event); }
3.LoadFrame.java
主画面,程序的入口,实现了IListener接口
package com.bzwm.testp.frame; import com.bzwm.testp.common.EventMgr; import com.bzwm.testp.event.BaseEvent; import com.bzwm.testp.event.LogonEvent; import com.bzwm.testp.listener.IListener; import com.bzwm.testp.listener.LogonService; import java.awt.BorderLayout; import java.awt.Color; import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JPasswordField; import javax.swing.JTextField; /** * @author bzwm * */ public class LoadFrame extends JFrame implements IListener { /** 标题:用户ID */ private JLabel userIdLbl = null; /** 输入框:用户ID */ private JTextField userIdTxt = null; /** 标题:密码 */ private JLabel passwordLbl = null; /** 输入框:密码 */ private JPasswordField passwordTxt = null; /** 登录按钮 */ private JButton logonBtn = null; /** 重置按钮 */ private JButton resetBtn = null; /** 新用户注册按钮 */ private JButton registerBtn = null; /** 错误提示信息 */ private JLabel errorLbl = null; EventMgr mrg = null; /** * 构造方法 * * @param title * 标题 */ public LoadFrame(String title) { super(title); initComponents(); layoutComponents(); mrg = new EventMgr(); // 追加监听器 mrg.addListener(this); mrg.addService(new LogonService()); } /** * 对组件进行初始化 * */ private void initComponents() { userIdLbl = new JLabel("用户名"); userIdTxt = new JTextField(10); passwordLbl = new JLabel("密码"); passwordTxt = new JPasswordField(10); logonBtn = new JButton("登录"); resetBtn = new JButton("重置"); registerBtn = new JButton("注册"); errorLbl = new JLabel(); errorLbl.setForeground(Color.RED); } /** * 进行布局,给组件添加事件监听 * */ private void layoutComponents() { JPanel topPnl = new JPanel(); topPnl.add(errorLbl); JPanel mainPnl = new JPanel(new BorderLayout()); JPanel inputPnl = new JPanel(new java.awt.GridLayout(2, 2, 2, 2)); inputPnl.add(userIdLbl); inputPnl.add(userIdTxt); inputPnl.add(passwordLbl); inputPnl.add(passwordTxt); inputPnl.setSize(280, 100); mainPnl.add(inputPnl, BorderLayout.NORTH); JPanel footPnl = new JPanel(new FlowLayout(FlowLayout.RIGHT)); logonBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { logonBtnClick(); } }); resetBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { resetBtnClick(); } }); registerBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { regBtnClick(); } }); footPnl.add(logonBtn); footPnl.add(resetBtn); footPnl.add(registerBtn); mainPnl.add(footPnl, BorderLayout.SOUTH); this.add(topPnl, BorderLayout.NORTH); this.add(mainPnl, BorderLayout.CENTER); this.setLocation(200, 200); this.setSize(300, 300); this.setVisible(true); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } /** * */ private void logonBtnClick() { new Thread() { public void run() { mrg.dispatch(new LogonEvent(userIdTxt.getText(), passwordTxt.getText())); } }.start(); } /** * */ private void resetBtnClick() { userIdTxt.setText(""); passwordTxt.setText(""); } /** * */ private void regBtnClick() { } /* * (non-Javadoc) * * @see com.bzwm.testp.listener.IListener#eventCompleted(com.bzwm.testp.event.BaseEvent) */ public void eventCompleted(BaseEvent event) { String error = event.getError(); if (error.equals("")) { errorLbl.setText("登录成功"); } else { errorLbl.setText(error); } } public static void main(String args[]) { new LoadFrame("LogonFrame"); } }
4.LogonService.java
登录事件的逻辑处理类
实现了IService接口
package com.bzwm.testp.listener; import com.bzwm.testp.event.BaseEvent; import com.bzwm.testp.event.LogonEvent; /** * @author bzwm * */ public class LogonService implements IService { /* * (non-Javadoc) * * @see com.bzwm.testp.listener.IService#eventStarted(com.bzwm.testp.event.BaseEvent) */ public void eventStarted(BaseEvent event) { LogonEvent logonEvent = (LogonEvent) event; String id = logonEvent.getId(); String password = logonEvent.getPassword(); // 对用户的输入进行初步check if (id == null || "".equals(id)) { event.setError("用户名为空"); } if (password == null || "".equals(password)) { event.setError("密码为空"); } } /* * (non-Javadoc) * * @see com.bzwm.testp.listener.IService#service(com.bzwm.testp.event.BaseEvent) */ public void service(BaseEvent event) { if (!event.isError()) { LogonEvent logonEvent = (LogonEvent) event; // 用户id和密码已经经过的初步check,现在去登录,检查是否已经注册 logon(logonEvent); // 这里停顿秒,算是去服务器验证了 try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } } } private void logon(LogonEvent e) { // 验证DB,用户ID是否存在,密码是否正确 // 如果不存在,则setError(),密码不正确,setError() // ... } }
5.EventMgr.java
管理事件
package com.bzwm.testp.common; import com.bzwm.testp.event.BaseEvent; import com.bzwm.testp.listener.IListener; import com.bzwm.testp.listener.IService; import java.util.ArrayList; import java.util.List; /** * @author bzwm * */ public class EventMgr { /** 回调回主画面的监听集合 */ private List<IListener> listeners = new ArrayList<IListener>(); /** 业务逻辑的监听集合 */ private List<IService> services = new ArrayList<IService>(); /** * * @param event */ public void dispatch(BaseEvent event) { fireEventStarted(event); fireEventService(event); fireEventCompleted(event); } /** * * @param listener */ public void addListener(IListener listener) { listeners.add(listener); } /** * * @param listener */ public void removeListener(IListener listener) { listeners.remove(listener); } /** * * @param service */ public void addService(IService service) { services.add(service); } /** * * @param service */ public void removeService(IService service) { services.remove(service); } /** * * @param event */ private void fireEventStarted(BaseEvent event) { for (IService service : services) { service.eventStarted(event); } } /** * * @param event */ private void fireEventService(BaseEvent event) { for (IService service : services) { service.service(event); } } /** * * @param event */ private void fireEventCompleted(BaseEvent event) { for (IListener listener : listeners) { listener.eventCompleted(event); } } }
6.BaseEvent.java
抽象类,抽象的事件
这其中的设计还很有待商榷
package com.bzwm.testp.event; /** * @author bzwm * */ public abstract class BaseEvent { /** * 处理过程中的错误信息 这里可以选择自定义一个Error类来完成 由于只是示范,就不写了 另外,这种保存error信息的方法确实很笨 */ private String error = ""; /** * 保存处理返回的结果 这里可以自定义一个Data来管理 由于是示范,也就不写了 用一个Object返回处理结果,有很多局限性 */ private Object result = null; /** * @return the result */ public Object getResult() { return result; } /** * @param result * the result to set */ public void setResult(Object result) { this.result = result; } /** * @return the error */ public String getError() { return error; } /** * @param error * the error to set */ public void setError(String error) { this.error = this.error + " " + error; } public boolean isError() { return !error.equals(""); } }
7.LogonEvent.java
用户登录事件
package com.bzwm.testp.event; /** * @author bzwm * */ public class LogonEvent extends BaseEvent { /** 用户ID */ private String id = null; /** 密码 */ private String password = null; public LogonEvent(String i, String p) { setId(i); setPassword(p); } /** * @return the id */ public String getId() { return id; } /** * @param id * the id to set */ public void setId(String id) { this.id = id; } /** * @return the password */ public String getPassword() { return password; } /** * @param password * the password to set */ public void setPassword(String password) { this.password = password; } }
根据上述代码,基本功能完成了。
也许会感觉为这一点功能写这么多类不值得,
但代码会清晰一点,虽然设计上,比如类的构造上还不够合理,
我也没思考太多,想到这些就写出来了。
不过这样还有一个好处,比如说,你想给登录事件加输出log的功能,
那么比较简单,
只要写一个类,实现了IService即可。(因为log写在本地文件里,所以不用回调回主画面)
然后在LoadFrame.java中的构造方法改一下就OK了。
下面是输出log用的service
8.LogService.java
package com.bzwm.testp.listener; import com.bzwm.testp.event.BaseEvent; import com.bzwm.testp.event.LogonEvent; /** * @author bzwm * 关于输出log类的设计,还可以改进,比如LogService可以是个抽象类,下面是具体的LogonLogService, * 在父类中做一些共同的操作,比如IO操作等。 * 因为很多操作都要有log,可以试着给每个画面定义一个输出log的类, * 专门来管理log信息。 * 这里为简单起见,也就不做那么多了,并且只将信息打印在控制台。 */ public class LogService implements IService { /* * (non-Javadoc) * * @see com.bzwm.testp.listener.IService#eventStarted(com.bzwm.testp.event.BaseEvent) */ public void eventStarted(BaseEvent event) { // TODO Auto-generated method stub if (event instanceof LogonEvent) { LogonEvent e = (LogonEvent) event; System.out.println(e.getId() + " 准备登录了"); } } /* * (non-Javadoc) * * @see com.bzwm.testp.listener.IService#service(com.bzwm.testp.event.BaseEvent) */ public void service(BaseEvent event) { // TODO Auto-generated method stub if (event instanceof LogonEvent) { LogonEvent e = (LogonEvent) event; String error = e.getError(); if (error.equals("")) { System.out.println(e.getId() + " 登录了"); } else { System.out.println(e.getId() + " 登录失败了 " + error); } } } }
接着,改 LoadFrame.java类。
只写出改的部分,它的构造方法:
public LoadFrame(String title) { super(title); initComponents(); layoutComponents(); mrg = new EventMgr(); // 追加监听器 mrg.addListener(this); mrg.addService(new LogonService()); mrg.addService(new LogService()); }
这样,把代码拷贝下来,
重新运行下,就出现log了。
这只是登录的功能,
如果还要完成注册的功能,
则还是一样的步骤,
写一个Dialog类,继承JDialog,实现IListener接口,
定义一个RegEvent.java继承BaseEvent.java,
写一个RegService类,实现IService接口,
总之把相应包下面,加入自己的实现,
然后将监听还是注册到EventMgr里就可以实现了。
好了。欢迎交流,谢谢!