《领域特定语言》一第3章 实现DSL 3.1DSL处理之架构

简介: 本节书摘来自华章出版社《领域特定语言》一书中的第3章,第3.1节,作者 (英)Martin Fowler,更多章节内容可以访问云栖社区“华章计算机”公众号查看

第3章 实现DSL

至此,对于什么是DSL,以及为何要用DSL,我们已经透彻理解。如果要开始构建DSL,那么现在该深入研究所用的技术了。虽然构建内部DSL和外部DSL所用的技术有所不同,但它们还是有一些共通之处的。本章主要关注内部DSL和外部DSL的一些共通问题,而下一章再讨论各自具体的问题。本章先不谈语言工作台,留待后续探讨。

3.1DSL处理之架构

关于DSL实现的大体结构(见图3-1),也就是所谓的DSL系统架构─可能是我们要谈论的最重要的内容之一。

image

迄今为止,你应该已经厌倦了听我说了无数次的“DSL是模型上面薄薄的一层结构”。这里所说的“模型”,称为“语义模型”(第11章)模式。这个模式背后的概念是:所有重要的语义行为都可以在模型中捕获,而DSL的任务就是通过解析来填充模型。所以,根据我的理解,语义模型在DSL中扮演着核心角色─事实上,全书都会首先假设,我们在使用语 义模型。(当然,在本节的最后,在我们有足够的上下文去讨论它们的章节,我会谈谈语义模型的替代方案。)
因为我是一个面向对象偏执狂,所以我理解的语义模型首先是一个对象模型。我喜欢既有数据又有行为的多功能对象模型 (Rich Object Model),但语义模型不必拘泥于此,它也可以仅仅是一种数据结构。虽然我坚持我们应该使用一个合适的对象,但有数据模型来表示语义模型总好过没有。所以,在本书的讨论中,尽管我会将其假定为有合适行为的对象,但事实上,数据结构也是可以用来描述语义模型的选择之一。
很多系统都使用Domain Model [Fowler PoEAA]捕获系统的核心行为,而且通常DSL就是负责组装Domain Model的重要部分,但我依然坚持把Domain Model和语义模型区分开。DSL的语义模型通常是一个系统的Domain Model的子集,因为并不是Domain Model的所有部分都适合用DSL处理。另外,DSL的任务不仅仅是填充Domain Model,它还用于 其他任务。
语义模型完全就是一个普通的对象模型,可以像操作其他所有对象模型一样操作它。在前面关于状态(state)的例子中,用状态模型的命令–查询API组装一个状态机,然后运行它,获取状态对象的行为。从某种意义上说,它与DSL是相互独立的,但在现实中,它们又是焦不离孟,孟不离焦。
(如果有编译器背景知识,你可能会将Domain Model等同于抽象语法树。简而言之,二者不同。我们会在3.2节中再进行分析。)
分离语义模型和DSL有几个好处。首先,我们可以暂时不纠结于DSL的语法和解析器,而专注于当前领域的语义。如果用上DSL,这就说明我们所表达的东西已经非常复杂,复杂到了要拥有自己的模型来表示。
特别是,这样一来,就可以直接创建语义模型中的对象,操作它们进行测试。比如,可以创建一堆状态和迁移(transition),测试事件(event)和命令(command)是否运行良好,而无须处理解析。如果状态机的执行存在问题,问题必然在模型中,无须理解解析如何运作。
对于显式的语义模型而言,可以用多种DSL组装它。比如,从简单的内部DSL开始,稍后再给出一个更加易读的外部DSL版本。考虑到既有的脚本和用户,也许我们希望保留既有的内部DSL,同时支持内部和外部DSL。因为二者都可以解析成相同的语义模型,所以这么做并不困难。它还可以帮助我们避免语言之间的重复。
更为重要的是,拥有一个独立的语义模型,模型和语言就可以独立演化。如果要改变模型,无须修改DSL就可以探索做法,模型能够工作之后,给DSL添加必要的语言构造即可。同样,如果需要尝试不同的DSL语法,只要验证它们是否可以创建相同的模型对象即可。比较它们组装语义模型方式的不同,就可以知道两种语法之间的区别。
从许多方面来看,语义模型和DSL语法的分离实际上反映了领域对象及其表现的分离,这种做法在企业级软件设计中是很常见的。有时,我甚至把DSL视为另一种类型的用户界面。
对比DSL和表现,也有一些局限性。DSL和语义模型始终还是关联的。如果要给DSL增加新构造,就需要保证语义模型中有支持,这也就意味着二者需要同时修改才可以。但是另一方面,二者的分离可以将语义问题和解析问题分开思考,从而简化了很多事情。
内部DSL和外部DSL的不同就在于解析这一步,既包括解析的目标,也包括解析的方式。两种风格的DSL都会产生同样的语义模型,正如前文暗示的一样,没有理由不使用单独的语义模型。事实上,这也正是我在编写状态机的例子中所使用的策略:多种DSL,一个语义模型。
当使用外部DSL时,DSL脚本、解析器和语义模型之间有条清晰的界限。DSL脚本由一种独立的语言编写,解析器读取这些脚本,然后组装语义模型。而使用内部DSL时,它们之间更容易混杂在一起。我提倡使用一个显式的对象层次(“表达式生成器”(第32章)),后者提供必要的连贯接口作为一种语言,然后,运行DSL脚本,调用表达式生成器的方法组装语义模型。所以对内部DSL而言,DSL脚本的处理是由宿主语言解析器和表达式生成器共同完成的。
这让我们想到另一个有趣的问题:在内部DSL中使用“解析”这个词可能令人感觉古怪,我承认,我对此也并不感到十分舒服。然而,我发现,对内部DSL和外部DSL的处理进行类比思考其实是非常有用的。传统的解析方式是,获得文本流,将文本组织为解析树,处理解析树产生有用的输出。而解析内部DSL,输入就是一系列方法调用,将它们组织到一个层次结构中(通常是隐式地在栈中组织),以便后续产生有用的输出。
这里使用“解析”这个词还涉及另一个因素,在很多场景下它并不直接处理文本。在内部DSL中,宿主语言解析器处理文本,而DSL处理器处理的更多是语言构造。但是XML DSL也有同样的情况:XML解析器把文本翻译成XML元素,而DSL处理器基于这个基础进行工作。
是时候重温一下内部DSL和外部DSL的区别了。之前采用的区分标准─是否以应用的基础语言编写─通常而言是对的,但不绝对。一个极端的例子是,应用以Java编写,而DSL以JRuby编写。在这种情况下,我依然会将其归为内部DSL,而我们用到的技术都是来自本书关于内部DSL的章节。
二者之间的真正区别在于,内部DSL使用一种可执行的语言编写,然后,通过在那种语言中执行DSL进行解析。无论 是JRuby还是XML,DSL都是嵌入载体语法中,但不同之处在于,JRuby代码是要执行的,而XML数据结构只是读取而已。当然,大多数时候,内部DSL都是以应用的主要语言所实现的,因此,通常来说,这个定义还是有用的。
一旦有了语义模型,就需要让模型按照期望工作。在状态机的例子中,这个任务就是控制安全系统。有两种方法可以达到这个目的。最简单的,通常也是最好的办法就是: 执行语义模型,因为语义模型本身就是代码,可以根据需要执行它。
另一个方法是代码生成。代码生成是指,生成单独编译和运行的代码。在一些圈子中,代码生成被视为DSL的根本。我看到一些讨论,认为任何和DSL相关的工作都需要通过生成代码来实现,代码生成不可或缺。在个别情况下,我甚至发现一些人在谈论或编写“解析器生成器”(第23章)时,总是不可避免地要谈论代码生成。DSL同代码生成并没有本质关联,大多数情况下,执行语义模型是最好的选择。
代码生成在一种情况下最为有用,就是运行模型同解析DSL不在同一个地方的时候。一个很恰当的例子就是,代码执行在一个语言选择有限的环境中,比如在硬件有限制,或者在关系数据库中。你肯定不希望在一个烤箱或者在SQL中运行解析器,因此用一种更加合适的语言生成解析器和语义模型,然后再用它生成C或者SQL。另一个类似的场景是,解析器依赖于生产环境用不到的程序库。这种情况很普遍,比如,为了DSL使用一个复杂工具,这也就是语言工作台倾向于使用代码生成的原因。
对于这些情况,在解析环境下,有一个“无须生成代码也可以运行”的语义模型还是很有用的。运行语义模型,就可以在不了解代码生成如何运作的情况下,也能够体验DSL的执行。如果不生成代码,就能够测试解析和语义,就可以更快测试以及隔离问题。基于语义模型进行验证,可以在生成代码之前就捕获一些错误。
即便在一个能够很好地解释语义模型的环境里,关于代码生成,也存在一个争论,许多开发人员发现,富语义模型中的某些逻辑非常难以理解。从语义模型生成的代码会让一切变得更加明显,不再如魔法般隐晦。在一个缺乏高水平开发人员的团队中,这点至关重要。
就代码生成而言,务必记住一点,它只是DSL蓝图中的一个可选项。用到时,它极为关键,但大多数情况用不到。在我看来,DSL如同雪地靴,在雪地中远足,当然要有一双保暖防水的靴子,而在炎炎夏日,却完全用不着。
使用语义模型生成代码还有一个好处,它解耦了代码生成器和解析器。纵然对解析过程一无所知,我也能够写出一个代码生成器,并对其进行独立测试。单凭这一点,语义模型就已值回票价了。另外,它也更容易支持生成多种目标代码,如果需要的话。

