惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Vulnerabilities – Threatpost
aimingoo的专栏
aimingoo的专栏
B
Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
GbyAI
GbyAI
阮一峰的网络日志
阮一峰的网络日志
Engineering at Meta
Engineering at Meta
IT之家
IT之家
V
Visual Studio Blog
The Cloudflare Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
A
About on SuperTechFans
博客园 - 聂微东
Blog — PlanetScale
Blog — PlanetScale
N
News and Events Feed by Topic
A
Arctic Wolf
WordPress大学
WordPress大学
小众软件
小众软件
C
CERT Recently Published Vulnerability Notes
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
D
Darknet – Hacking Tools, Hacker News & Cyber Security
F
Fortinet All Blogs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Y
Y Combinator Blog
T
Threat Research - Cisco Blogs
Latest news
Latest news
Simon Willison's Weblog
Simon Willison's Weblog
Cyberwarzone
Cyberwarzone
S
Schneier on Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
L
Lohrmann on Cybersecurity
Stack Overflow Blog
Stack Overflow Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Privacy International News Feed
J
Java Code Geeks
Spread Privacy
Spread Privacy
宝玉的分享
宝玉的分享
I
Intezer
L
LangChain Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
G
GRAHAM CLULEY
博客园 - 叶小钗
博客园 - 三生石上(FineUI控件)
The GitHub Blog
The GitHub Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
N
News and Events Feed by Topic
AWS News Blog
AWS News Blog
Attack and Defense Labs
Attack and Defense Labs
Security Archives - TechRepublic
Security Archives - TechRepublic
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO

卡瓦邦噶!

服务器高性能网络调优 | 卡瓦邦噶! 为何写作 | 卡瓦邦噶! 读《金阁寺》 | 卡瓦邦噶! 雨季又来 | 卡瓦邦噶! MTU Probe 引起的初始延迟 | 卡瓦邦噶! 3.5 秒的固定延迟问题 | 卡瓦邦噶! 学习网络的一点经验 | 卡瓦邦噶! ARP 问题诊断 | 卡瓦邦噶! 网络断断续续…… | 卡瓦邦噶! Piccolo P2P 镜像分发 | 卡瓦邦噶! 一起看电影 | 卡瓦邦噶! 《征服C指针》 | 卡瓦邦噶! 我的姥姥 | 卡瓦邦噶! WordPress网站迁移方案 Python的哲学 Python 3.5的新特性 学校不教的计算机课 垃圾回收(GC)的三种基本方式 在编程中体验纯粹的快乐 从《美丽新世界》谈自由 在快钱实习 迷人的嗓音和迷人的故事——《Sleepyhead》 Python 的十个自然语言处理工具 记一个愚蠢的bug 一年炉石传说的游戏体验 《以撒的结合:重生》网页版图鉴 分清 C++的指针、引用和数组 笑话三则 自由比皇帝更伟大——《悲惨世界》笔记 Git 10 周年访谈:Linus 讲述背后故事 用 0x3f3f3f3f 设定最大int值的优点 Joel给计算机系学生的建议 一个词法分析器的简单实现 怎样才算健康的生活方式 MacVim 配置攻略 学习培训课程的视频效果好吗? 语言的控制 可悲的大多数 CSS样式思维导图 2014年终总结 奥巴马成为首位写程序的美国总统 青岛老城区的下水道好在哪里? 做优秀 UI 的七个建议(第二部分) 选择爱情的骑士 Git简明教程 Java集合总览 读 《1984》 java问答:终极父类(六)——等待/唤醒和接口 Java 问答:终极父类(五)——toString() Hyperlapse快速视频背后的技术细节 平庸之恶 Java程序员须知的七个日志管理工具 苏州 逛书摊随想 关于考试作弊 你的工作不仅仅是编程 推荐在线学习Java的英文资源 关注女性命运——《千禧年三部曲》 用好你的幻灯片——《演说之禅》 使用ReentrantLock和Lambda表达式让同步更纯净 新手学编程,从哪里开始? 程序员都是工程师吗? 不要学习代码,要学会思考 Java 问答:终极父类(四)——hashCode() Java 问答:终极父类(三)——finalize()和 getClass() 程序员职业之路的选择 我是一名摄影家 写给何小树的城市指南 为什么一些语言会比别的快? Java的常见误区与细节 跟朋友在一起玩游戏 死神永生——读《三体》 五种类型的程序员 创业圣经——读《黑客与画家》 纪念加西亚·马尔克斯 Junit中处理异常的另一种方式:catch-exception Linux HotSopt虚拟机GC线程的CPU占用率 J2EE概念介绍 如何成为一名黑客 Java 问答:终极父类(二)——equals()方法 为什么我喜欢Java Java 问答:终极父类(一)——clone()方法 七个改变世界的Java项目 java中默认类型转换的小问题 传统与创新 欢迎来到互联网 莫言和马尔克斯——读《生死疲劳》 位运算的妙用 读《人为什么活着》 写博客教会我的事情 中国特色操作系统 2013年总结 《永不妥协》影评 恨不相逢未嫁时——《廊桥遗梦》影评 如何优雅地使用PPT 给明年依然年轻的我们 天才与柱子 黑客守则和黑客精神 Looking for Freedom——《被解救的姜戈》 简洁之道
Java8采用Martin Fowler的方法创建内部DSL
laixintao · 2014-04-18 · via 卡瓦邦噶!

