正如我在介绍Flow Design的上一篇文章中所宣布的那样,我想展示一个Java的具体实现。 该示例的代码可以在GitHub上找到 。
我将关注Ralf Westphal的博客文章“ IODA Architecture for Example ”,该文章针对以下场景进行了流程设计和实现:
生成一个应用程序,将用户输入的罗马数字转换为阿拉伯数字,反之亦然。
阅读他的文章以获取更多详细信息可能也很有价值。 但是,我将重复他的步骤(有时会缩短一些步骤),以使您了解整个情况。 几乎所有图纸都来自Ralf的博客。
需求分析
分析方案说明后,必须有一个用户可以键入罗马或阿拉伯数字的应用程序。由于不需要特别说明,因此简单的控制台程序可以满足此要求。
可能看起来像:
> convertroman XIV
14
> convertroman 42
XLII
>
关于验证没有任何说明。 因此,验证将是非常基本的验证。
解决方案设计
当然,我们这里不做面向对象的设计。 我们还有另一个意图。 流程设计是目标。 因此,我们从顶层抽象开始。 从需求分析中我们所知道的就是,“世界”是由用户触发的:用户启动程序。 在流程设计中,可以表示为:
这是最高级别的抽象。
流程设计
现在,“零件”(又称功能单元)开始发挥作用。 下一个设计步骤是弄清楚,哪些处理步骤在最高抽象级别上构建根部分。 功能单元“罗马转换”成为集成层次结构的基础。
现在重复此过程,直到确实需要将功能单元划分为更多,更小的处理步骤,并且它们变得清晰且易于实现。
对于上图中的功能单元,两个外部的功能单元是相当了解的。 从命令行读取输入并在其上显示字符结果很容易实现。 但是,功能单元“转换”不是很清楚。 需要进一步完善。
直到现在,“转换”还是一项操作。 但是现在它已经开放,成为集成单元,可以连接更精细的操作。 验证在这里也应该有它的位置。 让我们放在一起:
“从罗马转换”和“转换为罗马”这两个功能单元均包含此实现的核心域逻辑。 这里需要更多的思考来进行进一步分解。 原始文章将其示例为“从罗马转换”部分,因为与“转换为罗马”相比这并不明显。 我在这里重复一遍:
- 罗马数字的每个罗马数字都转换为它的值。 结果是一个整数列表。 例如,
XIV”-> [10,1,5] 。
如果列表中的较小值先于较小的值出现,则取反较小的值。 例如[10,1,5]-> [10,-1,5]。 在原始文章中称为“应用减法规则”。
- 累积这些值以计算它们的总和。 例如[10,-1,5]-> 14
将这三个步骤联系在一起,就形成了以下“域过程”:
现在,无需进一步完善。 显然,必须执行特定的操作。 仅错误情况已被排除在外。 他们来了:
我想,现在很清楚了,为什么将此设计方法称为Flow Design 。 这是关于设计应用程序的数据流。 它由连接起来并由数据类型标记的操作和集成单元组成,它们从一个单元流向另一个单元。 因此,现在只剩下流动数据的设计了。 但是首先, 流程设计的整个结果以红色标记。
资料设计
IODA体系结构的[I]集成和[O]操作实体已设置。 要使用的[A] PI确实不值得一提; 显然是Java语言框架。 但是,仍然需要设计[D] ata。
数据类型已经在流程设计图中,并且可能会被读出。 在这里,它们被提取:
从功能单元到数据类型的连接的末尾有一个实心圆。 这就是在Flow Design中标记依赖项的方式。 因此,依赖性在此并不超出范围。 但是,它们仅限于自然的情况
- 功能单元取决于它们接收或发送的数据类型; 和
- 功能单元可能取决于保持状态的单元(例如数据库访问权限)
我们的设计中没有状态。 但是数据类型在那里。 对于那些人来说,面向对象的设计如果不是琐碎的话,将是一个合适的工具。 但是,遵循KISS原则 ,我们不会为域名概念罗马数字和阿拉伯数字引入特定类型。 第一个只是字符串,第二个是整数。 如果也可以使用普通的Java类型来设计特定类型,那么就没有任何价值。 值列表很容易用整数数组或Java列表表示。
无论如何,此处做出的关于数据类型的设计决定不会改变基本架构。 操作单元处理数据。 因此,如果数据设计发生变化,则它们主要受此影响。
类设计
当我们要用Java实现设置设计时,我们需要考虑类,因为类是面向对象语言的主要代码构建块之一。 代码构建块在模块的设计维度上形成层次结构,从方法或功能作为最小的代码模块开始,然后是类,包,库,组件和微服务。
与面向对象设计的区别很明显在这里出现:我们不首先考虑类。 无需提出“候选课程”。 我们首先考虑行为/功能。 这些类是直接从流程设计中看到的模式派生的。 气泡分为以下几类:
并非所有这些类都是显而易见的。 拉尔夫·韦斯特法尔(Ralf Westphal)解释他的分组决定如下:
- 提供者 :在任何软件中,使用API都是一个“硬”方面。 应将其隔离到特定模块中。 通常,每个API都应使用自己的类进行封装-但是在这种情况下,只有很少的一个类可以完成。
- FromRomanConversion和ToRomanConversion :进行实际转换是其自身的一个方面。 由于存在两个转换方向,因此每个模块似乎都是按顺序排列的。
- RomanConversion :此类属于应用程序的粗粒度域。 它包含与转换无关的“辅助功能”。
- 主体 :此类表示整体功能-但无需与用户交互。 就像程序应该执行的内部API一样。
- 头部 :头部负责触发身体行为。 它将人体与用户输入和输出的信息整合在一起。
类图遵循IODA架构; 集成,操作和API是可识别的。 由于场景非常简单,因此没有数据类型被设计,因此仅缺少数据:
接口和静态方法
在课堂设计上还有更多决定权。 特别是,我们需要知道哪些类将被实例化,以及哪一个仅成为静态方法的代码容器。
由于不涉及任何状态, 因此不需要实例化RomanConversion , FromRomanConversion和ToRomanConversion 。 它们包含的方法可以是静态的。
对于提供者,它看起来有所不同。 由于将要实现API访问,因此在这里应用了接口隔离原理 。 因此, Head也需要实例化。
另外,将“ 主体”描述为允许隔离测试头的接口。
包装设计
代码模块层次结构中的下一层是Java包。 这是非常特定于Java的,原始设计文章没有它,因为C#的名称空间的语言概念并不像Java包那样强大的代码结构化工具。 Java的程序包定义了类和方法的可见性,因此功能更强大。
从结构上讲,我的包装设计与原始文章的库设计非常相似。
- 转换包含业务领域的所有类别
- 提供者具有处理API的所有类
- 头是顶级集成商的所在地
- 主体包含负责“后端”集成的类; 在这里,行为被创建
但是,我介绍了另一个分组级别。 我将所有包含操作类的包放在子包操作下 ,所有要集成到子包集成中的类 。 最后,我还有打包合同 。 它包含所有设计的接口。
所有软件包的名称空间前缀de.grammarcraft.javaflow.examples.convertroman都具有唯一性。
图书馆设计
将代码组织到库中是构建模块层次结构的下一个工具。 在Java中,通常将其映射到JAR归档文件。
在设计过程中,这是非常值得考虑的,因为它通常是最小的部署工件。 在此级别上,可以将功能扩展和错误修复应用于已经推出的解决方案,以通过替换库来确保该解决方案的可扩展性。 特别是在分布式环境中,库是形成分布式模块格局的代码的切入点。
由于此解决方案非常简单,因此将只有一个库,一个JAR文件。
元件设计
Ralf Westphal将术语“组件”定义为一个或多个库的集合,由特定于平台的合同描述。 可以将组件想像成具有门面作为入口点的JAR文件束,或者像OSGi束。
将代码结构化为组件,其边界由诸如界面或外观类之类的契约确定,这是专业软件开发的前提。 至少,它对并行开发的团队有很大帮助。 每个团队成员都在其组件合同的标题下开发一个组件,同时应用其他可能尚未准备好的外国组件合同。
实作
实现是直接完成的。 这是Eclipse中产生的项目结构:
您可以在Github上查看源项目。
让我们仔细看看如何在Java中实现拆分流。 现在,我们可以利用lambda的新Java语言功能,这使实现变得非常简洁。 由于Java不允许设置内部DSL,因此至少要尽可能简洁(目前在Java中)。
查看上面的代码,您可能需要花一点时间来了解发生了什么。 但是,这只是一个简单的实现。
让我们采取最高流量。
这将导致下面已经显示的源代码:
只需从上到下以及从左到右读取函数调用即可。 缩进是流程中的不同路径。
实际上,图中的每个气泡都转换为一个方法调用。 如果气泡有一个输出,该函数将简单地返回它。 如果气泡有一个以上的输出,则将它们实现为延续–在Java中以数据类型作为通用参数将其定义为Consumer的函数对象。 因此,实现变得类型安全。
这些延续使代码难以阅读。 但仅在开始时。 这很不寻常,没有错。 您已经受了很长时间的培训,可以从右到左和由内而外读取嵌套的函数调用; 但是一旦习惯了继续学习,您就会意识到阅读代码又变得多么容易。
拉尔夫·韦斯特法尔
结论
我已经证明,基于流程设计的Java IODA架构实现就像Ralf Westphal在他的C#文章中所展示的一样容易。 实际上,这并不奇怪,因为使用lambda语言概念时,Java的委托人和lambda表示法与C#具有相似的强大功能。
但是,阅读Java或C#实现中的流程仍然有些困难。 如果可以直接在集成单元中指定流程,如下所示:
您可能不会更接近故意流程设计图。 那会大大提高可读性,不是吗?
如果您使用允许内部DSL实现的编程语言,那么即使在JVM上,也可以使用这种表示法。 我将在下一篇文章中展示如何在Scala和Xtend中完成此操作。 我将实现转换罗马数字的相同示例,以使其具有更好的可比性。
如果有人已经想看看,它已经在以下GitHub项目中实现:
敬请关注。
这篇文章最初发表在Beyond Coding上 。