Scala 中的 DSL 使用案例 类

DSL in scala using case classes

我的用例有 class 类似于

 case class Address(name:String,pincode:String){
    override def toString =name +"=" +pincode
  }

 case class Department(name:String){
   override def toString =name
 }

 case class emp(address:Address,department:Department)

我想创建一个类似 below.Can 的 DSL,任何人都可以分享有关如何创建 DSL 的链接以及实现以下目标的任何建议。

 emp.withAddress("abc","12222").withDepartment("HR")

更新: 实际用例class可能会有更多接近20的字段。我想避免代码冗余

我真的认为您不需要 Scala 中的构建器模式。只需给您的案例 class 合理的默认值并使用 copy 方法。

即:

employee.copy(address = Address("abc","12222"), 
              department = Department("HR"))

您还可以使用不可变构建器:

case class EmployeeBuilder(address:Address = Address("", ""),department:Department = Department("")) {
  def build = emp(address, department)
  def withAddress(address: Address) = copy(address = address)
  def withDepartment(department: Department) = copy(department = department)
}

object EmployeeBuilder {
  def withAddress(address: Address) = EmployeeBuilder().copy(address = address)
  def withDepartment(department: Department) = EmployeeBuilder().copy(department = department)
}

你可以

object emp {
  def builder = new Builder(None, None)

  case class Builder(address: Option[Address], department: Option[Department]) {
    def withDepartment(name:String) = {
      val dept = Department(name)
      this.copy(department = Some(dept))
    }
    def withAddress(name:String, pincode:String) = {
      val addr = Address(name, pincode)
      this.copy(address = Some(addr))
    }
    def build = (address, department) match {
      case (Some(a), Some(d)) => new emp(a, d)
      case (None, _) => throw new IllegalStateException("Address not provided")
      case _ => throw new IllegalStateException("Department not provided")
    }
  }
}

并将其用作 emp.builder.withAddress("abc","12222").withDepartment("HR").build().

您不需要可选字段,copy,或构建器模式(确切地说),如果您愿意让构建始终以特定顺序获取参数:

case class emp(address:Address,department:Department, id: Long)

object emp {
  def withAddress(name: String, pincode: String): WithDepartment =
    new WithDepartment(Address(name, pincode))

  final class WithDepartment(private val address: Address)
    extends AnyVal {
    def withDepartment(name: String): WithId =
      new WithId(address, Department(name))
  }

  final class WithId(address: Address, department: Department) {
    def withId(id: Long): emp = emp(address, department, id)
  }
}

emp.withAddress("abc","12222").withDepartment("HR").withId(1)

这里的想法是每个 emp 参数都有自己的 class,它提供了一种方法让你进入下一个 class,直到最后一个给你一个 emp 对象。这就像柯里化,但在类型级别。如您所见,我添加了一个额外参数,作为如何将模式扩展到前两个参数之外的示例。

这种方法的好处在于,即使您正在构建中途,您目前拥有的类型也会引导您进行下一步。因此,如果到目前为止您有一个 WithDepartment,您就知道您需要提供的下一个参数是部门名称。

如果你想避免修改原点 classes 你可以使用隐式 class,例如

implicit class EmpExtensions(emp: emp) {
  def withAddress(name: String, pincode: String) { 
    //code omitted 
  }

  // code omitted
}

然后 import EmpExtensions 在任何需要这些方法的地方

我使用反射创建了一个 DSL,这样我们就不需要向其中添加每个字段。

免责声明: 这个 DSL 是非常弱类型的,我这样做只是为了好玩。我真的不认为这在 Scala 中是一个好方法。

scala> create an Employee where "homeAddress" is Address("a", "b") and "department" is Department("c") and that_s it
res0: Employee = Employee(a=b,null,c)

scala> create an Employee where "workAddress" is Address("w", "x") and "homeAddress" is Address("y", "z") and that_s it
res1: Employee = Employee(y=z,w=x,null)

scala> create a Customer where "address" is Address("a", "b") and "age" is 900 and that_s it
res0: Customer = Customer(a=b,900)

最后一个例子相当于这样写:

create.a(Customer).where("address").is(Address("a", "b")).and("age").is(900).and(that_s).it

一种在 Scala 中编写 DSL 并避免括号和点的方法是遵循以下模式:

object.method(parameter).method(parameter)...

这是来源:

// DSL

object create {
  def an(t: Employee.type) = new ModelDSL(Employee(null, null, null))
  def a(t: Customer.type) = new ModelDSL(Customer(null, 0))
}

object that_s

class ModelDSL[T](model: T) {
  def where(field: String): ValueDSL[ModelDSL2[T], Any] = new ValueDSL(value => {
    val f = model.getClass.getDeclaredField(field)
    f.setAccessible(true)
    f.set(model, value)
    new ModelDSL2[T](model)
  })

  def and(t: that_s.type) = new { def it = model }
}

class ModelDSL2[T](model: T) {
  def and(field: String) = new ModelDSL(model).where(field)

  def and(t: that_s.type) = new { def it = model }
}

class ValueDSL[T, V](callback: V => T) {
  def is(value: V): T = callback(value)
}

// Models

case class Employee(homeAddress: Address, workAddress: Address, department: Department)

case class Customer(address: Address, age: Int)

case class Address(name: String, pincode: String) {
  override def toString = name + "=" + pincode
}

case class Department(name: String) {
  override def toString = name
}