统一建模语言(UML),作为一种实际应用的语言标准,借助一系列架构图呈现建模软件系统。UML 的出现鼓励了自动化软件工具的开发,有助于自动代码生成。UML 图面向对象系统和软件工具,将静态结构和动态行为以可视化的模型展现,而后继续根据既定的图表自动生成代码。
模型驱动工程(MDE)是一种软件开发方法,以模型代替源代码,作为架构图软件开发生命周期(SDLC)的主要构件。
模型驱动工程公认的发展源头是模型驱动架构(MDA)。这是对象管理组织(OMG)在 2001 年提出的一种新的软件开发范型(summerville, 2009)。模型驱动架构使用 UML 图的子集,如类图、序列图和状态图。其设计初衷是减少开发工作量,在软件开发过程的每个阶段创建、使用分析和设计模型,并自动从模型生成代码。
然而,自动代码生成在软件工程中得到了广泛的关注,因为它具有可重复使用、出错率低(对比人工代码编写)、易于维护和准确度高等优点。此外,如果可以从模型自动生成代码,以便精确理解模型和代码之间的对应关系,则高级建模和分析的优势将显著增强。
动机
鉴于抽象水平的差异,架构图通常存在模型 – 系统缺口。随着可视化建模的日益普及,使用高级模型进行程序代码自动生成也成为一个重要的问题。如果可以从模型中自动生成代码,从而更 地理解模型和代码之间的对应关系,则高级建模和分析的优势将更为显著。面向对象的方法可以帮助开发人员利用架构图分析、理解系统,但其分析和设计方法的致命弱点是向需要向代码过渡。
大多数可用的面向对象的方法在分析和设计阶段都需要遵循冗杂的步骤,而且很难描述如何将系统的分析和设计模型转换为代码。使用面向对象开发系统的一个难题是,即使创建了良好的模型,大部分软件开发人员也很难将设计模型转换为可执行的代码。所以,如果能有支持开发人员,并且能够自动生成或帮助模型生成可执行代码的工具就完美了。
此外,在 UML 模型的架构图中检查代码便于用户识别包含代码的关键模块,为后续深入了解预存的系统业务和系统需求做好准备,进而帮助开发人员全面理解源代码。
目标
开发架构图的终极目标是从 UML 类图自动生成代码,不过,一般的目标是:
- 从面向对象的编程语言(如 C++)中找到一种从 UML 类图生成实现代码的方法。 2、执行以上方法,并开发一个从 UML 类图生成
- C++ 代码的自动生成系统。我们的代码生成方法和工具将有助于弥合设计和开发阶段之间的差距,在软件开发过程中为开发人员提供支持。
实施方法
从 UML 生成 C++(双向工程)
架构图中的双向工程是指从源代码生成模型并从模型生成源代码,且两个过程可以同步进行。我们可以利用这种双向功能来保持模型和源代码的更新,生成模型的最新文件。
以类图为例。大多数的类图概念与编程语言概念都有一对一的映射。类图可以用类和对象、组合和继承等表示支持性概念的编程语言直接实现。
UML 与 C++ 的类图转换
有许多工具可以在线执行此过程,具体可以查看相关文档,了解如何使用这些工具。下表中提供了一些工具的名称和相应的网站:
UML 源代码生成器工具及网站:
上述一些工具可以免费试用,试用时间可长达一个月或以上。另外还需要注意的是,版权所属是进行双向处理时需要考虑的一个重要问题。在某些情况下,软件工具中的一些限制可能会阻止这个过程的运行。同样不可忽视的是,用户需要在启动这个过程前解决版权问题。下面列举了一些导致双向处理的情况,代码或 UML 图:
- 已经进行了开发
- 获得使用许可的只是第三方库的一部分
- 企业使用的只是框架的一部分
- 开发人员每天都在开发。
在开发架构图时,我们使用了一个名为 Visual Paradigm 的软件工具,该工具目前支持以下编程语言的双向工程:
- Java
- C++
- C#
- NET
- PHP
- ODL
- ActionScript
- Perl
- Python
- Objective C
- Ruby
- IDL
- XML Schema
Visual Paradigm 架构图从 UML 图中用 ANSI 生成代码,并用上述的编程语言从 ANSI 代码中逆向生成 UML 类图。详情请访问 Visual Paradigm 网站,了解如何使用工具以及上表中列举的其他工具。
从 UML 图生成 C++ 代码
架构图系统由多个状态图组成,每个状态图都显示系统类图中包含的特定对象类的行为。我们用 UML 类图演示代码生成方法。
我们将使用类图 Animal 来展示 C++ 中生成的代码。
许多用于双向工程的面向对象工具大都会从类图中生成头文件。仅从类图生成代码将生成有限的骨架代码,由类属性和方法标记组成,为系统的对象结构提供框架代码。由于面向对象模型系统的动态行为可以使用状态图实现,生成的架构图代码不完整,所以无法执行。基于对象动态的部分模型,开发者可以用目标语言(例如 C++、Java 等)显式地编写对象行为和通信,使其成为可执行的。
上面 Animal 类的源代码如下所示
这是 Animal 类的头文件
生成的源代码包括 UML Animal 类图中每个属性和方法的类定义、变量和函数存根。
逆向工程
架构图的逆向工程是将现有的源代码导入到模型元素中,将源代码结构投映到UML视图中。方便用户检查遗留代码和代码库的功能,并重新利用,同时有利于保持UML模型与代码最新版本。逆向工程使用的语言,在此建议与 Visual Paradigm代码生成的语言保持一致。
现有的源代码结构映射到UML,例如C++类映射到UML类元素,变量定义为属性,方法建模为操作。借助适当的连接器,C++类之间的交互在UML模型中呈现为类图。
借助逆向工程,用户可以直观地检查遗留代码和代码库,取其精华并予以重新利用,同时随时检查 UML模型与代码的版本,保持更新状态。
在UML模型中检查代码,用户可以识别包含代码的关键模块,便于之后深入理解现有系统的业务和系统需求,使开发人员也得以更全面得了解源代码,为开发和部署软件新版本提供一致性空间。这个内联函数有助于编写下述特点的文件:
- 友好,清晰
- 最新的
- 易于查找和使用
- 全面、详细说明项目的各个方面。
下面将使用 Visual Paradigm 演示架构图的逆向工程;上文中从 Animal 类图生成的源代码将通过以下步骤进行逆向转换:
- 编辑 C++ 源代码头文件,包括附加方法 / 操作 sleep 和 sit,然后保存文件。
- 将保存的文件源代码导入到 Visual Paradigm 中,逆向转换为 UML 类图。
这是在源代码文件头中添加了 sleep 和 eat 方法
上图显示了使用Visual Paradigm生成的逆向UML Animal类图,源代码经过编辑,在现有方法中添加了sleep和eat两种方法。查看Visual Paradigm网站了解详情。
注意:带有关系的源代码类图也可以使用上述过程,通过Visual Paradigm生成。
有关UML类图之间关系的资源可以在线获取,了解如何在不同类图之间生成关系。
类图和状态图的结合
架构图的类图和状态图必须结合起来,才能完整地生成整个应用程序模型的代码。类图和状态图的结合,囊括了软件的静态和动态行为信息,拓宽了应用领域并覆盖了更广的使用范围。此外,两者的合作有助于处理包含多个状态图和复杂状态图的情况。
一个系统由多个状态图组成,每个状态图都显示系统类图中包含的特定对象类的行为。UML类图和状态图展示了代码生成的方法。
洗碗机系统
下面是一个洗碗机系统的架构图示例,可以展示我们的代码生成方法。下图首先是洗碗机系统的静态结构。洗碗机系统由Dishwasher(洗碗结构)、Jet (喷射结构)、Tank (水箱)和 Heater(加热器)四类组成。Dishwasher 类与 Jet、Tank 和 Heater 类有单向聚合关系。聚合表示整体/部分关系。Dishwasher 代表“整体”,Jet、Tank 和 Heater代表“部分”。Dishwasher 类有四个属性,即 cycle(周期)、rinseTime(漂洗时间)、washTime(洗涤时间)和dryTime(烘干时间),这些都是 int 类型属性。
下面的状态图展示了 Dishwasher 类的动态行为。它有两个顶层状态 PowerOff(关机)和PowerOn(开机)。在powerBut(电源触动)的事件发生后,这些状态相应被激活。实心圆转换到默认状态。一开始,Dishwasher 处于默认状态 PowerOff,在此状态下它接受powerBut 事件。洗碗结构对powerBut做出响应,从 PowerOff 状态切换到 PowerOn 状态。
PowerOn 状态是 Active(活跃)和 Mode(模式)两个并发区域的复合状态。一旦 PowerOn状态被激活,这些区域就会同时激活。每一个并发区域都有许多连续的子状态。在给定的时间内,只有一个连续的子状态可以转变为活跃状态。每当 PowerOn 状态变为活跃状态时,活跃区域中的 DoorClosed(门已关闭)和模式区域中的 Normal(正常)状态同时变为活跃状态,因为这是 PowerOn 复合状态中每个并发区域的默认状态。
在我们的方法中,应用程序类在单独的文件中生成。类图中所有类的实例都在应用程序类中被定义。对象实例只在应用程序类的构造方法中创建一次。其中还包含 main() 方法,这是应用程序的入口点。初始化代码也在 main() 方法中生成。包含了类图中各个类实现代码的独立文件,也在此生成。
如果没有附加到类的状态图,则生成的代码只包含类属性、与其他类关联的属性和方法标记。所附的状态图应说明类的行为信息。如果一个类有关联的状态图,那么生成的代码除了包含类属性、关联属性和方法标记之外,还应包含上下文类的行为实现。在同一个文件中,状态图的状态类代码也会相应生成。生成的代码是可执行的,并且包含应用程序模型中给定的所有信息。
当处于 PowerOn 状态时,在关闭或打开事件时,Dishwasher 将切换到活跃区域中的下一个连续状态。DoorClosed 的子状态是一个包含 Stop(停止), Filling(填充), Rinsing(漂洗), Washing(清洗), Draining(排水) 和 Drying(干燥)顺序的复合分层状态。当 DoorClosed 状态激活时,它的一个连续子状态也同时处于活跃状态。在 open(打开)事件中,洗碗机在活动区域切换到 DoorOpen(开门)状态。在 close(关闭)事件中,它切换到 DoorClosed 的历史状态,并调用 DoorClosed 的最后一个活跃子状态。架构图的状态图描述了对象的动态信息,现在的行为依赖于过去。实际上,状态图规定了对象在其生命周期中所经历状态的合理顺序。在历史记录状态的授权下,包含顺序子状态的复合状态可以记住转换之前处于活跃状态的最后一个子状态信息。
Tank类的动态行为在状态图表中也有详细说明,如下图所示。图中显示四个顶级状态 Empty(空置)、Fill(填充)、 Full(填满)和 Drain(排水)。每当发生 tankFill(水箱填充)、tankFull(填满)、tankDrain(排水)或 tankEmpty(空置)事件时,这些状态相应激活。最初,Tank 处于 Empty 默认状态,在此状态下它接受 tankFill 事件。Tank 相应地从 Empty 状态切换到 Fill 状态。
在架构图设计期间,Jet类图的动态行为在状态图中展示,如下图所示。Idle(闲置)和 Running(运行)是两个顶级状态。一开始,Jet 处于默认 Idle 状态,此时它可以接受 jetOn(启动喷射结构)事件。Jet 相应地从 Idle 状态切换到 Running 状态。Running 状态是一个包含 Spraying(洒水)和 Pulsing(冲水)两个连续的子状态的复合层级状态。在给定时间,只有一个连续的子状态变为活跃状态。每当运行状态变为活跃状态时,Spraying 状态同时变为活跃状态,正如它是复合运行状态的默认状态。在运行状态下,jetPulse 事件发生,Tank切换到下一个连续的 Pulsing 子状态。在 jetOff 事件中,Jet 会切换回 Idle 状态。
在架构图设计期间,Heater类图的动态行为在状态图中展示,如上所示。它有两个顶级状态,Off(关)和 On(开)。最初,加热器处于默认关闭状态,在此状态下它接受 HeaterOn 事件,加热器相应地从 Off 状态切换到 On。在HeaterOff 事件中,切换回 Off 状态。
代码生成工具概述
以下将通过上述洗碗机系统图来演示 C++ 的架构图 UML 代码生成方法。Visual Paradigm 工具系统分为三个主要模块,即类图模块、状态图模块和代码生成模块。以下是每个模块的简要说明。
类图模块
下面显示的dishwasher类头文件的源代码是 Visual Paradigm 生成的。如上面的dishwasher状态图所示,它包括与dishwasher类有关系的所有其他类的声明。其他类也有各自的头文件,同样由 Visual Paradigm 生成。
类图模块读取类图的规范,标识类图的各种组件,并将此存储在类表中。DSL(领域专用语言)中的节点表示类。所有关于类的信息,包括类的名称、属性和方法头都被存储。DSL 中的弧线表示类之间的关系。与关系相关的所有信息也存储在表中,然后类图模块处理类表并提取状态图 DSL 文件名,将此信息传递给状态图模块以处理关联的状态图。更多内容,请参考 DSL 维基百科。
状态图模块
架构图的状态图模块从类图模块接收状态图 DSL 文件名,读取对应输入的状态图 DSL 文件,将状态图信息记录到状态表中,从而将 DSL 格式的信息转换为表格形式。
状态图模块继而处理状态表,并从状态表中删除伪状态(初始、历史、分叉和联接等)的信息,并更新表格。
状态图模块将转换后的状态表返回给类图模块。状态表与相应类的其他信息一起存储在表中。
代码生成模块
在架构图的代码生成模块中,工具从类和状态表中获取信息,运用建议的代码生成方法,生成整个应用模型的 C++ 代码。
在我们的方法中,使用 main() 方法生成一个应用程序类,该方法充当整个系统的入口点。对于洗碗机系统,如图 3.1 所示,将生成主要应用类洗碗机。类的名称根据输入类图 DSL 文件中指定的项目名称派生。该类图中所有类的实例已进行初始化,应用程序对象是在 main() 方法中创建和初始化的。
类图中的类
架构图类图中的所有类都被转换成 C++ 代码。类图中的每个类,都将生成扩展名为(.cpp)的单独文件和扩展名为(.h)的头文件。生成的代码包含名称、属性和方法的所有类定义。类之间的关系被识别并转换成代码。为了实现类之间的关联,在相应的类中生成公共可用的引用属性。如果关联是双向的,则在两个类中生成引用属性;如果关联是单向的,则引用属性仅在源类中生成。
结论
为了实现我们开发架构图的目的,我们提出了一种面向对象的方法来将 UML 类图转换为实现代码。这种方法帮助整个应用程序模型生成简洁高效的可执行代码。生成的代码包含应用程序模型中所有类的静态结构代码和动态行为代码。
该方法已经在我们的示例中实现,使用 Visual Paradigm,自动将 UML 类图说明转换为 C++ 源代码。
我们的方法是面向对象的,在本次研究中,我们使用了 C++ 作为架构图的目标语言。不过,我们的方法也是通用的,因此可以应用于其他面向对象语言,生成相应的低级代码。代码生成机制必须根据目标语言进行定制,因为某些功能在不同的编程语言(面向对象)中的实现方式不同。
点击了解 Incredibuild 加速 C++ 的 CI 构建编译的解决方案,并获取试用 License!