相关文章
|
6月前
|
存储 Go 数据处理
Go 语言整洁架构实践
Go 语言整洁架构实践
50 0
|
2月前
|
Go API 开发者
探索Go语言在微服务架构中的应用
本文旨在探讨Go语言(又称Golang)在构建高性能、可扩展微服务架构中的关键作用与优势。不同于传统摘要的概述方式,我们将通过一个实际案例,揭示Go语言如何解决微服务中的并发处理和网络通信问题,以及它在微服务生态系统中的位置。通过对比其他语言,本文强调Go语言的简洁语法、出色的并发支持和高效的性能表现,为开发者提供了一种有效的工具,以应对当今微服务架构的复杂需求。
|
4月前
|
消息中间件 运维 Java
java语言B/S架构云HIS医院信息系统源码【springboot】
java语言B/S架构云HIS医院信息系统源码【springboot】
75 0
|
6月前
|
运维 安全 Java
基层医院云HIS系统全套源码 java语言 SaaS模式 B/S架构
一款满足基层医院各类业务需要的云HIS系统。该系统能帮助基层医院完成日常各类业务,提供病患挂号支持、病患问诊、电子病历、开药发药、会员管理、统计查询、医生站和护士站等一系列常规功能,还能与公卫、PACS等各类外部系统融合,实现多层机构之间的融合管理。
|
11月前
|
存储 Java 云计算
java语言云计算技术B/S架构的医院信息系统源码HIS
基于云计算技术的B/S架构的HIS系统,为医疗机构提供标准化的、信息化的、可共享的医疗信息管理系统,实现医患事务管理和临床诊疗管理等标准医疗管理信息系统的功能。系统利用云计算平台的技术优势,建立统一的健康档案和电子存储平台,有效实现医疗数据共享与交换,解决数据重复采集及信息孤岛等问题,为实现区域人口健康平台和医联体、医共体奠定了基础。
|
11月前
|
消息中间件 缓存 运维
基于Java语言开发B/S架构实现的云HIS
云HIS功能模块 (1)医疗业务:门诊医生站、门诊护士站、住院医生站、住院护士站 电子病历、病历质控、患者列表、诊疗记录 (2)电子病历:合并预览、普通病厉、自定义模板、数据同步 病历打印、辅助输入、页面布局、导出PDF
154 0
|
Linux C语言 Android开发
【C 语言】CPU 架构 ( CPU 指令集类型 | CPU 指令类型 | CPU 架构 )
【C 语言】CPU 架构 ( CPU 指令集类型 | CPU 指令类型 | CPU 架构 )
315 0
|
应用服务中间件 开发框架 关系型数据库