用Java和Java 8创建内部DSL,采用Martin Fowler的方法

目前,我正在阅读Martin Fowler撰写的有关DSL- 特定于域的语言的精彩书籍。 围绕DSL的嗡嗡声,围绕轻松支持DSL创建的语言,以及DSL的使用使我好奇地了解和了解DSL的这一概念。 到目前为止,这本书的使用经验令人印象深刻。

马丁·福勒(Martin Fowler)在他的书中提到的DSL定义:

特定领域的语言(名词):一种表达能力有限的计算机编程语言,专注于特定领域。


DSL并不是什么新鲜事物,它已经存在了很长时间。 人们使用XML作为DSL的一种形式。 使用XML作为DSL很容易,因为我们有XSD来验证DSL,有解析器来解析DSL,还有XSLT来将DSL转换成其他语言。 而且大多数语言为解析XML和填充其域模型对象提供了很好的支持。 诸如Ruby,Groovy等语言的出现增加了DSL的采用。 例如,使用Ruby编写的Web框架Rails广泛使用DSL。

Martin Fowler在他的书中将DSL分为内部,外部和语言工作台。 当我阅读了内部DSL概念时,我使用Java作为宿主语言在自己的简单DSL上进行了一些尝试。 内部DSL位于宿主语言中,并受宿主语言的语法功能约束。 使用Java作为宿主语言并不能给我真正清晰的DSL,但是我努力使它更接近可以舒适地理解DSL的形式。

我试图创建用于创建图的DSL。 据我所知,输入和表示图形的不同方式是: 邻接表和邻接矩阵 。 我一直发现这很难使用,尤其是在Java等语言中,这些语言没有作为一等公民的矩阵。 在这里,我试图创建一个内部DSL,以用Java填充图形。

马丁·福勒(Martin Fowler)在他的书中强调,需要保持语义模型与DSL不同,并引入一种中间表达构建器,该构建器可以从DSL填充语义模型。 通过保持这种状态,我能够通过编写不同的DSL语法和表达式构建器并同时使用相同的语义模型来实现3种不同形式的DSL。

了解语义模型

在这种情况下,语义模型是Graph类,它包含Edge实例的列表,每个Edge包含从VertexVertex以及权重。 让我们看一下相同的代码:

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;}@Overridepublic 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;}
}

顶点

public class Vertex implements Comparable<Vertex> {private String label;public Vertex(String label) {this.label = label.toUpperCase();}@Overridepublic int compareTo(Vertex o) {return (this.getLabel().compareTo(o.getLabel()));}public String getLabel() {return label;}public void setLabel(String label) {this.label = label;}
}

现在我们已经有了语义模型,让我们构建DLS。 您应该注意到,我不会更改语义模型。 语义模型不应该改变,这不是一个硬性规定,相反,可以通过添加用于获取数据或修改数据的新API来发展语义模型。 但是将语义模型紧密绑定到DSL并不是一个好的方法。 将它们分开可以帮助独立测试语义模型和DSL。

Martin Fowler陈述了创建内部DSL的不同方法:

  • 方法链接
  • 功能顺序
  • 嵌套函数
  • Lambda表达式/闭包

除了功能序列,我已经在这篇文章中说明了3。 但是我在使用Closures / Lambda表达式时使用了Functional Sequence方法。

通过方法链接实现内部DSL

我设想我的DSL是这样的:

Graph().edge().from("a").to("b").weight(12.3).edge().from("b").to("c").weight(10.5)

为了实现这种DSL的创建,我们必须编写一个表达式生成器,该表达式生成器允许弹出语义模型并提供能够创建DSL的流畅接口 。

我创建了2个表达式构建器-一个用于构建完整的Graph,另一个用于构建单个边。 在构建Graph / Edge的过程中,这些表达式构建器始终保存中间的Graph / Edge对象。 通过在这些表达式构建器中创建静态方法,然后使用静态导入在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

您是否发现此方法比“邻接表/邻接矩阵”方法更易于阅读和理解? 该方法链接类似于我之前写的Train Wreck模式 。

嵌套功能的内部DSL

在嵌套函数方法中,DSL的样式不同。 在这种方法中,我会将函数嵌套在函数中以填充语义模型。 就像是:

