摘自 IBM WebSphere 开发者技术期刊。
引言
当大多数人想到模型驱动的开发时,首先出现在脑海中的是使用某种 UML 模型进行编码以及从该模型生成相应的构件。然而,事情并不总是这样的。模型存在于各种各样的地方。模型是任何驱动产生过程或行为的构件。
模型驱动的开发具有许多目标:
- 减少在开发常见的构件上耗费的时间。
- 维护最小限度的信息量。
- 以一种中立的方式对模型进行维护,这使得从相同的模型生成多种类型的实现和构件成为可能。例如,我们应该可以使用不同的模板从相同的模型中生成 Web UI 和本地的 GUI UI。
本文将通过设计模式工具箱来介绍模型驱动的开发,该工具箱是一种支持 Eclipse 的模板引擎,用来根据可自定义的、模型驱动体系结构转换(JET2 的一种推动技术,请参见参考资料部 分)生成相应的应用程序。我们将介绍设计模式工具箱 (DPTK) 中的代码生成机制的基本知识,与此同时,一并阐述引导您实现模型驱动开发 (MDD)的代码生成方面的一些良好设计。这些设计原则使得 DPTK 不仅仅只是玩具代码生成器,而是一种功能齐全的模型驱动的开发工具,可以生成非常复杂的系统。本文将重点关注于构建模式模板的基本原理。
有关更多的内容或下载设计模式工具箱,请访问 IBM alphaWorks。
模式模板
DPTK 中的模式创造者使用标记来构建模式模板。这些标记具有特殊的行为,用来访问模型、执行逻辑并完成各种各样其他的任务。丰富的标记提供了大量的功能,并且实 际上代表了构建任何复杂的 MDD 转换所需的基本构件。模式模板的主要目标是生成相应的构件,然而正如我们将在下一部分中看到的,模式模板可以用来完成更多的任务。
为了能够更好地理解相关内容,让我们先来研究下面简单的模式模板:
图 1. 简单的模式模板
这是一个非常简单的 DPTK 视图:
- 该模式应用于简单的 XML 模型。
- DPTK 引擎调用了一个模式模板。模式模板使用 DPTK 标记语言替换其中动态的部分。
- 并生成最终的构件。
从简单的 XML 声明生成相应的 Java™ 类并不是什么让人激动的事情,但是其目的是为对整个流程进行研究奠定基础。接下来,我们将对其进行更仔细的研究。
用于代码生成的模型-视图-控制器模式
模型-视图-控制器 (MVC) 是一种常用的设计模式,用于构建许多不同类型的应用程序。例如,在 Java EE 中,诸如 JavaServer™ Faces 和 Struts 这样的框架都专门提供了 MVC 设计。MVC 允许应用程序中事务的分离,其中模型代表数据和业务逻辑,视图代表如何观察数据,而控制器则处理用户的输入、模型调用和视图选择。
事实上,MVC 是使用设计模式工具箱构建 MDD 解决方案的一种优秀的设计模式。考虑一下图 2:
图 2. MVC 作为一种设计模式
DPTK 标记库包含各种各样的标记,它们可以用来完成许多任务。因此,我们可以使用特定的角色构建经过精心设计的模板。例如,控制器模式模板将生成许多构件的整体容器,并调用各种其他的模板,而这些模板将生成特定的构件。
通过下面一系列练习,您将可以通过示例了解并掌握如何使用 MVC 设计模式生成相应的构件。您可以在结尾处访问DPTK 并下载本文中的示例资料。
构建模式模板
在进行模型驱动的开发的过程中,最好时刻将目标铭记于心。也就是说,手动地构建出第一个案例。这样,您就可以弄清楚究竟需要生成什么样的内容。在开 始这项工作前,我们将向您介绍一个示例样本(在后续的文章中,我们将讨论一些工具,这些工具可以用来对样本进行分析,以便生成模式本身)。在这个示例中, 我们提供了在 IBM Rational® Software Architect 中使用设计模式工具箱的相关说明。然而,也可以使用安装了 DPTK 插件的普通的 Eclipse 轻松地完成这个练习(请参见参考资料部分)。
-
启动 Rational Software Architect,并打开一个新的、空的工作区:
-
通过选择 Window => Open Perspective => Other(图 3),切换到设计模式工具箱透视图。
图 3. Open perspective
-
从对话框(图 4)中选择 Design Pattern Toolkit。
图 4. Select perspective
-
导入我们的示例 Java 样本,这是我们将要生成的 JavaBean 的示例:
-
右键单击 Navigator 视图并选择 Import(图 5)。
图 5. 导入示例
-
从导入向导中选择 Project Interchange。(如果您正使用普通的 Eclipse,可以对该项目进行解压,并将其作为一个已有的项目导入到您的工作区。)
图 6. 导入示例
-
导航至下载资料并选择 MyExemplar.zip(图 7)。
图 7. 选择项目
-
选择 MyExemplar 项目,并按 Finish。
-
检查其中的构件:
-
展开该项目(图 8)并打开文件 sample.appdef。
图 8. 打开示例
这个文件将包含大量的 XML 标记。对于那些熟悉 IBatis(请参见参考资料部分)的读者,您将注意到,这个 XML 模型(清单 1)反映了 iBatis 映射文件的 parameterMap 结构。它定义了 Java 类,以及每个属性的相关属性和数据类型,并且将作为生成过程的模型。
(请记住,这里的目标是熟悉如何编写模式模板。这里并没有给出维护模型的最佳实践。在实际情况下,输入和内部模型并不匹配。事实上,稍后我们将对内部模型进行修改。)
清单 1
<app> <parameterMap class="com.ibm.dptk.ibatis.dataobject.Customer" id="newCustomerMap" > <parameter property="customerId" javaType="java.lang.Integer"/> <parameter property="name" javaType="java.lang.String"/> <parameter property="address1" javaType="java.lang.String" /> <parameter property="address2" javaType="java.lang.String" /> <parameter property="city" javaType="java.lang.String" /> <parameter property="state" javaType="java.lang.String" /> <parameter property="zip" javaType="java.lang.String" /> </parameterMap> </app>
|
-
如果打开 Java 类,您将发现一个简单的 JavaBean,它具有一些简单的属性以及相应的 setter 和 getter。在这个练习中,为了方便起见,我们根据 JavaBean 属性以及相应的 getter 和 setter 对其进行分组。这个 JavaBean 如下所示。
清单 2
public class Customer implements Serializable {
private java.lang.String address1;
/** * Gets the address1 * * @return Returns a java.lang.String */ public java.lang.String getAddress1() { return address1; }
/** * Sets the address1 * * @param java.lang.String * The address1 to set */ public void setAddress1(java.lang.String address1) { this.address1 = address1; }
...
|
上面是一种非常简单的检查目标示例的方法。在许多情况下,模型和生成的构件并不是那么明显。我们将在后续的文章中演示更高级的分析技术,以便对样本进行分析并发现其中的模型。
创建和运行模式
为了使您清楚地了解如何创建和测试模式,现在我们将创建一个模式项目,并将这个模式应用于一个模型。
-
创建模式项目。
-
右键单击 Navigator 视图并选择 New => New pattern project。(图 9)
图 9. 创建新的模式
-
将该模式命名为 IBatisJavaBeanGenerator
,然后按 Next(图 10)。
图 10. 创建新的模式
-
对下列字段进行验证或输入相应的值:
- Pattern Name:输入模式名称。
- Pattern ID:唯一模式 ID 值。
- Appdef Type:保存您的模型的文件扩展名类型。当针对一个模型运行具有这种扩展名的模式时,这个模式将显示可适用于这个模型的模式的列表。
- Applies To:表示该模型是否在单个文件中,即是否使用单个文件进行代码生成。
图 11. 创建新的模式
-
DPTK 设计为使用模型-视图-控制器模式。因此,设计模式工具箱项目向导创建了一个模型-视图-控制器的缺省项目结构。(图 12)
图 12. MVC 项目结构
-
控制器文件夹下的模板将会用来实现生成过程。当您将模式应用于模型时,首先会调用 control.pat 文件。
-
在视图文件夹中,模式作者将存储用来表示数据视图的模板。
-
这个项目还创建了一个虚拟的 sample.appdef 文件(图 13)。在 Navigator 视图中选择该文件以打开它。
图 13. 虚拟的 sample.appdef 文件
-
图 14 中显示的是一个仅具有简单的 <app> 标记的 sample.appdef 文件。
图 14. 虚拟的 sample.appdef 文件
-
DPTK 还提供了简单的视图模板的示例,即 dump.pat 文件(图 15)。
图 15. 视图模板示例
-
如果打开这个 dump.pat 文件,您将发现另一个简单的文件,如清单 3 所示。这个文件采用了内存 (In-memory) 模型,并将其中的内容转储到一个文件中。
清单 3
<exists node="/*/.." attr=".encoding"> <?xml version="1.0" encoding="<attr node="/*/.." name=".encoding"/>"?> </exists> <exists node="/*/.." attr=".name"> <!DOCTYPE <attr node="/*/.." name=".name"/> PUBLIC "<attr node="/*/.." name=".publicId"/>" "<attr node="/*/.." name=".systemId"/>"> </exists> <dump node="/*" format="true" entities="true"/> |
-
打开 control.pat 文件。(图 16)
图 16. 打开 control.pat 文件
-
清单 4 显示了 control.pat 文件中的内容。如果查看代码中的粗体部分,您将看到我们使用 <start> 模板标记来调用 dump.pat。这个 start 标记将调用模式模板,并将结果转储到指定的资源,在我们的示例中,是 dump.xml 文件。
清单 4
*** High-Level Controller
*** Apply naming conventions <include template="ibatis.java.bean.generator/controller/naming.pat"/>
*** Derive names and other data <include template="ibatis.java.bean.generator/controller/derived.pat"/> *** Dump the modified appdef file for testing and debug purposes <start template="ibatis.java.bean.generator/view/dump.pat" resource="dump.xml" replace="true"/>
|
-
现在我们可以来研究如何调用这些模式。
-
右键单击所包含的 sample.appdef 文件,然后选择 Apply Pattern。(图 17)
图 17. 应用模式
-
选择 Ibatis Java Bean Generator 并按 OK。(图 18)
图 18. 生成模式
-
在生成该模式后,您应该看到图 19 中所示的文本框。
图 19. 成功地应用模式
-
您应该还有一个名为 dump.xml 的文件(图 20)。
图 20. Dump.xml 文件
- 如果仔细检查 dump.xml (图 21)的内容,您将发现,它是一个复制了该模型的简单的 XML 文件。(当然这并不是什么非常令人激动的事情,但是对于调试来说却非常有用。)
图 21. Dump.xml 文件
-
如果您感兴趣,可以将 sample.appdef 从样本项目中复制到模式项目,并覆盖缺省的项目。
-
重新将该模式应用于 sample.appdef(图 22)。
图 22. 重新应用模式
-
现在,dump.xml 文件应该包含了我们的样本模型。(图 23)
图 23. Dump.xml 文件
|
|
使用 DPTK 标记创建模式模板
现在可以创建我们的第一个模板了。我们的目标是从 sample.appdef 文件生成一个 JavaBean。
-
要生成 Java 源文件,生成过程必须发生在一个 Java 项目中。设计模式工具箱可以生成各种各样的 Eclipse 项目类型,但是考虑到我们的目的,我们将手动地创建一个 Java 项目:
-
在设计模式工具箱中选择 File => New=> Project(图 24)。
图 24. 创建 Java 项目
-
选择 Java Project(图 25)。
图 25. 创建 Java 项目
-
将该项目命名为 MyJavaBeanProject
,并按 Finish(图 26)。
图 26. 创建 Java 项目
-
当询问是否切换到 Java 透视图时,回答 No(图 27)。
图 27. 拒绝透视图切换
-
将 sample.appdef 文件从样本中复制到这个 Java 项目(图 28)。
图 28. 复制 sample.appdef 文件
-
现在我们将创建一个模式模板。右键单击 view 文件夹,选择 New => File(图 29),并将新的文件命名为 JavaBean.pat
(图 30)。
图 29. 创建模式模板
图 30. 创建模式模板
-
使用 Customer JavaBean 作为起点,将 JavaBean 代码复制到该模板中。从样本项目中复制 Customer.java 代码,并将其粘贴到该模板中。这个 pat 文件应该与图 31 中所示的文件类似。
图 31. 创建模式模板
-
要使 JavaBean 模板成为我们的模式中的一部分,必须将其添加到 control.pat。
-
打开 control.pat 文件(图 32)。
图 32. 打开 control.pat 文件
-
添加另一个 start 标记,如清单 5 中的粗体文本所示(或从下载文件的 C:/DPTKArticles/Part1/CodeSnippet1.txt 中进行复制)。template 属性指向 JavaBean.pat 文件。resource 属性定义了文件的名称。(资源文件夹是相对于 Eclipse 项目中指定的 src 目录,而不是项目的根目录,因为文件类型为 java。)
清单 5
<start template="ibatis.java.bean.generator/view/dump.pat" resource="dump.xml" project="MyJavaBeanProject" replace="true"/> <start template="ibatis.java.bean.generator/view/JavaBean.pat" resource="com/ibm/dptk/ibatis/dataobject/Customer.java" replace="true"/>
|
-
通过右键单击 MyJavaBeanProject 中的 sample.appdef,应用这个模式(图 33)。
图 33. 应用模式
-
从模式列表中选择 Ibatis Java Bean Generator(图 34),这时应该生成了 Customer 类(图 35)。
图 34. 应用模式
图 35. 生成的 Customer 类
使用设计模式工具箱进行构建
现在,我们将使用一些设计模式工具箱标记来构建 JavaBean 模式模板。
-
设计模式工具箱中包含一些标记,这些标记允许我们访问模型并使用模式中的数据。第一个标记是访问模型中的属性数据:
-
您将从该模型中回忆起用来获取类名的信息(图 36)。现在,打开 JavaBean.pat 文件(图 37)。
图 36. 打开 JavaBean.pat 文件
图 37. 打开 JavaBean.pat 文件
-
第一个标记是 <attr> 标记,它允许我们访问特定标记中的属性。<attr> 标记还有其他的一些属性,它们允许您以某种方式对文本进行格式化。如图 38 所示,使用清单 6 中的文本替换包名和类名。您还可以从 C:/DPTKArticles/Part1/CodeSnippet2.txt 中复制相应的文本。(另外,下载文件中提供了一个完整的模板,如果您希望加载它。)
图 38. 替换包名和类名
清单 6
package <attr node="/app/parameterMap" name="class" format="QP" />;
import java.io.Serializable;
public class <attr node="/app/parameterMap " name="class" format="QC"> implements Serializable {
|
-
您可以使用标记对模型中的属性数据进行转储。然而,有时可能需要修改内存模型。打开 control.pat 文件(图 39)并检查我们以前添加的 start 模板标记(清单 7)。
图 39. 打开 control.pat 文件
清单 7
<start template="ibatis.java.bean.generator/view/JavaBean.pat" resource="com/ibm/dptk/ibatis/dataobject/Customer.java" replace="true"/>
|
-
问题是,我们不能在另一个 DPTK 标记中使用 attr 标记。DPTK 提供了一种可用来访问数据的动态表达式语言。有一个问题是,类的格式需要在一个目录结构中。动态表达式可能会变得很笨拙。解决这个问题的另一种方法是,对 内存模型进行修改。这种方法允许您以不同的方式对相同的数据进行格式化,然后直接访问它。<setAttr> 标记允许您设置现有标记的属性。
-
添加清单 8 的粗体文本中的标记(或从 C:/DPTKArticles/Part1/CodeSnippet3.txt 中进行复制)。这里,我们以几种不同的方式对类名进行格式化。
清单 8
<setAttr node="/app/parameterMap" name="classname" ><attr node="/app/parameterMap" name="class"></setAttr> <setAttr node="/app/parameterMap" name="name" ><attr node="/app/parameterMap" name="class" format="QC"></setAttr> <setAttr node="/app/parameterMap" name="dir"><attr node="/app/parameterMap" name="class" format="PD"></setAttr> <setAttr node="/app/parameterMap" name="package"><attr node="/app/parameterMap" name="class" format="QP" /></setAttr> *** Dump the modified appdef file for testing and debug purposes <start template="ibatis.java.bean.generator/view/dump.pat" resource="dump.xml" project="MyJavaBeanProject" replace="true"/> <start template="ibatis.java.bean.generator/view/JavaBean.pat" resource="com/ibm/dptk/ibatis/dataobject/Customer.java" replace="true"/>
|
-
应用该模式,然后查看 dump.xml 文件,它显示了内存模型的情况(图 40)。我们并没有修改原始的输入模型。
图 40. 内存模型
-
现在,我们可以对模板进行修改,以访问新的模型元素:
-
修改 resource 属性的值,如下面的粗体文本所示。
清单 9
<start template="ibatis.java.bean.generator/view/JavaBean.pat" resource="%/app/parameterMap(dir)% .java" replace="true"/>
|
-
如下所示,回到 JavaBean.pat 并修改 attr 标记,以引用模型中新的数据。
清单 10
package <attr node="/app/parameterMap" name="package" />;
import java.io.Serializable;
public class <attr node="/app/parameterMap" name="name"> implements Serializable {
|
-
接 下来,我们需要生成 JavaBean 属性。因为 Java 属性的数目是可变的,所以我们需要对模型进行遍历。设计模式工具箱中具有 iterate 标记。(另外,从 C:/DPTKArticles/Part1/CodeSnippet4.txt 中为这部分内容复制完整的类代码段。)
-
打开 the JavaBean.pat 文件。
-
我们仅需要一个 Java 属性作为模型。删除第一个属性以外所有的属性。保留 address 属性以及它的 getter 和 setter。
-
在清单 11 中添加 iterate 标记,使之包含 Java 属性、getter 和 setter。图 41 演示了这个示例。
清单 11
<iterate nodes="/app/parameterMap/parameter" name="currentParameter" >
|
图 41. Iterate 标记
-
使用下面的标记替换类和属性。可以参考图 42,以了解在何处进行替换。
清单 12
private <attr node="currentParameter" name="javaType"/> <attr node="currentParameter" name="name"/>;
|
图 42. 替换类型和属性
-
最后,如清单 13 中的粗体文本所示,对 setter 和 getter 标记进行更新,并应用该模式(图 43)。
清单 13
package <attr node="/app/parameterMap" name="package" />;
import java.io.Serializable;
public class <attr node="/app/parameterMap" name="name"> implements Serializable {
<iterate nodes="/app/parameterMap/parameter" name="currentParameter" > private <attr node="currentParameter" name="javaType"/> <attr node="currentParameter" name="name"/>;
/** * Gets the <attr node="currentParameter" name="name"/> * @return Returns a <attr node="currentParameter" name="javaType"/> */ public <attr node="currentParameter" name="javaType"/> get<attr node="currentParameter" name="name" format="U1"/> />() { return <attr node="currentParameter" name="name"/>; } /** * Sets the <attr node="currentParameter" name="name"/> * @param <attr node="currentParameter" name="javaType"/> The <attr node="currentParameter" name="name"/> to set */ public void set<attr node="currentParameter" name="name" format="U1"/> /> (<attr node="currentParameter" name="javaType"/> <attr node="currentParameter" name="name"/>) { this.<attr node="currentParameter" name="name"/> = <attr node="currentParameter" name="name"/>; }
</iterate> }
|
图 43. 应用模式
生成第二个 JavaBean
要了解我们的模式的通用性,那么需要通过生成其他的一些内容来对其进行测试。我们的模式刚刚从单个模型生成了单个文件。然而,我们可以进行一些细微 的修改。(另外,您可以从 C:/DPTKArticles/Part1/Part1Solution.zip 加载解决方案项目交换文件,并运行最后的结果。)
-
让我们向模型中添加另一个 JavaBean。从 MyJavaBeanProject 中打开 sample.appdef 文件(图 44),然后添加一个附加的 parameterMap 标记,如清单 14 中的粗体文本所示。
图 44. 打开 sample.appdef 文件
清单 14
<app> <parameterMap class="com.ibm.dptk.ibatis.dataobject.Customer" id="newCustomerMap" > <parameter property="customerId" javaType="java.lang.Integer"/> <parameter property="name" javaType="java.lang.String"/> <parameter property="address1" javaType="java.lang.String" /> <parameter property="address2" javaType="java.lang.String" /> <parameter property="city" javaType="java.lang.String" /> <parameter property="state" javaType="java.lang.String" /> <parameter property="zip" javaType="java.lang.String" /> </parameterMap> <parameterMap class="com.ibm.dptk.ibatis.dataobject.Order" id="newCustomerMap" > <parameter property="customerId" javaType="java.lang.Integer"/> <parameter property="orderId" javaType="java.lang.String" /> </parameterMap> </app>
|
-
接下来,要对该模式进行更新,以使它能够生成多个文件,我们需要对 control.pat 文件进行更新:
-
打开 the control.pat 文件,并按照下面粗体文本的内容进行修改。我们可以使用 iterate 标记批量地生成文件,以及批量地更新模型。
清单 15
*** High-Level Controller
*** Apply naming conventions <include template="ibatis.java.bean.generator/controller/naming.pat"/>
*** Derive names and other data <include template="ibatis.java.bean.generator/controller/derived.pat"/> <iterate nodes="/app/parameterMap" name="currentBean" > <setAttr node="currentBean" name="classname" ><attr node="currentBean" name="class"></setAttr> <setAttr node="currentBean" name="name" ><attr node="currentBean" name="class" format="QC"></setAttr> <setAttr node="currentBean" name="dir"><attr node="currentBean" name="class" format="PD"></setAttr> <setAttr node="currentBean" name="package"><attr node="currentBean" name="class" format="QP" /></setAttr> </iterate> *** Dump the modified appdef file for testing and debug purposes <start template="ibatis.java.bean.generator/view/dump.pat" resource="dump.xml" project="MyJavaBeanProject" replace="true"/> <iterate nodes="/app/parameterMap" name="currentBean" > <start template="ibatis.java.bean.generator/view/JavaBean.pat" resource="%currentBean(dir)%.java" replace="true"/> </iterate>
package <attr node="currentBean" name="package" />;
import java.io.Serializable;
public class <attr node="currentBean" name="name"> implements Serializable {
<iterate nodes="currentBean/parameter" name="currentParameter" > private <attr node="currentParameter" name="javaType"/> <attr node="currentParameter" name="property"/>;
/** * Gets the <attr node="currentParameter" name="property"/> * @return Returns a <attr node="currentParameter" name="javaType"/> */ public <attr node="currentParameter" name="javaType"/> get<attr node="currentParameter" name="property" format="U1"/> />() { return <attr node="currentParameter" name="property"/>; } /** * Sets the <attr node="currentParameter" name="property"/> * @param <attr node="currentParameter" name="javaType"/> The <attr node="currentParameter" name="property"/> to set */ public void set<attr node="currentParameter" name="property" format="U1"/> /> (<attr node="currentParameter" name="javaType"/> < attr node="currentParameter" name="property"/>) { this.<attr node="currentParameter" name="property"/> = <attr node="currentParameter" name="property"/>; } </iterate> }
|
结束语
在 本文中,我们通过一个简单的示例向您介绍了如何使用 DPTK。然而,DPTK 还可以用来以最少的工作量解决非常复杂的问题。我们重点关注于构建模式模板;DPTK 还提供了相应的分析工具,用于从现有的实现中发现模型。这使得我们可以通过反向工程将代码转换到相应的中性模型和功能,它们可以作为构建基于资产的业务的 基础。在后续文章中,将介绍一些更复杂的问题,以及如何使用设计模式工具箱中更高级的特性来解决这些问题。
致谢
本文作者要感谢 Geoffrey Hambrick 为本文提供了有价值的建议和评论。
下载
描述 |
名字 |
大小 |
下载方法 |
Code samples |
DPTKArticleMaterials.zip |
11 KB |
FTP|HTTP |
参考资料
作者简介
|
|
|
Roland Barcia 是位于纽约/新泽西地区的 IBM WebSphere 软件服务部的一位认证 IT 专家。他是 IBM WebSphere: Deployment and Advanced Configuration 的合著者。有关 Roland 的详细信息,请访问他的网站。 |
|
|
|
Chris Gerken 是 IBM Software Services for WebSphere group 中 Asset Reuse Enablement Team 的成员。他创建和编写了设计模式工具箱。 |