|
|
如何降低软件开发维护成本、提高开发效率、减小需求变更对系统的影响以及延长系统的生命周期是每个软件开发者都在思索的问题。然而传统的开发途径中上面的问题总是很难完美的解决。 MDA(模型驱动架构,Model Driven Architecture)是由OMG组织提出的新的软件开发架构,他相对于传统的开发方式有了很大的变化,软件开发的驱动力不再来自于传统的概要设计、编码,而是由模型来驱动开发。使用MDA开发团队可以将时间和精力集中在应用的业务逻辑上,而不需要花费大量时间来设计架构。MDA不仅仅是一种开发架构,更主要的是一种方法、标准,它独立于系统平台和开发语言之外。各个软件供应商也都相继提供了MDA的解决方案,本文所讲的ECO(Enterprise Core Objects)就是其中之一。
Borland在2004年初推出的Delphi 8 for Microsoft .NET Framework称得上是Delphi历史上最重要的一个版本,这个版本能否成功关系到Delphi能否在.net平台上再现win32平台上的辉煌。Delphi在win32平台上的成功很大程度上要归功于优秀的VCL架构。正是因为微软在Win32平台上没有提供完整易用的组件库,才使得VCL有如此蓬勃的发展。而在.NET平台上微软已经提供了完善成熟的Framework,Delphi8中虽然依旧包含VCL,但现在的VCL.NET已经不能完全和.NET Framework的Winform兼容了,而是为了方便Delphi的用户平滑过渡到. NET。失去VCL的优势对于Delphi来说也并非完全是件坏事,Borland会把更多的精力放在如何提高开发工具的生产力上。Borland在收购TogetherSoft、BoldSoft、Starbase等公司后,开始对所掌握的技术进行整合,ECO正是Bold基础上发展出的.NET平台MDA解决方案。它提供了封装有各种典型业务应用的底层应用服务,可以很好的完成各种系统开发和业务应用开发。Borland软件产品的副总裁,Boz Elloy认为,"ECO不但可以减少编写和维护的代码量,同样可以减少风险,支持更高的软件产品质量,我们相信ECO至少要比市场上的同类产品领先一年。"下面我们就实际体验一下MDA在Delphi中带给我们的惊喜。
实战ECO开发
下面我们要开发的是一个汽车销售客户管理系统,汽车销售商要管理客户的资料,客户中既有个人客户也有企业客户,两者的属性各有不同。汽车的型号资料和每辆汽车的销售记录同样也要管理。根据上面的简单需求来开始我们的ECO开发之旅。
1. 使用ECO Application Wizard建立一个工程
1) 打开Delphi 8 架构版在点击File →New →Other打开新建项目对话框。选择ECO Windows Application后点击OK
2) 输入工程名称和路径后点击OK,ECO Application Wizard为我们创建了所需要的工程单元文件。(注意:因为Delphi 8 Eco中存在的一个BUG,在工程路径或工程名中存在全角字符会引起编译器编译是发生错误,这里必须保存在英文路径下)
ECO Application Wizard生成的新工程包含以下几个文件:
| 文件名 |
描述 |
| CoreClasses.pas |
其中包含UML packages、 interfaces、 classes之间关联关系的源代码和在模型中定义的类型 |
| EcoCRMEcoSpace.pas |
由Borland.Eco.Handles.EcoSpace派生出的子类TEcoCRMEcoSpace的源代码 |
| WinForm.pas |
应用程序主窗体的源代码 |
Borland.Eco.Core.dll Borland.Eco.Handles.dll Borland.Eco.Interfaces.dll Borland.Eco.Ocl.ParserCore.dll
|
Borland.Eco.Persistence.dll 这些文件是ECO applications运行时所需要的文件,存放在C:\Program Files\Common Files\Borland Shared\BDS\Shared Assemblies\2.0目录下 | 2. 需求分析
由上面所提供的需求我们可以提炼出以下几个类:
| 名称 |
描述 |
| Customer |
客户,包含属性有:Name(名字)、Phone(联系电话) Address(住址) |
| Person |
个人客户,除了包括客户的属性外还有Sex(性别)属性 |
| Company |
公司客户,其具有Customer的全部属性 |
| Car |
汽车,包含的属性有ID(汽车编号) Name(汽车名称) CarType(汽车型号) |
| CarSell |
汽车的销售记录,包含的属性有BuyDate(购车时间)Fee(价格) | 各个类之间的对应关系如下:
Person和Company都是Customer的子类,它们都拥有Customer的属性。汽车的销售记录中包含所销售的车辆,CarSell和Car有一对一或一对多的对应关系,也就是一条销售记录可能对应多个车辆,同样客户和销售记录也有一对一或一对多的对应关系。
3. 设计模型
根据上面的分析结果我们可以开始从ECO中进行建模,首先选择模型视图,打开CoreClasses包,然后双击CoreClasses打开模型设计面板。
Tool Palette 中的UML Class Diagram 中共包含6个组件他们分别是:
| 名称 |
描述 |
| Eco Package |
模型包,如果系统结构复杂可用其来进行分解以降低复杂度,CoreClasses包是ECO Application Wizard默认生成的包 |
| Class |
类 |
| Generalization/Implementation |
继承关系 |
| Association |
联合关系 |
| Note |
注释 |
| Note Link |
注释链接 |
首先我们将一个Class拖放到设计器中,将其命名为Customer,再点击右键选择 Add →Attribute为其添加一个属性,并设置该属性的名称为"Name"类型为String。在属性的类型设置中,你可以输入Delphi支持的类型。同过设置Alias属性还可以在模型中显示中文别名,方便我们进行建模。Class常用的属性如下:
| 属性名 |
默认值 |
描述 |
| Abstract |
False |
抽象类 |
| Sealed |
False |
能否被继承 |
| Alias |
'' |
别名,显示在模型编辑器中的名字 |
| Persistence |
persistent |
是否被存储 |
| DefaultStringRepresentation |
'' |
默认显示字符,可将其设置为关键的属性名 |
根据我们在上面需求中所分析出的四个类在编辑器中创建,因为Person和Company都是继承自Customer,所以仅仅描述其不同于Customer的属性即可。完成后设计器内容如图所示:
完成上述工作后,我们开始对模型间的关联关系进行设计,首先来完成Customer的继承关系。选择Tool Palette中的Generalization/Implementation组件,点击Person模型后拖曳到Customer模型去,这样就建立好了两者间的继承关系,同样建立Company与Customer的继承关系。
然后开始建立Customer和CarSell之间的主子对应关系,选择Tool Palette中的Association组件,点击CarSell模型后拖曳到Customer模型去,即生成了一个Association。点击选中Association后设置它的属性,修改其name为PayAssociation,End1的 Multiplicity为0..*;End2的Multiplicity为1。Multiplicity属性用于控制模型间的连接关系,其可选属性的含义为:
| 可选择值 |
描述 |
| 0..1 |
有零个或一个连接 |
| 1..1 |
有一个且只有一个连接 |
| 0..* |
有零个或多个连接 |
| 1..* |
有一个或多个连接 | 同理为CarSell和Car建立关联关系,建立完成后的模型图如下:
到此为止我们的建模工作已经完成,在此之后我们还需要把对象保存发布到数据库中,这就用到了Eco所提供到的PersistenceMapper组件。在Delphi 8 中Eco一共提供了13个组件,它们分别是:
| 编号 |
组件名 |
命名空间 |
功能 |
| 1 |
CurrencyManagerHandle |
Borland.Eco.Handles |
用来操作当前游标所在的对象实体 |
| 2 |
ExpressionHandle |
Borland.Eco.Handles |
可以将其简单理解为一个数据源组件(TDataset),数据源通过SQL语句向数据库查询记录,而ExpressionHandle更为灵活可控的OCL语句查询对象实例 |
| 3 |
OclPSHandle |
Borland.Eco.Handles |
根据OCL表达式得到元素值,其主要方法为Execute |
| 4 |
OclVariables |
Borland.Eco.Handles |
定义在OCL中所使用的变量 |
| 5 |
ReferenceHandle |
Borland.Eco.Handles |
建立一个数据库连接,在使用中相当于dbConnection |
| 6 |
VariableHandle |
Borland.Eco.Handles |
通常和OclVariables一起使用 |
| 7 |
PersistenceMapperBdp |
Borland.Eco.Persistence |
将对象模型通过BdpConnection.进行发布 |
| 8 |
PersistenceMapperXML |
Borland.Eco.Persistence |
将对象模型通过Xml格式.进行发布. |
| 9 |
PersistenceMapperSqlServer |
Borland.Eco.Persistence |
将对象模型通过SqlServer.进行发布 |
| 10 |
EcoAutoFormExtender |
Borland.Eco.WinForm |
自动生成类信息窗体 |
| 11 |
EcoActionExtender |
Borland.Eco.WinForm |
和Button的EcoAction属性绑定,由按钮事件实现对象方法 |
| 12 |
EcoDragDropExtender |
Borland.Eco.WinForm |
提供Eco对象间在GUI中的拖曳支持 |
| 13 |
EcoListActionExtender |
Borland.Eco.WinForm |
和Button的EcoListAction属性绑定,用以控制CurrencyManagerHandle事件 | 其中Extender类组件将标准的.Net组件(DataGrid,TextBox等)和ECO进行了关联,这些Extender组件在由ECO Application Wizard创建完成后就自动生成了这些组件,我们不需要手工创建。我们下面用到的PersistenceMapper,是将对象模型发布到数据库或XML中,同时也将对象实例存储到数据库中。如果我们在项目中需要转换数据库平台,不需要作过多的改变,仅仅将PersistenceMapper类型更改以下就可以,实现了真正的数据库平台无关性。在本演示中我们用PersistenceMapperXML来进行数据的存储,在程序完成需要发布时根据需要再改为其它类型的数据库即可。
在工程视图中双击打开EcoCRMEcoSpace.pas,并切换到设计视图,从Tool Palette中双击PersistenceMapperXML,添加一个PersistenceMapperXML到设计器中,设置其FileName属性为"EcoCrmData.xml",并将TEcoCRMEcoSpace的PersistenceMapper属性设置为PersistenceMapperXML。
点击放大(上图)  |
4. 编码
下面我们要完成的就是界面设计、编码工作。在工程视图中双击WinForm.pas打开窗体编辑器。ECO Application Wizard已经创建了一个ReferenceHandle组件rhRoot和多个Extender组件。Extender组件保留其默认属性即可,如果将其删除用户界面中的控件将无法和ECO关联。设置rhRoot的EcoSpaceType 为 "EcoCRMEcoSpace.TEcoCRMEcoSpace",如果在EcoSpaceType属性下拉列表中没有选项,可重新编译一下工程。
在界面上放置6个DataGrid,分别将其重命名为dgPersons、dgCompanys、dgCompanyCars、dgPersonCarSells、dgCompanyCars、dgPersonCars,为使界面更清晰可以分别设定一下DataGrid的CaptionText。并且在每个DataGrid上放置两个按钮,其Text分别为"添加"、"删除"。完成界面设计为如下样式。
用户界面中的组件需要和ExpressionHandle相连接才能显示感知对象实例。在这里ExpressionHandle与Delphi中Dateset的概念很类似,它是一组数据的集合。向窗体编辑器中新添加一个ExpressionHandle,设置其Name属性为ehCompanys,其RootHandle属性为rhRoot,双击Expression打开OCL Expression编辑器。OCL Expression编辑器很类似于我们以前用的SQL编辑器,不过它使用的是OCL语法,在系统发布运行后ECO会根据不同的数据库平台将OCL翻译成相应的SQL进行执行。在OCL Expression编辑器中输入"Company.allInstances",它的含义是取得Company的所有对象实例,我们可以将它等效理解为SQL语句中的" Select * from Company"。
同样我们为其他的几个类建立对应的ExpressionHandle,它们的名字分别为ehPersons、ehCompanyCarSells、ehPersonCarSells、ehCompanyCars、ehPersonCars。为实现主子表关联关系我们还需要CurrencyManagerHandle组件,添加一个CurrencyManagerHandle组件到设计器中,将其重命名为cmhCompany,设定其RootHandle属性为主对象ehCompany,其BindContext 属性为显示Company列表的dgCompanys。
CurrencyManagerHandle组件的Element属性即表示对象集中的当前元素,它是根据BindContext属性来进行判断当前元素的。我们如果想要使ehCompanyCarSells与Company建立主子关联关系则需要设置ehCompanyCarSells的RootHandle属性为cmhCompany,Expression属性为"self.CarSell"。建立其他需要进行主子表关联的CurrencyManagerHandle,它们分别是cmhPerson、ehCompanyCarSells、ehPersonCarSells、ehCompanyCars、ehPersonCars。其属性与cmhCompany类似。
主要组件属性为:
ehPersons.Expression := 'Person.allInstances'; ehPersons.RootHandle := Self.rhRoot; ehCompanyCars.Expression := 'self.Car'; ehCompanyCars.RootHandle := Self.cmhCompanyCarSell; cmhCompanyCarSell.BindingContext := Self.dgCompanyCarSells; cmhCompanyCarSell.RootHandle := Self.ehCompanyCarSells; ehCompanyCarSells.Expression := 'self.CarSell'; ehCompanyCarSells.RootHandle := Self.cmhCompany; cmhCompany.BindingContext := Self.dgCompanys; cmhCompany.RootHandle := Self.ehCompanys; ehCompanys.Expression := 'Company.allInstances'; ehCompanys.RootHandle := Self.rhRoot; ehPersonCarSells.Expression := 'self.CarSell'; ehPersonCarSells.RootHandle := Self.cmhPerson; cmhPerson.BindingContext := Self.dgPersons; cmhPerson.RootHandle := Self.ehPersons; ehPersonCars.Expression := 'self.Car'; ehPersonCars.RootHandle := Self.cmhPersonCarSell; cmhPersonCarSell.BindingContext := Self.dgPersonCarSells; cmhPersonCarSell.RootHandle := Self.ehPersonCarSells; | 然后将刚才所设计的DataGrid和ExpressionHandle组件关联,设置DataGrid的DateSource属性为相对应的ExpressionHandle。下面我们需要为添加删除方法编写代码,对于那些没有主对象关联关系的对象来说代码十分简单。
procedure TWinForm.btnPersonAdd_Click(sender: System.Object; e: System.EventArgs); begin Person.Create(EcoSpace); end; | 添加人员只用这一句代码即可,删除代码同样很简单:
procedure TWinForm.btnPersonDelete_Click(sender: System.Object; e: System.EventArgs); begin Person(cmhPerson.Element.AsObject) .AsIObject.Delete end; | 添加删除Company的代码和这个类似,但带有主对象关联的对象在添加时候还需要为其主对象赋值。CompanyCarSells(企业购车纪录)的添加代码为:
| CarSell.Create(EcoSpace).Customer := Customer(cmhCompany.Element.AsObject); | 其中cmhCompany.Element.AsObject为当前所被选中的Company的值,将其转换Customer类型赋值给新创建好的 CarSell.Create(EcoSpace).Customer。
我们需要完成的代码十分简单,为每个方法添加一句类似于上述的过程代码即可。ECO带给我们的变化的确令人惊喜。我们现在完成了对对象操作的代码,下面还需要把所添加修改的事例保存到数据库中,添加一个按钮只需要执行一下EcoSpace.UpdateDatabase;就可以完成数据的保存工作。
系统运行界面如下:
在选择客户时,客户汽车销售纪录会自动显示相关纪录,而浏览销售纪录时下面会自动显示与销售纪录相关的汽车信息。ECO还提供了AutoForm 功能,选择一个DataGrid设置其EcoAutoForm属性为True[图16],在运行状态时,双击DataGrid标题即可打开ECO自动生成的对象属性Form。如果需要手工调用AutoForm可以用下面的代码。
Uses Borland.Eco.AutoContainers, Borland.Eco.ObjectRepresentation; .. var autoContainer: IAutoContainer; begin autoContainer := AutoContainerService.Instance.CreateContainer( EcoSpace, cmhCompanyCarSell.Element); Form(autoContainer).ShowDialog; end; | 后记
通过上面的实例我们已经看到MDA开发的威力了,它不仅大幅提高了开发效率,还以模型驱动的方式保证了设计和编码的一致性。但是对于ECO来说,前面还有很长的路要走,Delphi 8的完成度远远达不到它以前的版本。在笔者写DEMO过程中就发现了很多常规操作中的Bug,Borland即将推出Delphi 8 的Update,以修正其中的问题。现在Eco并不支持ASP.NET开发,如果不出意外在下个版本的Eco就会加入对ASP.NET的支持并添加更多易用的组件。Eco虽然还不够成熟,但是我们还是能从上面看到很多软件开发方式上的变化,相信随着ECO的成熟和完善,人们也会更加广泛的接纳它。
|
|