如何通过对代码库进行最少的更改来丰富案例 class?

How do I enrich a case class with minimal changes to the codebase?

使用 scala 2.11.12.

分散在我的代码库中我有一个这样的案例class:

case class Landscape(
  north: Sight,
  east: Sight,
  south: Sight,
  west: Sight
) {
  def toList: List[Sight] = List(north, east, south, west)

  def isIdyllic: Boolean = north.isPastoral && east.isPastoral && south.isPastoral && west.isPastoral
}

(带有自定义案例 class Sight)和相应的伴生对象:

object Landscape {
  def fromSeq(s: Seq[Sight]): Landscape = {
    require(s.length == 4)

    Landscape(
      north = s(0),
      east = s(1),
      south = s(2),
      west = s(3)
    )
  }

  def pickByBeautifulSouth(scape1: Landscape, scape2: Landscape): Landscape = {
    if (scape1.south.beauty > scape2.south.beauty) scape1 else scape2
  }
}

事实证明,拥有相似的类型会很有用,所以我创建了一个通用案例 class:

case class Compass[A](
  north: A,
  east: A,
  south: A,
  west: A
) {
  def toList: List[A] = List(north, east, south, west)
}

与相应的伴随对象:

object Compass {
  def fromSeq[A](s: Seq[A]): Compass[A] = {
    require(s.length == 4)

    Compass[A](
      north = s(0),
      east = s(1),
      south = s(2),
      west = s(3)
    )
  }
}

显然 isIdyllicpickByBeautifulSouth 对任意类型 A 没有意义。 现在我想使 Landscape 成为一个丰富的 Compass,这样我就不必再在 Landscape 中定义 toListfromSeq

我知道我做不到

case class Landscape(
  north: Sight,
  east: Sight,
  south: Sight,
  west: Sight
) extends Compass[Sight] {
  def isIdyllic: Boolean = north.isPastoral && east.isPastoral && south.isPastoral && west.isPastoral
}

因为个案继承是不可能的。我也不能使 Compass[A] 成为这样的特征:

trait Compass[A]{
  def north: A
  def east: A
  def south: A
  def west: A

  def toList: List[A] = List(north, east, south, west)
}

因为那样我会破坏 fromSeq,它利用了 Compass 的字段及其应用方法。

我也想过使用隐式 class

implicit class LandscapeOps(ls: Compass[Sight]) {
  def isIdyllic: Boolean = ls.north.isPastoral && ls.east.isPastoral && ls.south.isPastoral && ls.west.isPastoral
}

和我的代码库中的类型别名

type Landscape = Compass[Sight]

但是,这样一来我又会因为失去 Landscape 的应用方法而破坏我的代码。而且我也不知道如何添加 pickByBeautifulSouth.

长话短说:我正在寻找方法

不确定我是否理解所有的限制,但是.. 为什么不是特征 Compass[T] 和子类型 Landscape


trait Compass[A] {
  val north: A
  val east: A
  val south: A
  val west: A
  def toList: List[A] = List(north, east, south, west)
}

case class Landscape(
  north: Sight,
  east: Sight,
  south: Sight,
  west: Sight
) extends Compass[Sight] {
  def isIdyllic: Boolean = north.isPastoral && east.isPastoral && south.isPastoral && west.isPastoral
}

那么整个 Landscape 对象仍然有效

object Landscape {
  def fromSeq(s: Seq[Sight]): Landscape = {
    require(s.length == 4)

    Landscape(
      north = s(0),
      east = s(1),
      south = s(2),
      west = s(3)
    )
  }

  def pickByBeautifulSouth(scape1: Landscape, scape2: Landscape): Landscape =
    ???
}

所以这是我最终得到的一个最小工作示例:

case class Sight() {
  def isPastoral: Boolean = true
  def beauty: Int = 5
}

case class Compass[A](
  north: A,
  east: A,
  south: A,
  west: A
) {
  def toList: List[A] = List(north, east, south, west)
}

object Compass {
  def fromSeq[A](s: Seq[A]): Compass[A] = {
    require(s.length == 4)

    Compass[A](
      north = s(0),
      east = s(1),
      south = s(2),
      west = s(3)
    )
  }
}

object LandscapeModule {
  type Landscape = Compass[Sight]
  val Landscape = Compass

  implicit class LandscapeOps(ls: Landscape) {
    import ls._

    def isIdyllic: Boolean = north.isPastoral && east.isPastoral && south.isPastoral && west.isPastoral
  }
  
  object LandscapeOps {
    def pickByBeautifulSouth(scape1: Landscape, scape2: Landscape): Landscape = {
      if (scape1.south.beauty > scape2.south.beauty) scape1 else scape2
    }
  }
}

object HowToUseIt {
  import LandscapeModule.{Landscape, LandscapeOps}

  val sight = Sight()
  val sights: Seq[Sight] = Seq.fill(4)(sight)

  val landscape: Landscape = Landscape(north = sight, east = sight, south = sight, west = sight)
  val landscapeFromSeq = Landscape.fromSeq(sights)

  LandscapeOps.pickByBeautifulSouth(landscape, landscapeFromSeq)
}

这样我只需更改我的代码库中的两件事:

  • 导入:import LandscapeModule.{Landscape, LandscapeOps} 而不是 import Landscape
  • 除了 fromSeq 之外调用景观的自定义工厂方法:LandscapeOps.pickByBeautifulSouth 而不是 Landscape.pickByBeautifulSouth