Scala实现DSL的框架案例

简介: Scala实现DSL的框架案例

在skinny.validator框架中,提供如下的一种DSL调用方式来验证Map的值:

def validationRules = Validator(
  param("name" -> name) is notEmpty,
  param("dataSourceId" -> dataSourceId) is checkAll(intValue, intMinValue(0)),
  param("sql" -> sql) is notEmpty,
  param("fields" -> fields) is checkAll(notNull, notEmpty)
)

param()方法返回的类型为ParamDefinitionis之类的方法看起来应该是ParamDefinition的DSL方法。不过在实现中,却提供了一个私有类来定义这些DSL方法,然后再定义一个隐式转换将私有类转换为ParamDefinition

/**
 * Param definition which has #is and #are DSL methods.
 *
 * @param paramDef param definition
 */
private[validator] class ParamDefinitionWithIsDSL(paramDef: ParamDefinition) {
  def is(validations: ValidationRule): NewValidation = NewValidation(paramDef, validations)
  def are(validations: ValidationRule): NewValidation = NewValidation(paramDef, validations)
}
/**
 * Converts ParamDefinition to ParamDefinitionWithIsDSL implicitly.
 *
 * @param paramDef param definition
 * @return with dsl
 */
implicit def convertParamDefinitionToParamDefinitionWithIs(paramDef: ParamDefinition): ParamDefinitionWithIsDSL  = {
  new ParamDefinitionWithIsDSL(paramDef)
}

通过这种方式既可以将对应的DSL方法进行归类,又可以很好地支持DSL的编码形式,算是DSL的一种最佳实践。

isare等DSL方法接收的参数类型为ValidationRule(因为Scala允许以空格而非括号形式去调用,从而能够以自然语言的方式来表达)。ValidationRule的定义比较特殊,它本身是一个trait,但却继承自一个函数:

trait ValidationRule extends ((KeyValueParamDefinition) => ValidationState) with ErrorLike {}

看起来比较奇怪,其实不过是一种语法糖,因为在Scala中,函数就是一个定义了apply()方法的接口。这里之所以直接继承一个函数,倒也不是卖弄语法,而是因为作者压根就不想为这个函数定义一个类型。这也是FP的好处,因为函数自身就已经表达了一种最高层抽象,为什么还要多此一举去定义一个类型呢?对于一些不支持函数为一等公民的语言,才会这样行无奈之举。例如我需要抽象这么一个函数:

() => Unit

在Java语言中,由于不支持函数作为参数或返回值,那就只有无奈地寻找到一个所谓Action的概念,定义接口:

public interface Action {
    void execute();
}

ValidationRule的定义实则是一个模板方法模式,该类的apply()方法就是模板方法(template method),而isValid()方法就是基本方法(primitive method),交给实现ValidationRule的类去具体实现:

trait ValidationRule extends ((KeyValueParamDefinition) => ValidationState) with ErrorLike {
  /**
   * Validation itself.
   *
   * @param value any value
   * @return valid if true
   */
  def isValid(value: Any): Boolean
  /**
   * Applies this validation rule to parameter.
   *
   * @param paramDef param definition
   * @return validation
   */
  def apply(paramDef: KeyValueParamDefinition): ValidationState = {
    if (isValid(paramDef.value)) ValidationSuccess(paramDef)
    else ValidationFailure(paramDef, Seq(Error(this.name, this.messageParams)))
  }
}

skinny.validator框架提供了诸多内建的验证规则,并以case class或object(如果为单例)形式定义:

object notNull extends ValidationRule {
  def name = "notNull"
  def isValid(v: Any) = v != null
}
// param("x" -> "y") is required
object required extends required(true)
case class required(trim: Boolean = true) extends ValidationRule {
  def name = "required"
  def isValid(v: Any) = !isEmpty(v) && {
    if (trim) v.toString.trim.length > 0
    else v.toString.length > 0
  }
}
// param("x" -> "y") is notEmpty
// param("list" -> Seq(1,2,3)) is notEmpty
object notEmpty extends notEmpty(true)
case class notEmpty(trim: Boolean = true) extends ValidationRule {
  def name = "notEmpty"
  def isValid(v: Any) = !isEmpty(v) && {
    toHasSize(v).map(x => x.size > 0).getOrElse {
      if (trim) v.toString.trim.length > 0
      else v.toString.length > 0
    }
  }
  protected def isEmpty(v: Any): Boolean = v == null || v == ""
}

