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
}
我的用例有 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
}