我的Swing Jide WizardDialog实践个论
--外部化,统一化, 职责化你的代码
楔子
感觉好久没写技术方面的博客了,沉寂了这么久,今天正好借机疏解一下自己的“郁闷”之情。自从8月中旬加入花旗软件(大连),转眼已经快三个月了,原先的美好愿望和希冀,现在却由淡淡的“忧伤”和“郁闷”而替代,原因或许很多,也或许根本就没有啥,纯粹庸人自扰:
- 书稿写完了,提交给出版社之后的空虚?
- 因为前女友家里人全员反对而分开后的郁郁寡欢?
- 抑或是现在的工作与当初的设想大相径庭?!
道不清说不明的,反正这阵子一直是过得不快乐就是了。
其实,花旗大连这边的工作氛围是比较轻松的,应该说正是自己当时所希望的,但是,现实的情况却是,闲大法了也不好,当你已经习惯了飚车的速度感,猛然让你陷入闹市的堵车当中,应该是比较烦躁的吧?!
还记得自己当初刚进花旗软件(大连)时候的激情四射,狂啃项目组中业务相关的金融资料的情景,可是随着时间的流逝,才发现,短期内自己是多么的无助,呵呵,银行系统内部系统的庞大,又怎么可能一朝之间让你了然于心那?!现在的感觉真的只能用那句“心有余而力不足”来形容了。
当前项目组的技术架构虽然很简单,但是,前面写了两年多的codebase却着实让我无能为力,不能说代码很烂,但是,各种细节之间的纠缠却非我这刚进来的newbie所能掌控的,所以,每每有新的issue交给我处理,都是小心翼翼的,能不动之前的codebase就不动,
要知道,"失节"事小,"驾崩"事大,这可是金融系统,稍一不慎把系统搞挂掉,那可不是闹着玩的(虽然发布之前回旋余地大一些)。
不过那,不去动之前的codebase并不是说这些codebase有多好,有多容易维护,实际上,整个项目有些“挂羊头,卖狗肉”的味道,说是敏捷开发,但没有看到多少敏捷的实践;说是使用Spring框架,却让它形同虚设;说是用Hibernate做数据访问,却仅仅用它来做为调用存储过程的中间层,只用了调用存储过程后能够帮助你将结果映射到结果对象这一点点功能,个人感觉就是Hibernate在这里真的是“生不逢时”…
当然啦,我今天可不是要为了挑刺儿,毕竟任何项目都会存在这样那样的问题嘛,我今天要说的,只不过是从一个小小的功能点引申出来的代码实践而已,下面让我们进入正题…
Jide简介
或许做Swing的各位同仁对jidesoft(http://www.jidesoft.com/)所提供的产品早就有所耳闻,也或许没有,总之俺是进花旗这个项目组之后才知道有这么一个产品(其实,从身边许多人那里都可以了解到,许多金融相关产品的客户端使用swing做的),你可以鄙视我,谁让俺之前只做过两年左右的swt/jface而没有用swing那,呵呵。
应该说,jide提供的产品还是比较强大的,不过,商业产品,应该不是谁都愿意花这份儿银子吧!jide在原有swing基础上给出了多种扩展,包括dockable framework, action framework,dialogs,data grid/pivot table等等,我们今天要扯的话题与jide提供的WizardDialog有关,所以,就在这里对jide简单提及一下,希望没有说太多废话。
现有codebase中的WizardDialog代码实践
话说我们要为用户提供接口,分多步从用户那里获取输入数据,这种情况下,为用户提供一个向导式的输入界面应该说是比较自然的事情,现在,就有这样的一个场景,所以,我们决定使用jide提供的WizardDialog来实现之。
《JIDE Dialogs Developer Guide》虽然对WizardDialog相关的各个类以及功能做了介绍,但是,如何在开发过程中使用它们,就得由我们来决定了,这自然就会引出不同的实践方式,下面是我从现有的codebase中移植过来的代码片段原型,我称之为“内部化”的代码实践(代码风格如何暂且不论)。
首先,XAction类是一个标准的Swing Action,其定义如下:
XXXAction extends AbstractAction
{
…
Public void actionPerformed(ActionEvent e)
{
// collect data from event object or outer references
ByBkWizard.showByBkDialog(trades);
}
}
当用户触发该Action对应的事件之后,定义的逻辑即被执行,简单点来说就是,搜集前提条件相关的数据,然后显示WizardDialog给用户,当用户输入完成之后,根据收集的输入结果进行后继处理,而这些工作,现在显然都是由这个ByBkWizard类来做了:
public class ByBkWizard
{
public static void showByBkDialog(Collection<TransactionEntity> trades_) {
new ByBkWizard(trades_);
}
public ByBkWizard(Collection<TransactionEntity> trades_) {
_backupTrades = new HashMap<String, TransactionEntity>(trades_.size());
_trades = new ArrayList<TransactionEntity>(trades_.size());
for (TransactionEntity entity : trades_) {
try {
_backupTrades.put(getTempKey(entity), (TransactionEntity) entity.clone());
_trades.add((TransactionEntity) entity.clone());
} catch (Exception e) {
logger.error(e, e);
}
initWizard();
}
// initWizard定义
PageList model = new PageList();
AbstractWizardPage setDataView = new BuyBackStepOneWizardPage();
AbstractWizardPage editView = new BuyBackStepTwoWizardPage();
model.append(setDataView);
model.append(editView);
wizard.setPageList(model);
wizard.addWindowListener(new WindowAdapter() {
public void windowClosing(final WindowEvent e) {
wizard.dispose();
_tradePanel.destroy();
_tradePanel = null;
}
});
wizard.setFinishAction(new AbstractAction("Send") {
private static final long serialVersionUID = 1L;
public void actionPerformed(final ActionEvent e) {
if (wizard.closeCurrentPage()) {
if (! checkInput()) {
GenericExceptionHandler.showOptionDialog("Warn",
"The input is illegal for the callable trade(s)",
JOptionPane.WARNING_MESSAGE);
return;
}
handleFinish();
wizard.setVisible(false);
wizard.dispose();
_tradePanel.destroy();
_tradePanel = null;
}
}
});
wizard.setCancelAction(new AbstractAction("Cancel") {
private static final long serialVersionUID = 1L;
public void actionPerformed(final ActionEvent e) {
if (wizard.closeCurrentPage()) {
wizard.dispose();
_tradePanel.destroy();
_tradePanel = null;
}
}
});
// 内部类声明
Class BuyBackStepOneWizardPage
State, operations;
Class BuyBackStepTwoWizardPage
State, operations;
}
OK, 从功能上来说,ByBkWizard完成了预期的业务处理需求,但是,这样的ByBkWizard定义是否合适,却有值得商榷的地方(看官,如果你觉得“只要功能完成了就得了呗!”,那么就到此为止吧,下面的话其实再看就没啥意思了):
- 提供静态方法showByBkDialog的必要性是什么?!图方便?赶时髦?
- 将各种逻辑都塞到一个类(ByBkWizard)里面不会觉得让这个类身材太“丰满”吗?怎么说也不符合当今的审美观吧?!界面显示的逻辑,模型数据处理逻辑要是给予独立的关注,是不是能让整个逻辑实现更加清晰一些那?!
- 按理说这里的WizardDialog使用的各个Page确实也就这一个地方使用,将Page类定义为内部类也不算为过,而且,还能从ByBkWizard里直接访问这些Page的内部状态,多爽啊,不过,在我看来,更多的为是为了handleFinish()这个方法服务吧?!
- 而说到这个handleFinish()方法,也就引出了我最初所说的“内部化”实践的问题,对最终数据进行处理的逻辑“内部化”到了当前类当中,而不是剥离并封装为单独的实体,有错吗?!其实也没错,就是感觉不符合我的审美观,毕竟,DI和IoC提了这么些年,回过头来再看到这样的代码有些难以忍受罢了。
其实,往简单了说,所有的问题都归结为一点,即,代码职责不明确,或者专业点儿来说,关注点分离不够。而造成这种问题的前兆往往却是,不做足够的思考就根据功能去堆砌代码了。
重构后的WizardDialog代码实践
现在让我们来重构这一功能实现,重构的主要目标包括:
- 重用WizardDialog的初始化逻辑;
- 封装最终的数据处理逻辑;
- 处理页面间的数据状态管理;
通常情况下,WizardDialog的使用流程是这样的:
AbstractWizardPage page1 = …;
AbstractWizardPage page2= …;
AbstractWizardPage page3 = …;
…
PageList model = new PageList();
model .append(page1);
model .append(page2);
model .append(page3);
…
WizardSample wizard = new WizardSample("JIDE Wizard Demo");
wizard.setPageList(model);
wizard.setResizable(false);
wizard.pack();
wizard.setVisible(true);
显然,除了使用的Pages不同,初始化一个WizardDialog并使用它的流程基本模式是不变的,那么我们就可以这一流程采用模板化封装,以期之后能够复用之,这也就有了如下模板类定义:
public class WizardDialogLauncher {
public void launch(String title,final PageList pageList,final Runnable finishAction)
{
launch(title,new IWizardDialogCustomizer(){
public void customizeWizardDialog(WizardDialog dialog) {
dialog.setPageList(pageList);
dialog.setFinishAction(new AbstractAction("")
{
...
// finishAction.run();
}
...// other settings
}});
}
public void launch(String title,IWizardDialogCustomizer customizer)
{
WizardDialog dialog = new WizardDialog(title);
customizer.customizeWizardDialog(dialog);
dialog.setVisible(true);
}
}
其中,IWizardDialogCustomizer为Callback接口,如果默认的模板方法不能满足需求的话,可以通过该Callback接口进一步定制WizardDialog。
现在第一个重构目标达成,而重构的过程中我们也引出了第二个重构目标,即封装最终的数据处理逻辑。当用户走完WizardDialog给出的数据输入请求流程之后,我们就应该对最终收集到的用户输入结果进行处理了,显然,不同的WizardDialog因为会根据需求而使用不同的Page,那么,对应的最终数据处理逻辑也是各异的,所以,我们不能将这些数据处理逻辑硬编码到我们的模板类当中,所以,我们暂且以Runnable的形式抽取出来,这样,每次启动新的WizardDialog的时候,只要根据需要,传入相应的Runnable形式封装的最终数据处理逻辑即可。(当然,你也可以以其它形式来封装最终的数据处理逻辑,只要能够保证这一逻辑可以被重用,并能够灵活处理)
最后一个重构的目标是要处理页面间的数据状态管理,为什么要这么做那?!每一个WizardDialog都由多个WizardPage所组成,WizardDialog将这些Page来显示每一步的具体界面给用户,我们所要做的,只不过是通过PageList将这些Page交给WizardDialog进行显示罢了,至于这些Page管理则全都交由WizardDialog来做。但是,WizardDialog主要只负责这些页面之间的显示顺序以及最后向导结束后的行为定义,至于这些Page之间的数据状态管理,比如前后页面之间的数据依赖关系和传递,无论是《JIDE Dialogs Developer Guide》和相关类的Javadoc上都是只字未提,我想这就是为什么之前提到的实践方式中要将所有的数据状态以及页面逻辑都纳入一个类来管理的原因了,因为没有什么定规嘛,不过,这并不能为“内部化”的实践方式博得多少“同情”,实际上,我们完全可以将各个Page之间的模型数据以数据链的形式进行提供,比如,声明一个PagesModel,或者如果关心进一步的结构重用并不在乎数据类型检查的话,直接使用Map结构,让整个向导期间的数据交互都直接跟PagesModel或者Map形式的数据容器打交道,而之前的实践方式则基本上是通过同一个类定义范围内的状态共享来实现的。那么,我们是否要像之前的实践方式那样,做适当的变通,将状态定义为每一个Page的状态属性那?!稍微思考一下就会发现,这并不会带来什么好处,假设第二个Page要依赖第一个Page的数据状态,那么我们是不是要在初始化第二个Page的时候将第一个Page的引用传入那?如果第三个,第四个Page也有同样地状况那?!那就会纠缠不清了,还不如一了百了,让他们都直接跟某个统一的数据容器打交道来的清爽,我们所要所得,只不过是将同一个数据容器传给它们就是了。显然,作为数据容器的PagesModel或者Map可以在每一个Page中根据需要进行数据的获取和填充,比如页面初始化的时候获取数据容器中的数据初始化界面,而页面关闭的时候将页面上的某部分数据设置回数据容器,所有这些都可以通过为每个page注册相应的PageListener来完成,而且对每个Page都是独立的。
当所有这些重构目标达成之后,我们的XAction定义基本上就是如下的样子:
public class XAction extends AbstractAction {
private WizardDialogLauncher launcher;
public void actionPerformed(ActionEvent arg0) {
PagesModel model = new PagesModel();
model.setX(..);
...
PageList pageList = new PageList();
pageList.append(new YourPageOne(model));
pageList.append(new YourPageTwo(model));
...
Runnable finishAction = new YourFinishAction(model);
launcher.launch(pageList, finishAction);
}
// getters and setters
}
显然,如果有新的WizardDialog需求,我们所要做的,无非就是像如上代码所示,提供相应的Page实现和对应的FinishAction实现即可。
重构的过程其实很简单,最主要在于,即使你不能使用Spring或者Guice之类的IoC容器或者框架,那也不应该放弃“外部化你的依赖管理”,没有框架的支持,不应该成为你不去编写可复用性,可测试性,可维护性良好的代码的借口。
小结
- 以旁观者的角度去观察,设计和实现你的代码逻辑,这将使你更加专注于明确各个实现类的职责,而不会被来自不同“地区”的依赖打乱“战局”。
- 大家都知道,做同样一件事情有很多种方式,但是,总有更好的方式,如果你是一个追求完美的人,那即使没有任何的理论和设计模式之类的指导,你也同样可以写出令人赏心悦目地代码,因为,那是艺术,而你就是拥有艺术气质的那种人!
(感觉说的有些乱,各位看官讲究着看吧,只不过实在无聊,书写一些文字而已。拍砖都可以,别骂人就行)
分享到:
相关推荐
对于任何需要构建复杂Swing应用的开发者来说,JIDE都是一个值得考虑的优秀工具。通过深入理解和使用jidesoft-jide-oss-fbec295这个压缩包中的源码和资源,开发者可以更好地掌握JIDE的用法,创造出更加出色的应用程序...
Java Swing组件库JIDE是一个强大的开发工具,专为Java开发者设计,用于构建功能丰富的桌面应用程序。JIDE提供了许多预构建的、高度可定制的Swing组件,极大地扩展了标准Java Swing的功能。这个开源项目旨在简化GUI...
JIDE Common Layer是一套很不错的Swing组件类库,它JIDE软件公司其他产品的基础,于2007年4月开源,在其开源之前是JIDE商业软件中的一部分。它包含将近10万行代码,超过30个的Swing组件和工具。 压缩包中含有对应的...
JIDE是一个强大的开源库,专为Java Swing设计,旨在扩展和增强Swing组件的功能。它提供了一系列预构建的、高度定制化的UI组件,可以帮助开发者创建专业级别的应用程序。JIDE库不仅包含常见的UI元素,如表格、网格、...
开源的JIDE组件库则是对Swing的一个增强,它为开发者提供了更多定制化和专业化的GUI组件,以帮助构建更美观、功能更齐全的应用程序。JIDE组件库不仅包含Swing的标准组件,还添加了许多高级组件,如网格布局管理器、...
Demo for JIDE 美丽的令人震撼的swing界面!
JIDE(Java Integrated Development Environment)是一个强大的GUI库,它基于Java Swing框架进行扩展和增强,提供了丰富的组件和工具,旨在帮助开发者构建功能丰富的、具有专业外观的Java桌面应用程序。JIDE API是这...
JIDE不仅仅是一个简单的开发工具,更是一个强大的组件库,它包含了众多用于构建企业级应用的组件,如表格、网格、表单、图表等。这些组件的设计旨在提供高度定制化,满足开发者在创建用户界面时的各种需求。通过使用...
JIDE(Java IDE Components)是针对Swing组件的一个扩展,它为开发者提供了更多高级和定制化的组件,以增强Swing的基本功能。开源的JIDE组件库不仅丰富了Swing的功能,还促进了开发者的创新和协作,因为它允许社区...
JIDE是一个开源项目,它扩展了Swing组件,提供了更多功能和定制选项,帮助开发者创建更加美观和功能丰富的Java应用。 JIDE组件库包含了许多增强的Swing组件,如表格、网格、菜单、工具栏、对话框等,这些组件在设计...
JIDE2.9.0的破解文件 将下载的LM.class替换掉jide-common.jar里的原com.jidesoft.utils.Lm.class 适用于2.9.0版本
JIDE是一个开源项目,它扩展了Swing组件,提供了更多功能和定制选项,帮助开发者创建更加丰富和复杂的桌面应用。 JIDE组件库为Java开发者提供了一系列高级的UI控件和工具,包括但不限于表格、网格、表单、菜单、...
JAVA源码开源的Swing组件JIDE
java资源开源的Swing组件 JIDE提取方式是百度网盘分享地址
Jide开源库 让你的Swing界面更有看头
Jide提供的开源Swing库。 JIDE Common Layer is Swing component library built on top of Java/Swing. It is also the foundation of other component products from JIDE. This project has over 30 Swing ...
jide开源Swing组件中,jide-charts的jar包
JIDE RSS Feed Reader作为一款Swing组件,其依赖关系相对简单,主要依赖于Java Swing库及其相关的API。此外,为了实现RSS解析功能,它还依赖于解析RSS和Atom格式的第三方库。 总之,JIDE RSS Feed Reader是一款功能...
Jide是一款强大的Java库,专为提升Swing应用的外观和功能而设计。它扩展了Java Swing框架,提供了丰富的组件和工具,使开发者能够构建出更现代、更美观且功能丰富的用户界面。Jide源码的开放使得开发者有机会深入...