第七章 LPMT中的代码复用技术
软件复用是将已有的软件及其有效成分用于构造新的软件或系统。它不仅是对软件程序的复用,还包括对软件生产过程中其它劳动成果的复用,如项目计划书、可行性报告、需求分析、概要设计、详细设计、编码 ( 源程序 ) 、测试用例、文档与使用手册等等。面对越来越复杂的业务逻辑,软件复用技术一直以来都是业界关注的焦点,相关理论和技术层出不穷。作为一种比较出色的实现 MVC 设计模式的 FrameWork , Jakarta Struts 提供了一种比较理想的软件复用方法。在 LPMT 中,我们对其中的代码复用进行了有益的尝试。
7.1 Model复用
这里的 Model 包括所有的 Action 类、 ActionForm 类。
在 LPMT 中,我们设计了两个 Action : IssueAction 和 MainAction 。其中, IssueAction 负责 IssueAction 及 IssueData 、 ChangeLog 操作, MainAction 负责 User 及 Login 操作,据此实现 Action 复用。在每个 Action 中,通过参数 action 的值来判断操作的类型,继而实现相应的业务逻辑操作;
IssueAction :
// 查看所有的 Issue
if(("view".equals(action)) || (action == null)) {
}
// 查看单个 Issue 及其 IssueDatas 和 ChangeLogs
else if("viewDetail".equals(action)) {
}
// 新建一个 Issue
else if("create".equals(action)) {
}
// 编辑已有的 Issue
else if("edit".equals(action)) {
}
// 删除对应的 Issue
else if("delete".equals(action)) {
}
// 保存新建的 Issue
else if("save".equals(action)) {
}
// 新建 IssueData
else if("createIssueData".equals(action)) {
}
// 保存新建的 IssueData
else if("saveIssueData".equals(action)) {
}
// 编辑 IssueData
else if("editIssueData".equals(action)) {
}
// 删除 IssueData
else if("deleteIssueData".equals(action)) {
}
MainAction :
// 判断用户是否取消操作 , 是则就转向 main.jsp
if(this.isCancelled(request))
{
}
// 登陆
if(request.getParameter("action").equals("login"))
{
}
// 显示添加新用户界面
if(request.getParameter("action").equals("newUser"))
{
}
// 添加新用户
if(request.getParameter("action").equals("doNewUser"))
{
}
// 显示要修改的用户的资料
if(request.getParameter("action").equals("updateUser"))
{
}
// 修改用户资料
if(request.getParameter("action").equals("doUpdateUser"))
{
}
// 判断是否要删除用户
if(request.getParameter("action").equals("deleteUser"))
{
}
// 删除用户
if(request.getParameter("action").equals("doDeleteUser"))
{
}
// 查看用户列表
if(request.getParameter("action").equals("viewUser"))
{
}
通过参数 address 来保留下一步的映射目标;在 struts-config.xml 配置文件中,我们通过 global-forward 和 action-forward 映射,将不同的 address 映射到相应的目标 View 。
<global-forwards>
<forward name="loginSuccess" path="/main.jsp" />
<forward name="newUser" path="/newUser.jsp"/>
<forward name="updateUser" path="/newUser.jsp"/>
<forward name="deleteUser" path="/deleteUser.jsp"/>
<forward name="login" path="/index.jsp"/>
<forward name="success" path="/message.jsp"/>
<forward name="operationSuccess" path="/Success.jsp" />
<forward name="error" path="/error.jsp"/>
<forward name="viewUser" path="/viewUser.jsp"/>
</global-forwards>
<action name="issueActionForm" type="issuecontrol.action.IssueAction" validate="false" scope="request" path="/issueAction">
<forward name="viewIssue" path="/IssueList.jsp" />
<forward name="viewIssueDetail" path="/IssueDetail.jsp" />
<forward name="issueData" path="/IssueData.jsp" />
</action>
在必要的时候, IssueAction 和 MainAction 可以合为一个 Action ,由这个 Action 来负责所有业务逻辑操作。
在面向对象设计之后,对于每个需要显示的实体对象,我们定义了一个 ActionForm ,如 IssueActionForm 、 ComponentActionForm 、 PriorityActionForm 、 TypeActionForm 、 LogActionForm 。 ActionForm 实现了 Serializable 接口,每个 xxxActionForm 继承 ActionForm 类,适合用于数据封装、显示和远程网络传播。
通过在 struts-config.xml 中的映射,一个 Action 可以对应多个 ActionForm ,实现 Action 和 ActionForm 复用。
<form-beans>
<form-bean name="LoginForm" type="issuecontrol.actionform.LoginForm" />
<form-bean name="NewUserForm" type="issuecontrol.actionform.NewUserForm"/>
<form-bean name="BaseForm" type="issuecontrol.actionform.BaseForm"/>
<form-bean name="loginActionForm" type="issuecontrol.actionform.LoginActionForm" />
<form-bean name="componentActionForm" type="issuecontrol.actionform.ComponentActionForm" />
<form-bean name="environmentActionForm" type="issuecontrol.actionform.EnvironmentActionForm" />
<form-bean name="flagActionForm" type="issuecontrol.actionform.FlagActionForm" />
<form-bean name="issueActionForm" type="issuecontrol.actionform.IssueActionForm" />
<form-bean name="logActionForm" type="issuecontrol.actionform.LogActionForm" />
<form-bean name="priorityActionForm" type="issuecontrol.actionform.PriorityActionForm" />
<form-bean name="typeActionForm" type="issuecontrol.actionform.TypeActionForm" />
<form-bean name="issueDataActionForm" type="issuecontrol.actionform.IssueDataActionForm"/>
</form-beans>
<action-mappings>
<action name="LoginForm" type="issuecontrol.action.MainAction" validate="true" scope="session" input="/index.jsp" path="/login" />
<action path="/newUser" name="BaseForm" type="issuecontrol.action.MainAction" validate="false" scope="request" input="/main.jsp"/>
<action path="/doNewUser" name="NewUserForm" type="issuecontrol.action.MainAction" validate="true" scope="request" input="/newUser.jsp"/>
<action path="/viewUser" name="BaseForm" type="issuecontrol.action.MainAction" validate="false" scope="request" input="/main.jsp"/>
<action path="/updateUser" name="BaseForm" type="issuecontrol.action.MainAction" validate="false" scope="request" input="/viewUser.jsp"/>
<action path="/doUpdateUser" name="NewUserForm" type="issuecontrol.action.MainAction" validate="true" scope="request" input="/newUser.jsp"/>
<action path="/deleteUser" name="BaseForm" type="issuecontrol.action.MainAction" validate="false" scope="request" input="/viewUser.jsp"/>
<action path="/doDeleteUser" name="BaseForm" type="issuecontrol.action.MainAction" validate="false" scope="request" input="/deleteUser.jsp"/>
<action name="issueActionForm" type="issuecontrol.action.IssueAction" validate="false" scope="request" path="/issueAction">
</action>
<action name="issueDataActionForm" type="issuecontrol.action.IssueAction" validate="false" scope="request" path="/issueDataAction"/>
</action-mappings>
7.2 View复用
这里的 View 主要是指用 Jakarta Struts 标签库构建起来的 JSP 页面。正如本文“ LPMT 中的控制结构”和“ WebForm 的 Jakarta Struts 标签技术实现”部分所说的,通过 <logic:equal> 标签和 Action 、 ActionForm 中 action 、 actionType 参数的设置,可以很容易将创建、查看、修改、删除等功能的 View 显示集合在一个 JSP 页面完成。理论上,可以只创建一个 View ,但是由此所带来的创建、修改和维护成本将不合算。所以我们通常把相同或相似的业务逻辑结果显示放在一个 View 中完成,以此实现 View 的复用。
7.3 Controller复用
Struts 的 Controller 由 ActionServlet 、 RequestProcessor 、 ActionMapping 等类组成。这些类在 Struts 中唯一充当控制器角色, Model 中相应的 ActionForm 和 URL 中的参数则作为数据封装和传递的媒介。这些类通过相应的方法和接口复用,实现 Model 的复用。
7.4 其他面向对象的代码复用
在 LPMT 中,我们采用了面向对象的分析、设计和编码方法,因此,我们主要着眼于面向对象思想在下面几个方面实现代码复用:
7.4.1 封装性
在软构件的定义中,用户只关心事件的输入输出,对事件内部不必关心,方法和事件是独立于应用的,用户可以在软构件中定义自己的事件,对于内部的复杂性调用这并不知晓,从而提高了隐蔽性。在 Logic 、 DataPersistence 等相关 Bean 和 DAO 类中,我们遵循数据封装和持久原则,在 Action 中调用相关方法只要符合相应的输入和输出条件即可实现既定的业务逻辑操作 , 而不需要了解业务逻辑的处理过程。
7.4.2 重载
重载就是在同一软件构件中用同一名字来表示不同的方法名。一般有两种实现方法,一是方法参数的个数重载,二是方法参数的类型重载。在 IssueDAO 中,根据应用需求,我们定义了 delOneLog 方法,通过方法参数的类型( Log 类型、 Long 类型)不同实现重载。另外的 getIssueByPage 方法,则通过方法参数的个数不同实现重载。详细代码参考附录。
7.4.3 继承
继承就是高层的类在不同范围的复用。在 Web 系统设计中,经常需要验证客户端的输入是否符合要求,比如输入不能为空、 Email 段输入必须符合规范等等。在 Model 层的 ActionForm 类中,我们定义了一个高层类 BaseForm , BaseForm 扩展了 ActionForm ,定义了几个常用的数据验证方法,比如验证输入是否为空的 isBlankString 方法、验证前后输入是否一样的 isTheSame 等。其他的 ActionForm 通过继承 BaseForm 实现。另外一种方法,可以通过定义一个 ValidateForm 的静态类来负责常用的几种输入验证,其他 ActionForm 类在 Validate 方法中通过调用 ValidateForm 类的相应方法即可实现相应的输入验证逻辑。
结论
通过上述背景阐述、技术分析和代码实践,可以看到,使用 Struts 构建的实现了 MVC 设计模式的系统,在提高系统的构建效率、可扩展性、可维护性、可复用性方面均有突出的表现。而本文在 LPMT 中的 Struts 应用方式比如架构设计、 WebForm 构建技术等有效的发挥了 Struts 技术的优势,快速实现了系统的应用需求,有一定的参考价值。
致谢语
在本文写作和系统设计过程中, 林劲松 老师给了大量热心的帮助和指导,在此特别感谢。作者的同组同学范晓鑫和罗佳在系统设计和编码方面也让作者受益颇多,作者在厦门合强软件有限公司的同事在 MVC 思想方面给了作者很大的提示和帮助,在此一并感谢。同时要感谢自动化系所有的老师和同学在大学四年对作者的关心和帮助。
参考文献
(1) James Goodwill《Mastering Jakarta Struts》Indianapolis, Indiana,Canada Wiley Publishing ,Inc.2002年
(2) Hafech Mili,Ali Mili,Sherif Yacoub etal著,韩柯译《基于重用的软件工程》北京电子工业出版社 2003年
(3) 孙琪《软件复用技术概述》http://www.tongtech.com/jsqy/yqxwview.asp?id=209 2003 年6 月30 日
(4) 赵晨希 《用Struts建立MVC应用的介绍》 http://www-900.ibm.com/developerWorks/cn/java/l-struts-mvc/index.shtml 2002 年 12 月
(5) 刘天北 《理解企业应用框架》http://www.socent.com/crm_oa/message.asp?id=118 2003 年7 月1 日
21MM 《框架不是框框 — 应用框架的基本思想》 http://www.digitalspirit.info/phf/artichecture/applyFrame.htm