Graph(edge(from("a"), to("b"), weight(12.3),edge(from("b"), to("c"), weight(10.5)
);

这种方法的优点是它的层次结构自然不同于方法链接,在方法链中,我不得不以不同的方式设置代码格式。 而且这种方法不会在“表达式”构建器中保持任何中间状态,即,在解析/执行DSL时,表达式构建器不保存GraphEdge对象。 语义模型与此处讨论的相同。

让我们看一下此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使用了不同的软件包。 因此,请根据您使用的软件包名称更新导入。

//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表达式支持。

使用Lambda表达式的内部DSL

如果您想知道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 expression / closure / block,然后通过执行lambda expression / closure / block填充语义模型。 此实现中的表达式生成器以通过方法链接在DSL实现中相同的方式维护GraphEdge对象的中间状态。

让我们看一下表达式构建器:

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种不同的方法。

总结一下:

    • 在这篇文章中,我谈到了DSL, 即 Martin Fowler在《 领域特定语言 》一书中提到的DSL。
    • 为实现内部DSL的三种方法分别提供了一种实现:
        • 方法链接
        • 嵌套函数
        • 具有功能序列的Lambda表达式

参考: 使用Java,Java 8创建内部DSL 8-在Experiences Unlimited博客中采用了JCG合作伙伴 Mohamed Sanaulla的Martin Fowler的方法 。

翻译自: https://www.javacodegeeks.com/2013/06/creating-internal-dsls-in-java-java-8-adopting-martin-fowlers-approach.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/368030.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【VSCode】Windows下VSCode便携式c/c++环境

http://blog.csdn.net/c_duoduo/article/details/52083494 Ver 1.1 完整版&#xff08;修复mingw环境变量错误&#xff09;下载&#xff1a; http://pan.baidu.com/s/1jIwZcUU 转载于:https://www.cnblogs.com/shiningrise/p/8401644.html

【校招面试 之 网络】第3题 HTTP请求行、请求头、请求体详解

1、HTTP请求报文解剖 HTTP请求报文由3部分组成&#xff08;请求行请求头请求体&#xff09;&#xff1a; 下面是一个实际的请求报文&#xff1a; ①是请求方法&#xff0c;GET和POST是最常见的HTTP方法&#xff0c;除此以外还包括DELETE、HEAD、OPTIONS、PUT、TRACE。不过&a…

奇妙的 CSS shapes(CSS图形)

CSS 发展到今天已经越来越强大了。其语法的日新月异&#xff0c;让很多以前完成不了的事情&#xff0c;现在可以非常轻松的做到。今天就向大家介绍几个比较新的强大的 CSS 功能&#xff1a; clip-pathshape-outside shape 的意思是图形&#xff0c;CSS shapes 也就是 CSS 图形…

c++ map 多线程同时更新值 崩溃_深入理解并发安全的 sync.Map

golang中内置了map关键字&#xff0c;但是它是非线程安全的。从go 1.9开始&#xff0c;标准库加入了sync.Map&#xff0c;提供用于并发安全的map。普通map的并发问题map的并发读写代码func main() { m : make(map[int]int) go func() { for { _ m[1] // 读 } }(…

CS Academy Round #65 Count Arrays (DP)

题目链接 Count Arrays 题意 给定$n$和$m$个区间。若一个长度为$n$的$01$序列满足对于每一个给定的区间中至少有一个位置是$0$&#xff0c; 那么这个$01$序列满足条件。求有多少满足条件的$01$序列。 设$f[i]$为考虑到第$i$位的时候&#xff0c;有多少满足条件的$01$序列。 则…

c语言中栈堆,C语言中堆和栈的区别

二.堆和栈的区别1.申请方式(1)栈(satck):由系统自动分配。例如&#xff0c;声明在函数中一个局部变量int b;系统自动在栈中为b开辟空间。(2)堆(heap):需程序员自己申请(调用malloc,realloc,calloc),并指明大小&#xff0c;并由程序员进行释放。容易产生memory leak.eg:char p;…

webview部分安卓机中文乱码

在开发过程中需要加载html片断&#xff0c;发现安卓机上是乱码&#xff0c;这可郁闷了&#xff0c;官方文档也没有这方面的介绍&#xff0c;还好度娘还好的&#xff0c;记录一下解决方案&#xff1a; 其实很简单&#xff0c;只在source对象中添一个属性baseUrl为空就可以解决&a…

MongoDB存储基础教程

一、MongoDB简介 1. mangodb是一种基于分布式、文件存储的非关系型数据库   2. C写的&#xff0c;性能高   3. 为web应用提供可扩展的高性能数据存储解决方案   4. 所支持的格式是json格式 二、MongoDB三元素&和关系型数据库的区别 三元素&#xff1a;数据库 集合(类…

Tomcat的带有守护程序和关闭挂钩的正常关闭

我的最后两个博客讨论了长时间轮询和Spring的DeferredResult技术&#xff0c;并且为了展示这些概念&#xff0c;我将我的Producer Consumer项目中的代码添加到了Web应用程序中。 尽管该代码演示了博客所提出的观点&#xff0c;但其逻辑上确实包含大量漏洞。 除了在实际的应用程…

java 递归从子节点删除父节点_LeetCode450. 删除二叉搜索树中的节点

删除一个二叉搜索树中的节点&#xff0c;需要进行情况的分类讨论&#xff0c;看一下将这个节点删除之后是否需要对二叉搜索树进行调整&#xff08;为了保持树的连接和维持二叉搜索树的性质&#xff09;。&#xff08;1&#xff09;如果删除的是一个叶子节点&#xff0c;那问题不…

1. [文件]- 文件类型,文件open模式

1.文件类型&#xff1a;文本文件和二进制文件 硬盘中的文件保存为01010101格式&#xff0c;一般读取文件是把文件从硬盘中读取到内存中。 文本文件需要进行格式转换才能读取出来。二进制文件一般用于传输二进制文件&#xff1a;视频图片 2.文件打开模式 几种不同的读取和遍历文…

c语言鼠标移动响应,CSS鼠标响应事件经过、移动、点击示例介绍

几种鼠标触发CSS事件。说明&#xff1a;onMouseDown 按下鼠标时触发onMouseOver 鼠标经过时触发onMouseUp 按下鼠标松开鼠标时触发onMouseOut 鼠标移出时触发onMouseMove 鼠标移动时触CSS 鼠标响应事件.Off{ background-color: #00FF66; padding:100px;}.up{background-color: …

node安装node-sass失败,配置淘宝源

node-sass 安装失败的原因是因为无法下载 .node 文件&#xff0c;解决办法就很简单了&#xff0c;就是我们把文件下载路径复制一份到浏览器里&#xff0c;然后使用浏览器下载文件就可以了。 具体方法 1.从node命令行中复制 .node文件下载链接并在浏览器打开下载文件https:/…

django 日志配置

django 日志配置 LOGGING { version: 1, disable_existing_loggers: False, formatters: { standard: { format: %(levelname)s %(asctime)s %(pathname)s %(filename)s %(funcName)s %(lineno)d: %(message)s }, # INFO 2016-09-03 16:25:20,067 /home/ubuntu/mysite/views.p…

带有Atomikos示例的Tomcat中的Spring JTA多个资源事务

在本教程中&#xff0c;我们将向您展示如何使用Atomikos Transaction Manager在Tomcat服务器中实现JTA多个资源事务。 Atomicos事务管理器为分布式事务提供支持。 这些是多阶段事务&#xff0c;通常使用多个数据库&#xff0c;必须以协调的方式提交。 分布式事务由XA standard描…

mac vs 返回上一步_mac电脑打不开应用程序的解决方法

mac电脑跟windows电脑一样&#xff0c;经常会出现打不开应用程序的情况&#xff0c;并且提示“因为它来自身份不明的开发者”&#xff0c;也不知道哪里出现问题&#xff1f;由于MAC系统与windows界面不一样&#xff0c;很多小编不懂怎么操作&#xff1f;为此&#xff0c;小编给…

C#DES加密

记录一下 DES加密 public static string DESEncrypt(string Data, string key){return DESEncrypt(Data, key, "utf-8");}/// <summary>/// DES加密算法/// </summary>/// <param name"Data">加密明文</param>/// <param name&…

c语言链表有没有哨兵的区别,链表中的哨兵(sentinel)

哨兵节点广泛应用于树和链表中&#xff0c;如伪头、伪尾、标记等&#xff0c;它们是纯功能的&#xff0c;通常不保存任何数据&#xff0c;其主要目的是使链表标准化&#xff0c;如使链表永不为空、永不无头、简化插入和删除。问题&#xff1a;删除链表中等于给定值val的所有节点…

jquery 获取标签名(tagName)

如果是为了取到tagName后再进行判断&#xff0c;那直接用下面的代码会更方便&#xff1a; $(element).is(input) 如果是要取到标签用作到别的地方&#xff0c;可以使用一下代码&#xff1a; $(element)[0].tagName或&#xff1a;$(element).get(0).tagName 转载请注明&#xff…

番石榴条纹类的细粒度并发

这篇文章将介绍如何使用Guava中的Striped类来实现更细粒度的并发。 ConcurrentHashMap使用条带化锁定方法来增加并发性&#xff0c;并且Striped类通过赋予我们具有条带化Locks &#xff0c; ReadWriteLocks和Semaphores的能力来扩展此主体。 当访问对象或数据结构&#xff08;例…