D.1 Scala语言的DSL相关特性
Scala是一种在JVM上运行的,兼有面向对象和函数式编程范式的语言。由于Scala与Java共享对象模型(以及很多其他方面),所以两者的互操作性十分优秀。Scala的语法简练优美,具有类型推断能力,还因为综合了OO和函数式两种范式,因而拥有十分丰富的抽象设计机制。
表D-1 Scala语言特性汇总
基于类的OOP | |
---|---|
Scala是一种面向对象的语言。我们可以定义含有实例变量和方法的类。除了类之外,Scala还有很多其他的表示类型的语言构造适合用于抽象设计,而且每一种都有其自身的特点和应用范围。本篇附录会尽量介绍它们 按照Scala DSL设计的一般习惯,无论类还是别的能够把若干相关功能组织成一体的语言构造,都常常被用来建模领域实体 类定义语法的详情请参阅第D.2节文献[1] |
类的定义可以加上参数。在上面的代码片段中, no 和name 前面的val 意味着它们都是不可变的,不允许再次赋值
balance 是一个方法。它没有任何参数,返回值类型为Int
|
Case类 | |
只要类定义前面加上case字样,编译器就会生成一种附带诸多福利的抽象。这种抽象Scala称为case类。对于一个case类,编译器会自动施行以下动作
- 转换构造器的参数为不可变的val 。不希望被转换的参数可明确标明为var
- 为该类实现equals 、hashCode 和toString 方法
- 允许使用简写形式来调用构造器。初始化该类的一个对象时,不需要写出new 关键字。编译器会产生一个伴随对象,含有用来构造对象的apply() 方法和提取构造参数的unapply() 方法
Case类的另一个作用是参与模式匹配。它最常也最习惯被用来实现一些参与代数运算的数据类型
由于case类天然地具有各方面的不可变特征,我们在设计DSL的时候常常用它来实现不可变的值对象
|
按照这段case类定义,我们可以用 val p = Var("p") 的写法来实例化一个对象,不需要明确写出new 关键字
|
Trait | |
Trait也是Scala表达抽象的一种手段。它和Java的接口(interface)类似,允许将具体的实现留给具体类去完成。但trait有一点和接口不一样,它可以选择性地给一部分方法提供默认的实现 trait是Scala实现mixin的机制,也提供了一条正确实现多重继承的途径 |
Trait善于设计开放的、不绑定到特定实现的、可重用的抽象。在上面的trait定义里, view_trail 方法保持开放的状态,留待混入该trait的抽象去实现
|
高阶函数和闭包 | |
在Scala语言里,函数与其他的可作为值传递的类型完全平等,我们可以把一个函数作为参数传递给另一个函数。函数也可以返回另一个函数。函数和值的等同性赋予了Scala实施函数式编程的强大能力 高阶函数可以精简代码,且便于我们在DSL中表达正确的动词语义 闭包能让我们少写一些类和对象,多用函数式的程序构造 |
第一个例子的 exists 方法通过它的参数获得一个函数来处理字符串中的每一个字符。如果用Java语言来实现同样的功能将会烦琐很多
第二个例子演示了Scala的函数字面量(function literal)语法
|
模式匹配 | |
Scala也拥有函数式编程语言必备的模式匹配功能。我们可以匹配任意的表达式,匹配过程会在找到第一个匹配项时成功结束,即以顺序为优先(first-match-wins)的匹配规则 我们在Scala语言下施展函数式的编程手法,case类和模式匹配的组合可以说是杀手锏 Case类可以用来实现可扩展的Visitor模式 我们从第6章的例子可以体会到,模式匹配是清晰表达业务规则的利器 |
这段代码使用Scala的语法简单复制了Java语言的switch/case语句。其实模式匹配还有很多别的用法 Java语言下的 instanceOf 检查,按Scala的习惯一般会写成上面的样子
Case类可直接用于模式匹配。Case类默认实现的属性提取器(extractor)在匹配过程中起到重要作用 |
起模块作用的object 语法 | |
Scala的object 语法定义了一种可执行的模块。我们使用类和trait定义好的各种抽象,通过object 语法把它们组合为一个具体的对象实体。Java语言中与object 语法最为接近的对应物是静态内部类
|
RuleComponent 是用声明中所列抽象组合而成的一个单件对象
|
隐含参数 | |
我们可以将函数的最后一个参数声明为implicit ,从而达到调用时省略该参数的目的。编译器会在包围该函数的作用域内查找匹配的参数
|
如果无法在作用域内找到匹配的参数,编译器将提示编译错误 |
隐式类型转换 | |
隐式类型转换可以帮助我们在不对现有库进行任何改动的前提下,实现对该库的扩展。其原理类似于Ruby的猴子补丁,但多了词法作用域的约束 Martin Odersky把这种手法称为Pimp My Library模式(参见第D.2节文献[2]) 编译器会在需要的时候自动调用隐式转换函数。我们可以利用隐式类型转换让旧的抽象适应新的API |
这里定义的带 implicit 关键字的enrichArray 函数,是一个从Array 类型到RichArray 类型的转换函数
|
偏函数 | |
偏函数只对其参数所有可能取值中的一部分有定义。Scala的偏函数形式上呈现为含有一连串case语句的模式匹配代码块 习惯上我们常常把Scala actor的消息接收循环定义成偏函数 |
onlyTrue 是一个限定了参数取值范围的PartialFunction 。它只针对Boolean 值为true 的情况定义。PartialFunction trait含有isDefinedAt 方法,会在参数取值满足偏函数定义域的时候返回true 。以下是两个例子:
|
泛型和类型参数 | |
Scala允许在类和方法的声明中指定类型参数。而且我们还可以对这些类型显示指定一些抽象必须满足的约束条件。对约束条件的检查将由编译器自动执行,我们无需自行编写任何验证代码 我们设计DSL时,可以善加利用Scala语言的特点,尽量把DSL的约束条件纳入其类型系统 |
按照这里的类定义,不满足约束条件的账户将无法生成相应的 Trade 实例
|