上下文
我正在尝试为移动部门构建一个小型企业规则应用程序,特别是我正在建模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