当前页面位置: » 丰搜网 » 文档中心 » 详细内容
利用“轻量级域描述模式”提高开发生产力
利用“轻量级域描述模式(lightweight domain specific modeling)”提高开发生产力作者:patrik nordwall译者:cleverpig(http://blog.matrix.org.cn/page/cleverpig)简介:通过尝试本文表述的简单方法可使开发生产力得到提升。这个方法便是使用一个域描述语言(domain specific language)和基于问题域的自定义代码生成器来驱动开发。轻量级域描述
模式(dsl)意味着作为开发者将实现并使用自己的代码生成器,有效地避开重复的手工编码,从而达到提高开发中的生产效率。在开发中,我们握有控制权并可轻而易举地按照需求利用工具和我们身边常用的
框架。此方法利用
eclipse中与一些免费工具去实现。
问题:迄今为止,众多的软件开发方式还是原始且不能称得上高效。往往一些自动化处理开发过程的尝试会让勇敢的尝试者陷入难于自拔的复杂性中。
编码者(programmers)企图通过高频率的重复工作搭建起程序的骨架。不久之后,它将成为一个维护重担,因为单个位置的每次修改都将波及到数个其它的类、接口或者配置文件。
举个例子,使用抽象工厂(abstract factory)思想的设计:工厂的接口与实现与产品分类,多数情况下称得上是良好设计。但是,它仍然存在着缺陷。假如我们的10个产品种每个产品需要3个工厂和2种不同的产品实现(真和假),这时至少需要99个类。
于是“传统”的重复性复制、粘贴、替换的编码工作将出现在开发任务中,这意味着我们开发者的开发快乐将被这些烦劳冲净。不仅如此,当我们需要修改产品定义或者实现方式(例如在接口中的命令模版介绍或者实现的模版方法),更多的复制、粘贴。。。闻一下,仿佛嗅到了beck所讲的第一号“坏味道”(bad smell)——重复。
解决:使用dsl匹配问题域、利用定制代码生成器生成实现。它完成了提升抽象等级和自动化手工编码的工作。这并不是创新思路,但使用dsl而又不介入更多的复杂性并非易事。
控制dsl和代码生成工具成为了行之有效的方法:利用工具满足系统需求。作为开发者,我们将定义dsl、自己实现代码生成器。这一切都在我们的控制下。这是从众多
模式驱动
框架(model driven architecture)工具中脱颖而出的“另类”方法。原因是第三方提供的代码生成工具常在第一个demo中表现良好,但后期使用将变为不适用、难于随着需求而改进。相反,自己开发代码生成器可随时按需修改。
图1.概貌:实现方法和工具
文中的工具集和图1中的工具仅作为如何实现方法的示例。我们应根据需要组织适当的工具。
上图具体展示了实现方法的各个部分:
1. argouml:用于定义dsl
模式。由作为开发者的我们设计dsl
模式去匹配问题域。
2. 转换格式:利用argo2ecore转换argouml
模式到
eclipse中的
模式ecore。
3. 开发代码生成器:使用jet模版开发代码生成器,jet是
eclipse中使用的代码生成插件。
4. 导入ecore
模式:将ecore
模式导入模版。定义在ecore
模式中的
模式元素和merlin jet模版(eclispe插件)之间的映射。
5. 生成代码:最终期待的结果。
本文表述的实现方法可为视为域描述
模式(domain specific modeling,简称dsm)的轻量级变体。dsm面向大型项目和特殊的产品家族。本方法适用于中型项目(<10开发者,<1年开发),着眼在以务实的解决方式减少投资和最小化学习成本上。
作为一种务实且灵活的方法,它必须易于修改、增加代码生成模版。而且代码生成器的开发必须投资低、回报周期短。必要时在几小时内便可完成新的代码生成器。
工具不应限制设计或者设计需求。我们了解系统需求,应该为生成的代码负责。开发者的枪并没有被装备“银弹”,作为开发者,利用自身设计技术和系统定制设计才能使项目开发走向高效。
本文中选择了uml作为dsl。类图表也被使用,当然我们也可选择别种,但是uml具有明显的“天然”优势——众多可选择的工具,它们将作为我们开发的基础工具。
模式并非1对1的实现表示法。
模式是作为代码生成输入的高级别抽象,它应愈简单愈好。代码生成模板面向问题和
模式,所以
模式不能包罗万象。一些信息可以简单的放到模版中,如命名转换。
模式设计简单非常重要,
模式不能作为处理一切奇特情况的通用解决方案。这里再一次验证了前面所讲的,正是因为我们握有dsl
模式和模版的控制权,所以选择最简单的解决方式才是可行的。
dsl
模式是具体的
模式,无需定义元
模式(meta model)。当然,也不需要基于元
模式的形式化的
模式转换(formal model transformations)。这一点是与mda的主要差异,mda强调工具是第三方工具提供者提供的作为普通目的工具,因此需要
模式转换的复杂理论支持。
dsl
模式的目的是驱动开发,而不是系统文档。对于生成系统文档,普通的反向工程uml工具即可完成,但这个反向
模式并不属于dsl
模式。
是否分离生成代码是由开发者按照设计需求而确定。本方法生成混合代码:生成代码和手写代码。这里存在着弊端,详见separate generated and non-generated code模版中的描述。
轻量级dsm实现方法集成应用
框架的方式是perfect。这一点在rich domain specific platform模版中有所说明。利用设计模版和
框架是本实现方法的关键成功要素。设计优良的
框架经常需要插入一些特定实现并编写配置使其协同工作。代码生成方式则是一条“高速路”,它减少了
框架完成后的代码数量和复杂性。在被用作某个特定
框架时,这个优点的应效愈加明显。因为我们可以轻松的定义适合自己
框架的dsl和模版。
实例:为了展现轻量级dsl实现如何被用在实际开发中,下面用一个实例进行说明。另外,相关的详细内容可以在参考附表中找到,完整的实例源代码下载可由这里下载。
这个实例是一个“简易”的用于保管电影和书籍的图书馆系统。相对它提供的功能来讲,它算是“过剩设计”了。这是为了能强调代码生成的功效,而从实际设计的角度评价——它不是一个最好的设计,但它捕捉到了一些常用在实际应用中的模版。这些设计模版在这里没有被详尽的说明,请在参考附表中获得更多的信息。
实例中采集了一些简单基本类和工具类与
hibernate框架结合使用,以说明如何实现与应用
框架的集成。
设计:本章节着笔于设计过程。从下图“域
模式”看出,域
模式(domain model)位于系统的核心。图书馆有各色的物理媒体(physicalmedia)构成。书籍和电影分属不同的媒体(media),如dvd、纸制书籍、电子书等,而媒体存储在物理媒体上。每个媒体具有主角,如由一个人(pierce brosnan)扮演的james bond(笔者相信007的大名“詹姆斯.邦德”和扮演者“皮尔斯.布鲁斯南”已经家喻户晓了)。每个人可能出现在不同的媒体上(如电影和期刊),实际上每个人可由多个受雇关系在同一个媒体上(如出任演员和做导演)。
图2.图书馆的域
模式仓库(repositories)被用于寻找/更新域
模式集合体(aggregates)。仓库具有实现了特定持久化功能的访问对象(
access objects),后者分离了接口和实现。抽象工厂(abstract factory)被用于建立访问对象的实例。访问对象如命令(commands)一般的被实现。
hibernate作为o/r映射
框架和访问对象crud操作的通用集合,成为了应用
框架的一部分。
在域
模式的前端,我们准备了服务层(service layer),它通过调用仓库完成寻找/更新域
模式集合体。
图3.整体设计:服务、仓库和访问类
工具:文中用到的工具:
eclipse3.1,
java5.0,emf,merlin,argo2ecore,argouml,
hibernate和
mysql。我们需要安装它们才能运行实例程序。本文选择这些工具的主要因为它们易于集成和使用。开发者亦可自己决定使用的工具。
dsl模式:实例中的dsl
模式分为三部分:域
模式、仓库、服务。它被裁减成为了一个相当于技术系统设计
模式,并不依赖业务
模式。为了充分衡量dsm的作用,最好在dsl中使用来自业务
模式的概念。至于为什么这样做,请看why dsm。
在图4“域
模式”中的类图表显示了dsl
模式中的域
模式。它定义了类、属性、域
模式关联关系。这些信息将被用于生成域
模式实现类和
hibernate映射、
数据库schema。
图4.在argouml中的域
模式图中有两个仓库类——分别为图书馆和人的集合体。它们已经被
模式化为具备操作方法的普通类。在仓库类、访问对象类、抽象工厂类生成时,这些信息都被将用到。
图5.dsl
模式中的仓库类
下面的服务类为client应用提供接口。在dsl
模式中,我们定义服务操作和最后到仓库的委托。
图6.client通过服务层与应用交互
使用argouml定义好dsl
模式后,使用argo2ecore插件将argouml
模式转换为ecore
模式。这些工作只需在被导出的
xml文件上点击右键菜单来完成。关于ecore,请看参考附录的ecore部分。
代码生成:jet在本实例中用作代码生成模版。jet与
jsp类似,具备
jsp开发经验的开发者上手很快。使用方法详见参考附表的jet 基础。
jet模版定义在一个独立的
eclipse jet工程里,但无需定义它们的package。我们使用helper类从模版中使用ecore
模式,这个helper提供了我们所需的功能,例如字符串维护。在本实例中helper被命名为ecoregenerationhelper。我们直接使用它,或者修改/扩展其功能。在一些特殊情况下,我们自己需要实现提供某些功能的helper类,并用在需要这些功能的模版中。本实例中的databasegenerationhelper就是用于
hibernate和ddl生成的。
图书馆实例中使用了11个jet模版,其中一个是repository.
javajet,下面列出一些有趣的部分。
在dsl
模式中的两个仓库类映射到了repository.
javajet,它生成libraryrepository.
java和personrepository.
java的命名转换规则。命名转换规则:输入类必须以“repositroy”结尾,前面的字符作为类名的一部分。package命名转换也被定义在模版中。
下面的repository.
javajet的一段有趣的代码,用于生成方法。它生成到抽象工厂的委托和访问对象,但是如果“no
accessobjet”标注定义在操作上,则将生成一个空白的方法存根(为手工编码提供方便)。
<%for (eoperation op : h.getoperations(eclass)) {
// a few naming mapping conventions
string mappedopname = h.getmappedoperationname(op);
boolean findbyid = (mappedopname.equals("findbyid"));
%>
/**
* <!-- begin-user-doc -->
* <!-- end-user-doc -->
* @generated
*/
<%=h.getvisibility(op) %><%=h.gettypename(op)%>
<%=h.getname(op)%>(<%=
h.getparameterlist(op)%>) <% if (findbyid) {
%>throws <%=basename%>notfoundexception<%}%> {
<% if (h.getannotation(op, "noaccessobject") != null) {%>
// todo auto-generated method stub
throw new unsupportedoperationexception("<%=mappedopname
%> not implemented");
<%} else {%>
<%=h.capname(mappedopname)%><%=producttype%>
<%=h.getgenerictype(op)
%> ao = <%=h.uncapname(basename)%><%=producttype%>
factory.create<%=
h.capname(mappedopname)%><%=producttype%>();
<%for (eparameter parameter : h.getparameters(op)) {%>
ao.set<%=h.capname(h.getname(parameter))%>(
<%=h.getname(parameter)%>);
<%}%>
ao.execute();
<%if (!h.gettypename(op).equals("void")) {%>
<%if (findbyid) {
eparameter idparam = h.getparameters(op).get(0);
%>
if (ao.getresult() == null) {
throw new <%=basename%>notfoundexception("no <%=
basename%> found with <%=h.getname(idparam)%>: " + <%=
h.getname(idparam)%>);
}
<%}%>
return ao.getresult();
<%}%>
<%}%>
}
<%}%>
注意:使用有类型修饰的列表(typed lists)时的约定,在
模式中使用操作的类型数组,如media[],生成代码为list<media>。此映射在helper的gettypename方法中实现。这是在模版中实现的典型设计方式。
下面是实例中其它部分生成代码的细节描述:
domain model
access objects
repository
ddl
hibernate mapping
service layer
模式->模版映射下一步是建立
模式元素与模版之间的联系。merlin被用于定义这个映射,它提供了可以方便的在
模式元素和jet模版之间进行拖拽的用户接口。一个
模式元素代表了一个类、操作或者package,只要它被定义在
模式中即可。这些
模式元素将作为模版输入参数。在merlin jet映射
模式后,我们开始生成代码。可查看附录中关于merlin tools的部分。
图7展示了如何用jet模版生成代码
图7.左侧的ecore
模式映射为中间的jet模版,而后在右侧生成代码。
本系统部分使用了生成代码,另一部分为手写代码。轻量级dsm方法并不可能生成所有的代码。而我们需保持它们尽可能的简单。如果手工编码很容易的话,那么就要手工编写。详见:混合生成和手写代码。
资源:[1]
java 5: http://
java.sun.com/
j2se/1.5.0/
[2]
eclipse 3.1: http://www.
eclipse.org/
[3] emf sdk 2.1.0, can be downloaded from
eclipse.org update site: http://www.
eclipse.org/emf/
[4] merlin generator 0.5.0: http://sourceforge
.net/projects/merlingenerator/
[5] argo2ecore 2.1.0: http://sourceforge
.net/projects/argo2ecore
[6] argouml 0.20: http://argouml.tigris.org/
[7]
hibernate 3.0.5: http://www.
hibernate.org/
[8]
mysql: http://www.
mysql.com/
[9] model driven architecture: http://www.omg.org/mda/
[10] domain specific modeling: http://www.dsmforum.org/
[11] patterns for model-driven software-development: http://www.voelter.de/data/pub/mddpatterns.pdf
[12] patterns of enterprise application architecture, martin fowler: http://www.martinfowler.com/eaacatalog/
[13] domain-driven design, eric evans: http://domaindrivendesign.org/book/
[14] designing enterprise applications with the
j2ee platform: http://
java.sun.com/blueprints/
guidelines/designing_enterprise_applications_2e/
[15] design patterns: elements of reusable object-oriented software, eric gamma et al.: http://www.amazon.com/gp/product/0201633612/104-6195925-4777508
关于作者:patrik nordwall是avega公司(位于瑞典斯特哥尔摩的it顾问公司)的软件开发者。具有10年
java开发经验,长于软件架构。专长领域遍布
j2ee,
swing,设计模版,
框架和
开源软件(
spring、
hibernate、
struts、
eclipse)。patrik身兼sun认证程序员、开发者、企业架构师的三重身份。有兴趣可以联他:patrik.nordwall@gmail.com。
原文地址:
http://www.theserverside.com/articles/content/lightweightmodeling/article.
html