Swing
程序最佳架构设计
—
以业务对象为中心的
MVC
模式
前言:
我打算写一系列关于Swing程序开发的文章。这是由于最近我在做一个Swing产品的开发。长期做JavaEE程序,让我有些麻木了。Swing是设计模式的典范,是一件优雅的艺术品,是一件超越时代的产品!
有机会作Swing软件的开发,让我非常有感觉!
呵呵,希望有机会能够用Java3D编写软件,那种感觉一定更棒!
Java和Swing都是杰作。我这个人对别人一向很挑剔的,能够得到我由衷地赞誉,可想而知它们有多优秀了。奇怪的是,它们居然一直都无法占领桌面市场。有人说这是技术的原因。我认为这应该是商业、历史的原因才对。
好了,我也应该为java和Swing在桌面的成功尽我绵薄之力,共享我的思想吧!以感谢这么多年来,Java带给我的美好回忆。
台灯、电脑、Java,还有Coffee,伴我度过多少不眠之夜!I love U!
Swing
程序最佳架构设计之一
业务模型中心架构模式
以业务模型为中心
以业务模型为中心的架构模式。这是Swing和其他所有程序应该采用的架构模式。之所以强调Swing,是因为在Swing程序中使用这种“以业务模型为中心”的架构模式,更有意义,作用更大!
我们知道,一个软件由几个部分组成:
1,表现层(用户界面层)
这时软件和用户交互的部分,可以分为字符界面(DOS等命令行),GUI图形界面(桌面GUI,浏览器GUI,三维界面)等。
这仅仅是软件的外表,外貌再华丽,也不能说明软件的优劣!Google的界面平淡无奇,但它确是今天最强大的软件!
2,业务层
这是实现软件功能的地方。存在着大量映射着业务领域的对象,存在着复杂的业务计算。这才是软件真正工作的地方。
软件的核心和难点就在这里!巨大的开发工作量和思考,也都集中在这里。
业务模型,及其互相之间的交互,这才是软件的核心!
3,数据库访问层(可能有)
有些软件需要把操作保存进数据库,永久保留下来。通常,企业级等需要业务数据的软件需要这个模块。
它实现业务对象和关系型数据库的映射。称为“O-R”映射。
4,持久化层(可能有)
有些软件不把数据保存进数据库,而是保存为文件。如,二进制文件,文本文件,XML文件等。
它实现业务对象和文件之间的映射。可能是序列化业务对象。如果是保存为xml文件,我们称它为O-XML映射,是业务对象和XML文件之间的映射。
现在有一些开源软件实现了Java对象和XML文件之间的映射,就如同是O-R映射一样。其实,基于Java的XML访问组件,自己开发O-XML映射也是非常简单的,我自己也写了一个这样的库。
可以看到,“以业务模型为中心”,并不是一种特例,而是最基本的软件开发规律!是所有软件共有的基本特性。只是,今天,在各自不同的领域,这个基本常识被一些常用的领域中的架构模式所模糊了。
如,在Java的企业级软件开发中,大家都知道J2EE软件分为3层:
1,表现层
2,业务层-----业务对象和Service助手类
3,数据库访问层-----DAO助手类
这样的架构模式,模糊了“业务对象”是整个软件的核心这样一个事实。
Service助手类和DAO助手类,实际上都是业务对象的一部分,完全可以被放在业务类中。
助手类和业务对象分离,不过是一种设计模式而已。
Swing程序中的应用
好了,本文并不想讨论这些“形而上学”的问题。上面的讨论,不过是想让你明白,本文所论述的“以业务模型为中心”的架构模式,并不是Swing应用程序所特有的,而是所有软件的特性,只是,在Swing这个条件优越的桌面开发环境中,更加有用罢了!
Swing程序作为桌面程序,拥有比JavaEE程序更好的环境,更强的功能。
Swing程序常常是单机的,即使是网络化的,也可以在业务层中实现分布式,而不像JavaEE程序,用户界面和响应用户操作的代码都是分布在不同的机器上的!
因此,Swing程序更能发挥我们软件开发者的智慧和能力!
Swing
程序最佳架构设计之二
MVC
模式
Swing程序的组成
一般,Swing程序由以下几部分组成:
1,表现层----Swing组件组成的GUI
2,业务层------业务对象,及其相互之间的关系
3,持久化层-----把业务对象保存为文件,或者是数据库记录。
MVC模式在Swing程序中的应用
我们知道,Swing是MVC模式的典范。Swing的各个可视化组件都使用MVC模式来设计。
Swing组件一般由三个部分组成:
1,Swing的Model,这是MVC中的M—模型部分。它保存了Swing组件所需要的数据。Swing组件的UI需要根据它来展现。
2,Swing的UI类。这是MVC模式的View—视图部分。它根据组件的Model中的数据,执行绘制、展现Swing组件的任务。
3,Swing组件类。这是“门面”,它封装了Swing的UI对象和Model对象。我们一般都是通过它来操纵Swing组件,不会直接使用Swing组件内部的UI对象和Model对象。
Swing组件之间,还有着复杂的关系。Swing的UI类,监听Model对象的数据改变,即时进行重绘界面的工作。
在Swing组件上还可以注册一系列的事件监听器。它们就是MVC模式中的Controller控制器。
Swing应用程序中使用MVC模式
一个Swing应用程序的GUI由很多个Swing组件组成。各个Swing组件本身是由MVC模式设计的,而我们的整个Swing应用程序的表现层也应该由MVC模式设计。
使用MVC模式,能够更好地分离代码和数据。使整个应用的表现层部分,更加低耦合、高内聚,灵活度更高。
在Swing应用程序的编写过程中,我们需要使用Swing组件的2个部分:
1,Swing组件的Model。Swing组件的MVC模式设计,使整个Swing组件是以Model为核心的。通过更改Model,我们就能够即时改变Swing组件的UI外观。
2,Swing组件上注册事件监听器。这是Swing组件的控制器,也可以作为整个Swing程序的控制器之一。
在Swing组件的监听器中,我们可以响应用户的操作,作为整个程序功能的入口。在这里,我们可以调用业务层的代码进行业务计算。计算完毕之后,可以修改Swing程序的外观。常常是修改某个Swing组件的Model。
Swing组件的监听器,一般是使用Swing程序的内部类来实现的。因此,我们可以在控制器中完全使用整个Swing程序的所有资源!因此,Swing组件的控制器是足够足够强大的!
此Model非彼Model
读者应该对JavaEE比较熟悉吧。毕竟,今天Java的主战场是在企业级应用上,会java,而不懂javaEE,这就有点说不过去了。
这里,我以大家比较熟悉的JavaEE的表现层技术来说明Swing程序的MVC设计。
Struts中,其MVC由以下几部分组成:
1,View视图,就是使用Struts标签的JSP页面。
2,Controller控制器,就是响应用户操作的Action。
3,Model模型,就是用户html页面表单中提交的数据。这在Struts中被称为“form”表单。
HTML表单的数据,都是String型的,而我们的业务对象的属性,却未必都是String型的。因此,在Struts应用程序中,我们需要根据接收到的form对象,构造业务对象。
在SpringMVC中,提供了一个机制,可以帮助我们自动把表单数据转为业务对象。对于一些特殊的类型转换,还是需要我们手工提供转换。
Form和业务对象之间的自动转换,固然是提高了开发效率。但是,我们必须要明白,GUI组件中的数据Form和业务对象是截然不同的两种东西。2者碰巧一样,或者可以自动转换,只是特例。它们本质上还是两样截然不同的东西。
Form是表现层的数据容器,而业务对象则是来自于特定业务的对象。Form的数据类型,变化不多,而业务对象的数据类型,那就是千变万化了。
因此,Form对象类型和业务对象类型一致,这是少之又少的特例。
Swing程序中的表单数据(Form)和业务对象(Model)
Web应用程序中的表单数据form,是来自于Html的表单的各个项的数据,都是String类型。
Swing程序中的表单数据Form。是由Swing程序的各个Swing组件的Model组成的。其各个部分的数据类型非常不同。
如,最简单的文本框JTextField的Model,是
javax.swing.text.Document类型的对象。这个对象,我们也可以直接得到或设置String的值来作为Model。实际上这个String类型的数据,是Document的属性。
还有,如JTree组件的Model是TreeModel对象。
因此,Swing程序中的表单数据Form的类型是非常多的。显然,我们的业务对象不大可能和Swing的Form数据的类型一致。
因此,Swing程序的表现层的数据Form和业务对象Model之间的转换,必然要由我们自己去实现。不大可能直接把Form作为业务对象扔到业务层中使用。也不可能直接在Swing的GUI中显示业务对象的数据!
Swing程序的MVC模式的使用
整个Swing程序应该这样使用MVC模式:
1,JFrame或者其他顶层容器中,由各个Swing组件构成了View视图层。用来展现数据,提供用户操作的图形界面。
2,一个或者一组业务对象是Model。它们存放了Swing组件要显示的数据。它们是业务对象,因此,可以直接在业务层代码中使用,执行复杂的业务计算。
它们不能直接在Swing组件中显示。而是需要根据业务对象,构造Form对象,也就是Swing组件的Model来展示数据。
为了让View—Swing组件实时展现业务对象的数据,我们需要让Swing组件监听业务对象,一旦业务对象发生改变,就重新根据新的数据,构建新的Swing组件的Model,从而在Swing组件上展现最新的业务对象数据。
这里,我们使用
ChangeListener
改变监听器。
为了让业务对象能够得到用户最新输入的数据,我们还需要将业务对象注册到Swing组件上。一旦Swing组件的数据发生了改变,就通知业务对象。业务对象根据Swing组件的Model,也就是Form对象的数据,修改业务对象的值。
为了实现这样的功能,我也使用了事件机制,实现了这一功能。这将在下文中论述。
现在,业务对象和Swing组件之间,通过双向的事件监听机制,实现了双向的引用!
3,在Swing各个组件上注册响应事件的监听器(控制器),以响应用户的操作。这些控制器,使用匿名内部类实现。
每一个Swing组件上的控制器,都不仅仅是该Swing组件的控制器,而是整个Swing程序的控制器。因为,内部类可以操纵整个Swing程序的所有资源,因此,我们可以在控制器中,使用所有Swing组件的form,也可以使用所有业务对象,调用所有业务方法,实现任何需要的功能!
控制器,是业务功能的入口点,它连接了Swing程序的表现层和业务层,连接了Swing的Form(Swing组件的Model)和业务对象。
现在,我们的Swing程序结构清晰,功能区分合理,低耦合、高内聚,堪称是MVC模式的典范!
Swing组件和业务对象之间的互动关系
在上文中,我们提出了Swing组件和业务对象之间相互的事件监听机制。Swing组件监听业务对象,使用“观察者模式”的一种实现机制,ChangeListener改变监听器。当然,也可以使用Observe/Observable这样的机制,或者其它的事件机制。但是,经过权衡,我觉得还是ChangeListener最符合我的需要。它功能强大,又很简单。
当我们需要某个Swing组件监听我们的业务对象时,我们创建一个该Swing组件的子类,它实现ChangeListener接口,实现
public void stateChanged(ChangeEvent e){…}方法,根据监听的业务对象的数据,构建自己新的Model,从而改变本Swing组件的显示。
业务对象又该怎样监听Swing组件呢?
我钟爱ChangeListener,还是在Swing组件上注册一个ChangeListener监听器,这个监听器就是我们的业务对象。
但是,考虑到ChangeListener使用场合甚广,为了避免Swing组件的其他操作激发该事件,我参照ChangeListener接口,新建了一个类型的新接口:ControllerChangeListener接口。
现在,Swing组件上的数据就能够和我们的业务对象的数据保持同步了!Form和业务对象虽然类型不同,却好像是同一个对象,有着同样的数据!
采用MVC模式后的Swing程序的执行流程
1,用户可以看到一个Swing组件构成的GUI界面。
2,用户在界面上进行操作。
3,Swing组件的控制器被触发。
1)可能会激发ControllerChangeListener的事件,引起业务对象自动使用Swing组件的Model数据进行更新。
2)业务对象更新数据,又会激发业务对象上的ChangeListener的事件。这会引起所有监听业务对象其Swing组件更新其Model的数据,从而改变Swing组件的显示。
3)我们可能会针对业务对象,执行业务层代码,进行复杂的业务计算。得到新的业务对象的值。
这同样会激发业务对象的ChangeListener事件,让Swing组件的Model得到更新,从而改变Swing组件显示的界面。
4)最后,当然,我们也可以直接修改某些Swing组件的Model或者外观。因为,作为内部类的控制器可以使用Swing程序所有的属性和方法。
在我的
《Swing多线程编程》一文中,我论述过Swing程序中,应该将SwingUI处理代码和一般的业务代码分配在不同的线程中。
所以,在这里的控制器中,我们应该使用SwingWorker这个助手类,把业务代码放在
public
Object construct() {…}
方法中执行,
而把直接修改
Swing
组件
UI
的方法,放到
public void
finished() {…}
方法中执行。这样,
Swing
程序的交互性、响应能力和性能将大大提高!
可以看到,这里,通过将业务对象和Form对象(Swing组件的Model)关联起来,我们在Swing应用程序中只需要“以业务对象为中心”。操纵业务对象,执行业务操作即可。
Swing程序的用户界面,就会自动更新。
这就是采用“以业务对象为中心的MVC模式”的巨大优势!