本文由 ImportNew - 赖 信涛 翻译自 javacodegeeks。欢迎加入翻译小组。转载请见文末要求。
我最近在读Martin Flower写的一本非常棒的关于DSLs(Domain Specific Languages)的书。围绕DSLs及语言的内容使得我们可以很方便地创建DSLs,DSLs的使用让我对DSLs的概念更加好奇,这本书让人印象深刻。在Martin Fowler一书的开始是这样定义DSLs的:
Domain-specific language (noun): 一种专注于某一领域,仅针对部分表达方式的计算机编程语言。(译者注:求专不求全。)
DSL不是什么新鲜玩意,很久以前,人们就将XML作为一种DSL的一种形式来使用了。将XML作为DSL来使用非常便捷,因为我们可用来检查DSL的XSD,有解释DSL的解释器,还有能将DSL转换成其他语言的XSLT。并且,多数的语言都对解释XML和获取该语言领域中模型对象的内容提供了很好的支持。像是Ruby,Groovy等等这些语言的出现使得DSL被更广泛的接受。比如Rails,一个使用Ruby写的Web框架,广泛地采用了DSLs。
Martin Fowler在他的书中将DSLs分为三类:内部的DSL,外部的DSL,和语言工作平台的DSL。当我读到内部DSL概念时,使用Java作为宿主语言,用我自己简单的DSL小试牛刀了一下。内部DSLs驻于宿主语言中,并且遵守宿主语言的语法。尽管使用Java作为宿主语言没有让我非常清楚的了解DSL,但却有效地让我以一种合适的方式来了解DSL。
我打算写一个能产生图表的DSL。在Java中有两种接收输入并产生表格的方法:Adjacency List和Adjacency Matrix。然而我发现,在没有将矩阵作为“一等公民”提供支持的语言(尤其是Java)实现是非常困难的,所以,我就尝试着在Java中写一个内部DSL来实现对表格的操作。
在他的书中,Martin Fowler强调需要保持语义模型不同于DSL,并且引入了一个中间表达式构造器来从DSL中产生语义模型。所以为了遵守上述内容,我通过写不同的DSL语法和表达式构造器实现了三种不同的DSLs形式,同时又使用了相同的语义模型。
理解语义模型
在这种情景下,语义模型就是包含了Edge实例数组的Graph类,每一个Edge对象又存放了从Vertex到Vertex的数据和一个weight。下面看一下代码吧。
Graph.java
import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.TreeSet; public class Graph { private List<Edge> edges; private Set<Vertex> vertices; public Graph() { edges = new ArrayList<>(); vertices = new TreeSet<>(); } public void addEdge(Edge edge){ getEdges().add(edge); } public void addVertice(Vertex v){ getVertices().add(v); } public List<Edge> getEdges() { return edges; } public Set<Vertex> getVertices() { return vertices; } public static void printGraph(Graph g){ System.out.println("Vertices..."); for (Vertex v : g.getVertices()) { System.out.print(v.getLabel() + " "); } System.out.println(""); System.out.println("Edges..."); for (Edge e : g.getEdges()) { System.out.println(e); } } }
Edge.java
public class Edge { private Vertex fromVertex; private Vertex toVertex; private Double weight; public Edge() { } public Edge(Vertex fromVertex, Vertex toVertex, Double weight) { this.fromVertex = fromVertex; this.toVertex = toVertex; this.weight = weight; } @Override public String toString() { return fromVertex.getLabel()+" to "+ toVertex.getLabel()+" with weight "+ getWeight(); } public Vertex getFromVertex() { return fromVertex; } public void setFromVertex(Vertex fromVertex) { this.fromVertex = fromVertex; } public Vertex getToVertex() { return toVertex; } public void setToVertex(Vertex toVertex) { this.toVertex = toVertex; } public Double getWeight() { return weight; } public void setWeight(Double weight) { this.weight = weight; } }
Vertex.java
public class Vertex implements Comparable<Vertex> { private String label; public Vertex(String label) { this.label = label.toUpperCase(); } @Override public int compareTo(Vertex o) { return (this.getLabel().compareTo(o.getLabel())); } public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } }
好了,既然语义模型已经到位,我们就开始做DSLs吧。你应该已经注意到了吧,我不打算修改我的语义模型。没有硬性规定语义模型不可以修改,相反,随着新的可以读取和修改数据的API的加入,语义模型可以不断被完善。但是将语义模型和DSL绑定的太死并不可取。保持它们相对分离可以独立地测试语义模型和DSL。
Martin Fowler陈述的创建内部DSLs 的方法是:
- 方法链
- 功能序列
- 嵌套函数
- Lambda表达式/闭包
除了功能序列之外,我在这篇文章中图文并茂的介绍了其中的3种。但是我也在Lambda表达式/闭包中使用了功能序列的方法。
用方法链创建的DSL
我幻想出一种像这样子的DSL:
Graph() .edge() .from("a") .to("b") .weight(12.3) .edge() .from("b") .to("c") .weight(10.5)
为了能够实现这样的DSL,我们要写一个表达式构造器,能够产生语义模型,提供能产生DSL的流接口。
我写了来年改革表达式构造器——一个用来完成图表,另一个用来建立每一个的边界。这些图表和边界建立的时候,这些表达式构造器就持有中间的图表和边界对象。以上的语法可以在表达式构造器的静态方法中实现,然后用静态导入,就可以在DSL中使用了。
Graph()方法开始生成Graph模型,同时edge()和一系列方法,也就是from(),to(),weight()产生Edge模型,edge()同时也产生Graph模型。
让我们来看一下GraphBuilder(生成Graph模型的表达式构造器)吧:
GraphBuilder.java
public class GraphBuilder { private Graph graph; public GraphBuilder() { graph = new Graph(); } //Start the Graph DSL with this method. public static GraphBuilder Graph(){ return new GraphBuilder(); } //Start the edge building with this method. public EdgeBuilder edge(){ EdgeBuilder builder = new EdgeBuilder(this); getGraph().addEdge(builder.edge); return builder; } public Graph getGraph() { return graph; } public void printGraph(){ Graph.printGraph(graph); } }
接下来是EdgeBuilder(生成Edge模型的表达式构造器):
EdgeBuilder.java
public class EdgeBuilder { Edge edge; //Keep a back reference to the Graph Builder. GraphBuilder gBuilder; public EdgeBuilder(GraphBuilder gBuilder) { this.gBuilder = gBuilder; edge = new Edge(); } public EdgeBuilder from(String lbl){ Vertex v = new Vertex(lbl); edge.setFromVertex(v); gBuilder.getGraph().addVertice(v); return this; } public EdgeBuilder to(String lbl){ Vertex v = new Vertex(lbl); edge.setToVertex(v); gBuilder.getGraph().addVertice(v); return this; } public GraphBuilder weight(Double d){ edge.setWeight(d); return gBuilder; } }
让我们来试一下这个DSL吧:
public class GraphDslSample { public static void main(String[] args) { Graph() .edge() .from("a") .to("b") .weight(40.0) .edge() .from("b") .to("c") .weight(20.0) .edge() .from("d") .to("e") .weight(50.5) .printGraph(); Graph() .edge() .from("w") .to("y") .weight(23.0) .edge() .from("d") .to("e") .weight(34.5) .edge() .from("e") .to("y") .weight(50.5) .printGraph(); } }
输出结果是:
Vertices... A B C D E Edges... A to B with weight 40.0 B to C with weight 20.0 D to E with weight 50.5 Vertices... D E W Y Edges... W to Y with weight 23.0 D to E with weight 34.5 E to Y with weight 50.5
这个方法不是比Adjacency List或者Adjacency Matrix方法更具有可读性吗?这个方法链和我之前写的Train Wreck pattern很像。
用嵌套函数创建的DSL
在DSL中使用嵌套函数的风格会有所不同。在这中方法中,我将会在函数之中嵌套函数,来写我的语义模型,向下面这样:
Graph( edge(from("a"), to("b"), weight(12.3), edge(from("b"), to("c"), weight(10.5) );
这种方法的好处是,它的层次天生juice不像访法链那样必须用另一种格式来写代码。而且,这种方法不需要在表达式构造器中提供中间变量,也就是说,当DSL被解析或者执行的时候,表达式构造器不需要持有Graph和Edge对象。语义模型和上文中谈到的相同。
以下是DSL的表达式构造器。
NestedGraphBuilder.java
//Populates the Graph model. public class NestedGraphBuilder { public static Graph Graph(Edge... edges){ Graph g = new Graph(); for(Edge e : edges){ g.addEdge(e); g.addVertice(e.getFromVertex()); g.addVertice(e.getToVertex()); } return g; } }
NestedEdgeBuilder.java
//Populates the Edge model. public class NestedEdgeBuilder { public static Edge edge(Vertex from, Vertex to, Double weight){ return new Edge(from, to, weight); } public static Double weight(Double value){ return value; } }
NestedVertexBuilder.java
//Populates the Vertex model. public class NestedVertexBuilder { public static Vertex from(String lbl){ return new Vertex(lbl); } public static Vertex to(String lbl){ return new Vertex(lbl); } }
如果你想严格遵守规则,让所有表达式构造器定义在静态上,我们可以使用静态导入的方法创建一个DSL。
注意:表达式构造器、语义模型和DSL分别在不同的包中,所以请根据您的包名更新一下import。
//Update this according to the package name of your builder import static nestedfunction.NestedEdgeBuilder.*; import static nestedfunction.NestedGraphBuilder.*; import static nestedfunction.NestedVertexBuilder.*; /** * * @author msanaull */ public class NestedGraphDsl { public static void main(String[] args) { Graph.printGraph( Graph( edge(from("a"), to("b"), weight(23.4)), edge(from("b"), to("c"), weight(56.7)), edge(from("d"), to("e"), weight(10.4)), edge(from("e"), to("a"), weight(45.9)) ) ); } }
输出如下:
Vertices... A B C D E Edges... A to B with weight 23.4 B to C with weight 56.7 D to E with weight 10.4 E to A with weight 45.9
有趣的部分来了:我们如何利用DSL支持的lambda表达式呢?
在内部DSL中使用lambda表达式
如果你还不知道lambda表达式在Java中能做什么的话,请先阅读本文有关语义模型的部分。
在这个例子中我们会继续使用上面描述的语义模型。这个DSL使用了支持lambda表达式的功能序列。让我们看一下最后的DSL是什么样子的吧:
Graph(g -> { g.edge( e -> { e.from("a"); e.to("b"); e.weight(12.3); }); g.edge( e -> { e.from("b"); e.to("c"); e.weight(10.5); }); } )
是的,我知道上面的这个DSL重载了操作符,但是我们不得不这样做。如果你不喜欢,那么可以选择另一种语言。
在这个方法中,我们的表达式构造器应该接受lambda表达式/closure/block,然后在lambda表达式/closure/block的基础上创建语义模型。这样实现的语义模型保留了Graph和Edge对象这样的中间值,就想我们在方法链中做的那样。
看一下我们的表达式构造器吧:
GraphBuilder.java
//Populates the Graph model. public class GraphBuilder { Graph g; public GraphBuilder() { g = new Graph(); } public static Graph Graph(Consumer<GraphBuilder> gConsumer){ GraphBuilder gBuilder = new GraphBuilder(); gConsumer.accept(gBuilder); return gBuilder.g; } public void edge(Consumer<EdgeBuilder> eConsumer){ EdgeBuilder eBuilder = new EdgeBuilder(); eConsumer.accept(eBuilder); Edge e = eBuilder.edge(); g.addEdge(e); g.addVertice(e.getFromVertex()); g.addVertice(e.getToVertex()); } }
EdgeBuilder.java
//Populates the Edge model. public class EdgeBuilder { private Edge e; public EdgeBuilder() { e = new Edge(); } public Edge edge(){ return e; } public void from(String lbl){ e.setFromVertex(new Vertex(lbl)); } public void to(String lbl){ e.setToVertex(new Vertex(lbl)); } public void weight(Double w){ e.setWeight(w); } }
在GraphBuilder中有两行高亮的代码。这个地方用了Java 8引入的功能接口,Consumer.
下面我们使用上面的表达式构造器来创建我们的DSL。
//Update the package names with the ones you have given import graph.Graph; import static builder.GraphBuilder.*; public class LambdaDslDemo { public static void main(String[] args) { Graph g1 = Graph( g -> { g.edge( e -> { e.from("a"); e.to("b"); e.weight(12.4); }); g.edge( e -> { e.from("c"); e.to("d"); e.weight(13.4); }); }); Graph.printGraph(g1); } }
输出如下:
Vertices... A B C D Edges... A to B with weight 12.4 C to D with weight 13.4
当我要结束这篇长长的文章的时候,我知道你可能想让我把它分成3个部分每部分介绍一种DSL的实现来发布。我坚持发布在一篇文章中是因为这样可以比较一下这3种方法。
概括:
- 这篇文章中我们讨论了Martin Fowler的Domain Specific Languages一书中涉及的DSL,内部DSL。
- 分别介绍了三种背部DSL的实现方法
- 方法链
- 嵌套函数
- 支持功能序列的Lambda表达式
原文链接: javacodegeeks 翻译: ImportNew.com - 赖 信涛
译文链接: http://www.importnew.com/10907.html
[ 转载请保留原文出处、译者和译文链接。]
相关推荐
《重构——改善既有代码设计》是软件工程领域的一部经典著作,作者Martin Fowler,该书与《设计模式》被并称为软件工程的双雄。《重构》一书的主旨在于向读者展示重构的过程与方法,即通过一系列小的、有步骤的改变...
马丁·福勒(Martin Fowler)在其著作《Domain Specific Languages》中深入探讨了这一主题,该书由Addison-Wesley Professional出版社于2010年9月24日出版。本书提供了关于如何设计、实现和使用DSLs的全面指南,并...
此外,重构不仅适用于Java语言,Martin Fowler在书中也强调,重构的理念适用于任何面向对象的编程语言。因此,无论是哪种编程语言,都可以通过重构来改善代码质量。 Erich Gamma,作为《设计模式》的作者之一,也为...
Martin Fowler名箸 Patterns of Enterprise Application Architec
本书首次出版于1996年,是分析模式理论的奠基之作,作者马丁·福勒(Martin Fowler)是国际知名的软件开发方法专家。在本书中,Fowler探讨了在软件开发过程中发现的一系列通用的问题解决方案,并将其定义为“分析...
《重构 -改善既有代码的设计》是由美国著名软件开发专家Martin Fowler所著,由熊节翻译的一本经典IT著作。这本书深入探讨了重构这一关键的软件工程实践,旨在帮助开发者提升既有代码的质量和可维护性。重构是软件...
### 分析模式-Martin Fowler #### 一、引言与概念模型 《分析模式》是IT界大师Martin Fowler的一部经典著作。本书旨在为复杂的业务分析领域提供一系列实用且易于理解的设计模式,帮助读者更好地理解和解决实际问题...
重构,改善既有代码的设计(中文版,Martin Fowler 著).part03
Martin Fowler和《重构:改善既有代码的设计》(中文版)另几位作者清楚揭示了重构过程,他们为面向对象软件开发所做的贡献,难以衡量。《重构:改善既有代码的设计》(中文版)解释重构的原理(principles)和最佳实践...
UML2初学好书-(“UML Distilled”:Martin Fowler)-中英文合辑 EN::(UML Distilled) Third Edition(2003)--CHM格式 , zhTW:(UML 精华第三版) /物件模型语言标准简介---PDF格式 [物件模型语言标准简介初学好书-UML-2...
Martin Fowler的文章探讨了Java社区近期关注的一个热点话题:轻量级容器及其背后的模式。这些容器的主要目标是帮助开发者将来自不同项目的组件组装成一个内聚的应用程序。在这些容器背后的核心模式被称为“控制反转...
在软件工程领域,设计是构建高质量系统的关键环节,而Martin Fowler的观点引发了业界对于传统设计方法与演进式设计之间关系的深思。 首先,让我们理解什么是演进式设计。演进式设计是一种灵活且适应变化的设计方法...
Martin Fowler的Inversion of Control Containers and the Dependency Injection pattern。中文版。 本文中,作者深入探索IOC模式的工作原理,给它一个更能描述其特点的名字——“依赖注入”(Dependency Injection...
这本书第一章讲得实例在现实中经常碰到,至于后续章节需要慢慢品味,除非你只想做个平庸的程序员!
[作者信息] Martin Fowler [出版机构] Addison-Wesley Professional [出版日期] 1996年10月19日 [图书页数] 384页 [图书语言] 英语 [图书格式] PDF格式 ======================================================= ...
1.3 分解并重组statement()8 1.4 运用多态取代与价格相关的条件逻辑34 1.5 结语52 第2章 重构原则53 2.1 何谓重构53 2.2 为何重构55 2.3 何时重构57 2.4 怎么对经理说60 2.5 重构的难题62 2.6 重构与设计66 2.7 重构...
Martin Fowler和《重构:改善既有代码的设计》(中文版)另几位作者清楚揭示了重构过程,他们为面向对象软件开发所做的贡献,难以衡量。《重构:改善既有代码的设计》(中文版)解释重构的原理(principles)和最佳实践...