诸如notNullrequirednotEmpty之类的object或case class都继承自ValidationRule,但它们本质上都是函数,接受的参数为KeyValueParamDefinition类型,返回ValidationState。在这些方法调用的背后,隐含地使用到了Scala的特殊语法:

param("x" -> "y") is notEmpty

param("x" -> "y")ParamDefinition类型,然后利用隐式转换的方式,使其拥有了is方法。因此前面的调用相当于:

param("x" -> "y").is(notEmpty)

该方法接收的参数表面上是ValidationRule类型,实则是一个函数,函数参数类型为KeyValueParamDefinition,它是ParamDefinition的实现类。param方法在返回KeyValueParamDefinition时,对应的实现是将传入的key/value键值对传递给了KeyValueParamDefinition

def param(kv: (String, Any)): KeyValueParamDefinition = KeyValueParamDefinition(kv._1, kv._2)

因此,就相当于把(x, y)这个键值对传递给了notEmpty()方法。由于scala的方法默认是strict方法,所以在将notEmpty函数传递给is方法时,就会去执行notEmptyapply()方法,内部就是调用它的isValid()方法,进而调用isEmpty(v: Any)方法,并将传递进来key/value键值对的value(即这里的y值)进行非空判断。

本文链接: http://zhangyi.xyz/framework-example-of-scala-dsl/

相关文章
|
Scala
169 Scala Actor 案例二
169 Scala Actor 案例二
39 0
|
Java Scala
168 Scala Actor 案例一
168 Scala Actor 案例一
46 0
|
1月前
|
分布式计算 大数据 Java
大数据-87 Spark 集群 案例学习 Spark Scala 案例 手写计算圆周率、计算共同好友
大数据-87 Spark 集群 案例学习 Spark Scala 案例 手写计算圆周率、计算共同好友
49 5
|
1月前
|
分布式计算 关系型数据库 MySQL
大数据-88 Spark 集群 案例学习 Spark Scala 案例 SuperWordCount 计算结果数据写入MySQL
大数据-88 Spark 集群 案例学习 Spark Scala 案例 SuperWordCount 计算结果数据写入MySQL
48 3
|
1月前
|
消息中间件 存储 druid
大数据-156 Apache Druid 案例实战 Scala Kafka 订单统计
大数据-156 Apache Druid 案例实战 Scala Kafka 订单统计
37 3
|
6月前
|
Scala 容器
Scala学习--day04--集合、常用方法、案例实操 - WordCount TopN、不同省份的商品点击排行
Scala学习--day04--集合、常用方法、案例实操 - WordCount TopN、不同省份的商品点击排行
106 2
|
Scala
170 Scala Actor 案例四
170 Scala Actor 案例四
32 0
|
前端开发 Scala
170 Scala Actor 案例三
170 Scala Actor 案例三
28 0
|
消息中间件 运维 数据可视化
【Kafka】基于Windows环境的Kafka有关环境(scala+zookeeper+kafka+可视化工具)搭建、以及使用.NET环境开发的案例代码与演示
基于Windows系统下的Kafka环境搭建;以及使用.NET 6环境进行开发简单的生产者与消费者的演示。
555 0
【Kafka】基于Windows环境的Kafka有关环境(scala+zookeeper+kafka+可视化工具)搭建、以及使用.NET环境开发的案例代码与演示
|
Web App开发 Java 测试技术
在Scala中构建Web API的4大框架
Scala是一种强大的语言,很快就成为许多开发人员的最爱。然而,语言只是一个起点 - 并非每个函数都将由语言核心覆盖。Scala还创建了一些厉害的框架。接下来看看Scala的4个强大框架以及其优点和缺点。
5994 0