如何为 Scala 子类型实现流畅的接口?
How to implement fluent interface for scala subtypes?
我可以通过以下方式使用流畅的界面实现嵌套 类:
class Animal(name: String, props: Map[String, Any]) {
def properties: Map[String, Any] = Map("name" -> name) ++ props
def withAge(age: Int): Animal = new Animal(name, props.updated("age", age)) // can't use built-in copy
}
case class Cat(name: String, lives: Int, props: Map[String, Any]) extends Animal(name, props) {
override def properties: Map[String, Any] = Map("name" -> name, "lives" -> lives) ++ props
override def withAge(age: Int): Cat = Cat(name, lives, props.updated("age", age))
}
然而我对此还很不满意。有很多重复,即使我使用继承,我也没有重用任何代码。
我试过使用 this.type
作为 return 类型,甚至使用 zio-prelude
子类型化功能,但一直存在的问题是,在某些时候,子类无法被正确识别。
有没有更好的方法来做到这一点而无需重复并利用 Scala 功能?
理想情况下我想要这样的东西
case class Animal(name: String, props: Map[String, Any]) {
def properties: Map[String, Any] = Map("name" -> name) ++ props
def withAge(age: Int): this.type = copy(props = props.updated("age", age))
}
final case class Cat(name: String, lives: Int, props: Map[String, Any]) extends Animal(name, props + ("lives" -> lives))
这样就不会发生重复。当然,以下内容没有编译。
val myCat: Cat = Cat("murzic", 9, Map()).withAge(4)
package animalworld
import scala.collection.mutable
class Builder(val properties: mutable.HashMap[String, Any]) {
def this() = this(mutable.HashMap.empty)
def withName(name: String): Builder = {
properties.put("name", name)
this
}
def withAge(age: Int): Builder = {
properties.put("age", age)
this
}
def setProperty(key: String, value: Any): Builder = {
properties.put(key, value)
this
}
def build[A <: Animal](implicit buildable: Buildable[A]): A =
buildable.build(this)
}
package animalworld
trait Buildable[A <: Animal] {
def build(builder: Builder): A
}
package animalworld
import scala.collection.mutable
class Animal protected (val name: String, val properties: Map[String, Any]) {
def toBuilder: Builder = new Builder(mutable.HashMap.from(properties))
}
object Animal {
implicit val animalBuildable: Buildable[Animal] = { builder =>
new Animal(
builder.properties("name").asInstanceOf[String],
Map.from(builder.properties)
)
}
}
package animalworld
class Cat protected (
override val name: String,
val lives: Int,
override val properties: Map[String, Any]
) extends Animal(name, properties)
object Cat {
implicit val catBuildable: Buildable[Cat] = { builder =>
new Cat(
builder.properties("name").asInstanceOf[String],
builder.properties("lives").asInstanceOf[Int],
Map.from(builder.properties)
)
}
}
package animalworld
object Main extends App {
val animal1 = new Builder().withName("tim-tim").build[Animal]
val cat1 = animal1.toBuilder.withAge(10).setProperty("lives", 9).build[Cat]
println(animal1.name)
// tim-tim
println(animal1.properties)
// Map(name -> tim-tim)
println(cat1.name)
// tim-tim
println(cat1.lives)
// 9
println(cat1.properties)
// Map(name -> tim-tim, lives -> 9, age -> 10)
}
我可以通过以下方式使用流畅的界面实现嵌套 类:
class Animal(name: String, props: Map[String, Any]) {
def properties: Map[String, Any] = Map("name" -> name) ++ props
def withAge(age: Int): Animal = new Animal(name, props.updated("age", age)) // can't use built-in copy
}
case class Cat(name: String, lives: Int, props: Map[String, Any]) extends Animal(name, props) {
override def properties: Map[String, Any] = Map("name" -> name, "lives" -> lives) ++ props
override def withAge(age: Int): Cat = Cat(name, lives, props.updated("age", age))
}
然而我对此还很不满意。有很多重复,即使我使用继承,我也没有重用任何代码。
我试过使用 this.type
作为 return 类型,甚至使用 zio-prelude
子类型化功能,但一直存在的问题是,在某些时候,子类无法被正确识别。
有没有更好的方法来做到这一点而无需重复并利用 Scala 功能?
理想情况下我想要这样的东西
case class Animal(name: String, props: Map[String, Any]) {
def properties: Map[String, Any] = Map("name" -> name) ++ props
def withAge(age: Int): this.type = copy(props = props.updated("age", age))
}
final case class Cat(name: String, lives: Int, props: Map[String, Any]) extends Animal(name, props + ("lives" -> lives))
这样就不会发生重复。当然,以下内容没有编译。
val myCat: Cat = Cat("murzic", 9, Map()).withAge(4)
package animalworld
import scala.collection.mutable
class Builder(val properties: mutable.HashMap[String, Any]) {
def this() = this(mutable.HashMap.empty)
def withName(name: String): Builder = {
properties.put("name", name)
this
}
def withAge(age: Int): Builder = {
properties.put("age", age)
this
}
def setProperty(key: String, value: Any): Builder = {
properties.put(key, value)
this
}
def build[A <: Animal](implicit buildable: Buildable[A]): A =
buildable.build(this)
}
package animalworld
trait Buildable[A <: Animal] {
def build(builder: Builder): A
}
package animalworld
import scala.collection.mutable
class Animal protected (val name: String, val properties: Map[String, Any]) {
def toBuilder: Builder = new Builder(mutable.HashMap.from(properties))
}
object Animal {
implicit val animalBuildable: Buildable[Animal] = { builder =>
new Animal(
builder.properties("name").asInstanceOf[String],
Map.from(builder.properties)
)
}
}
package animalworld
class Cat protected (
override val name: String,
val lives: Int,
override val properties: Map[String, Any]
) extends Animal(name, properties)
object Cat {
implicit val catBuildable: Buildable[Cat] = { builder =>
new Cat(
builder.properties("name").asInstanceOf[String],
builder.properties("lives").asInstanceOf[Int],
Map.from(builder.properties)
)
}
}
package animalworld
object Main extends App {
val animal1 = new Builder().withName("tim-tim").build[Animal]
val cat1 = animal1.toBuilder.withAge(10).setProperty("lives", 9).build[Cat]
println(animal1.name)
// tim-tim
println(animal1.properties)
// Map(name -> tim-tim)
println(cat1.name)
// tim-tim
println(cat1.lives)
// 9
println(cat1.properties)
// Map(name -> tim-tim, lives -> 9, age -> 10)
}