如何在 Scala 中对仅适用于特征的相同具体实现的操作进行建模?



上下文

我正在尝试为移动部门构建一个小型企业规则应用程序,特别是我正在建模Filters负责确定某个行程是否与给定的Filter配置匹配。另一个不变性是不允许相同类型的Filters重叠(然后根据混凝土过滤器定义重叠的含义(。

Filter性状

Filter特征本身非常简单,它只是定义了获取此过滤器标识符的方法。它还充当仅处理一般Filters的高级抽象的标记

sealed trait Filter {
def identifier: String
}

CanOverlap性状

更新 1

合并了 Brian 答案中的更改,但这也不是从下面的使用示例编译的。还更新了编译器错误消息。

更新 1 结束

此特征提供公共 APIoverlapsWith,用于检查两个筛选器是否重叠。它首先使用最终和私有方法isSameType检查两个过滤器是否具有相同的类型,并且只有在这种情况下,它才会委托给抽象保护的方法hasOverlapWith然后由实现提供。

sealed trait CanOverlap extends Filter {
type This >: this.type <: CanOverlap
final def overlapsWith(other: This): Boolean =
isSameType(this, other) && hasOverlapWith(other)
protected def hasOverlapWith(other: This): Boolean
final private def isSameType[U, P](left: U, right: P)(
implicit ev: U =:= P = null
): Boolean = ev != null
}

两个示例实现

以下两个示例应说明如何实现抽象。

出发前天数

final case class DayOfWeek(days: Set[java.time.DayOfWeek]) extends CanOverlap {
type This = DayOfWeek
protected def hasOverlapWith(other: DayOfWeek): Boolean = 
days.intersect(other.days).nonEmpty
def identifier: String = "day_of_week"
}

星期几

final case class DaysBeforeDeparture(lower: Int, higher: Int) extends CanOverlap {
type This = DaysBeforeDeparture
protected def hasOverlapWith(other: DaysBeforeDeparture): Boolean =
lower <= other.higher && other.lower >= higher
def identifier: String = "days_before_departure"
}

问题

有没有更简单的方法来实现相同的行为?

特别是CanOverlap特征中的isSameType方法对我来说似乎是一个非常笨拙的解决方案,我想知道如果两个实例属于相同的具体子类型,是否有一种更优雅的方法来处理唯一的运行操作。

提前致以亲切的问候和感谢!

编辑 1

所以布莱恩的回答已经让我有点向前了,但也许我不够明确。 因此,以下是预期用途的附加示例:

import java.time
val dowMondayTuesday = DayOfWeek(Set(time.DayOfWeek.MONDAY, time.DayOfWeek.TUESDAY))
val dowWednesdayFriday = DayOfWeek(Set(time.DayOfWeek.WEDNESDAY, time.DayOfWeek.FRIDAY))
val dowTuesdaySaturday = DayOfWeek(Set(time.DayOfWeek.TUESDAY, time.DayOfWeek.SATURDAY))
val dbdThreeFive = DaysBeforeDeparture(3, 5)
val dbdOneTwo = DaysBeforeDeparture(1, 2)
val dbdTwoFour = DaysBeforeDeparture(2, 4)
val filters : Set[CanOverlap] = Set(dowMondayTuesday, dbdOneTwo)
filters.exists(_.overlapsWith(dbdThreeFive)) // Should return false
filters.exists(_.overlapsWith(dbdTwoFour)) // Should return true - (2, 4) overlaps with (3, 5)
filters.exists(_.overlapsWith(dowWednesdayFriday)) // Should return false
filters.exists(_.overlapsWith(dowTuesdaySaturday)) // Should return true - Tuesday overlaps

现在,当从 IntelliJ 工作表运行时,这将给出以下编译错误:

Error:(15, 106) type mismatch;
found   : A$A0.this.dbdThreeFive.type (with underlying type com.flixbus.pricing.rules.model.Filters.DaysBeforeDeparture)
required: x$1.This
def get$$instance$$res0 = /* ###worksheet### generated $$end$$ */ println(filters.exists(_.overlapsWith(dbdThreeFive)));//
                                  ^

运行时方法

最大的问题是,根据您的使用示例,您需要一个运行时解决方案,但=:=是一个编译时解决方案。幸运的是,您可以改用模式匹配和ClassTag

sealed abstract class CanOverlap[This <: CanOverlap[This] : ClassTag] extends Filter {
final def overlapsWith[T <: CanOverlap[T]](other: T) = other match {
case t: This => hasOverlapWith(t)
case _ => false
}
protected def hasOverlapWith(other: This): Boolean
}

这应该足以解决您的问题。请继续阅读有关其他选项的一些讨论。


在该示例中,我CanOverlap更改为抽象类,以便可以使用上下文绑定。如果需要,您可以将其保留为特征并使用抽象类型,但它需要在此处和子类中更多样板:

sealed trait CanOverlap extends Filter {
type This >: this.type <: CanOverlap
implicit val cls: ClassTag[This]
...

这样做的优点是CanOverlap是一个特征,不需要类型参数,并且This具有下限。如果您希望用户经常引用CanOverlap,或者您有理由希望它成为一种特征,您可能会喜欢它。但是,它要求子类显式提供cls,所以如果你有很多子类,你可能更喜欢第一种方法。

还有一种方法可以通过将其推入另一个类来摆脱这两种样板:CanOverlap是一个特征,AbstractCanOverlap是一个扩展它的抽象类,使用上下文绑定,以便子类不必显式提供 ClassTag。这有点像 ScalaAbstractSeq如何处理Seq实现之间共享的样板文件。归根结底,这是一个你想把样板放在哪里的问题。

旧答案(编译时方法(

您的编辑已经向我明确表示这对您不起作用,但我会保留它,因为它可能会帮助其他人。

首先,您的isSameType方法不起作用。这是因为this的类型CanOverlap[This]可能与This不同,所以返回false

DaysBeforeDeparture(1, 1).overlapsWith(DaysBeforeDeparture(1, 1))

但是,我真的认为你根本不需要isSameType。我认为您基于通用的方法有效,但可以从一点改进中受益。这是我的方法,基于Odersky的要点。

sealed trait CanOverlap extends Filter {
type This >: this.type <: CanOverlap
def overlapsWith(other: This): Boolean
}

最显着的区别是This是抽象类型而不是类型参数,并且我添加了this.type作为下限。现在,让我们尝试一下:

final case class DaysBeforeDeparture(lower: Int, higher: Int)
extends CanOverlap {
type This = DaysBeforeDeparture
def overlapsWith(other: This): Boolean =
lower <= other.higher && other.lower >= higher
def identifier: String = "days_before_departure"
}
final case class DayOfWeek(days: Set[java.time.DayOfWeek])
extends CanOverlap {
type This = DayOfWeek
def overlapsWith(other: This): Boolean =
days.intersect(other.days).nonEmpty
def identifier: String = "day_of_week"
}
DaysBeforeDeparture(1, 1).overlapsWith(DaysBeforeDeparture(1, 1)) // Compiles
DaysBeforeDeparture(1, 1).overlapsWith(DayOfWeek(Set())) // Does not compile

相关内容

  • 没有找到相关文章

最新更新