我最近在读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 ListAdjacency Matrix。然而我发现,在没有将矩阵作为“一等公民”提供支持的语言(尤其是Java)实现是非常困难的,所以,我就尝试着在Java中写一个内部DSL来实现对表格的操作。

在他的书中,Martin Fowler强调需要保持语义模型不同于DSL,并且引入了一个中间表达式构造器来从DSL中产生语义模型。所以为了遵守上述内容,我通过写不同的DSL语法和表达式构造器实现了三种不同的DSLs形式,同时又使用了相同的语义模型。

理解语义模型

在这种情景下,语义模型就是包含了Edge实例数组的Graph类,每一个Edge对象又存放了从Vertex到Vertex的数据和一个weight。下面看一下代码吧。

Graph.java

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

import java.util.ArrayList;

import java.util.List;

import java.util.Set;

import java.util.TreeSet;

public class Graph {

  private List edges;

  private Set 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 getEdges() {

    return edges;

  }

  public Set 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

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

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

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

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

Java

1

2

3

4

5

6

7

8

9

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

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

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

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

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吧:

Test

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

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();

  }

}

输出结果是:

Result

Java

1

2

3

4

5

6

7

8

9

10

11

12

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中使用嵌套函数的风格会有所不同。在这中方法中,我将会在函数之中嵌套函数,来写我的语义模型,像下面这样:

Example

Java

1

2

3

4

Graph(

  edge(from("a"), to("b"), weight(12.3),

  edge(from("b"), to("c"), weight(10.5)

);

这种方法的好处是,它的层次天生juice不像访法链那样必须用另一种格式来写代码。而且,这种方法不需要在表达式构造器中提供中间变量,也就是说,当DSL被解析或者执行的时候,表达式构造器不需要持有Graph和Edge对象。语义模型和上文中谈到的相同。

以下是DSL的表达式构造器。

NestedGraphBuilder.java

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

//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

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

//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

Java

1

2

3

4

5

6

7

8

9

10

//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。

Test2

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

//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))

      )

    );

  }

}

输出如下:

Output

Java

1

2

3

4

5

6

7

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是什么样子的吧:

Final_DSL

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

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

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

//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

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

//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。

Creat_DSL

Java

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

//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);

  }

}

输出如下:

Output_3

Java

1

2

3

4

5

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表达式

英文原文由Mohamed Sanaulla发表在javacodegeeks赖信涛翻译,Importnew校对。