我需要一个函数来接受任何扩展特征的case类。举个例子
假设有一个特征
trait Action {
def execute(param: Parameters)
}
可以传递以下参数,具体取决于它的作用类型
trait Parameters
case class A(x: String, y: Double) extends Parameters
case class B(x: Int, y:String, z: Double) extends Parameters
用户应该能够指定它想要的参数
class Action1 extends Action {
override def execute(param: A)
}
class Action2 extends Action {
override def execute(param: B)
}
并且在呼叫时
def run(t: Action) {
t.execute(new A("a", 1.0))
}
如何实现这一目标?当我实现Action1时,它说我会得到我们无法覆盖的错误,因为trait说execute方法接受Parameters,但是在覆盖时,我们需要一个扩展Parameters的类。
答案 0 :(得分:4)
一种方法是参数化Action
。
trait Action[T <: Parameters] {
def execute(param: T)
}
然后
class Action1 extends Action[A] {
override def execute(param: A)
}
class Action2 extends Action[B] {
override def execute(param: B)
}
def run[T <: Parameters](t: Action[T], param: T) {
t.execute(param)
}
// You don't need new for a case class
run(new Action1, A("a", 1.0))
你必须在这里稍微修改一下run
,因为你无法通过单独的子类化来获得你所要求的东西。参数类型为contravariant,这意味着函数A => T
是函数Parameters => T
的超类。这是因为虽然类型A
的任何参数都适用于A => T
和Parameters => T
,但只有Parameters
类型的某些参数适用于A => T
,而所有参数都适用于Parameters => T
适用于{{1}}。
答案 1 :(得分:2)
您需要考虑的是为什么您需要Action
开始使用execute
方法。也许,你的代码中的其他地方有这样的东西:
def executeAll(actions: Seq[Action], params: Parameters) =
actions.foreach { _.execute(params) }
请注意,您的actions
列表可以包含不同类型的操作。如果其中一些除A
类型的参数外,有些需要类型为B
,那么它将不起作用,因为您的params
参数不能同时使用。
出于这个原因,你不能覆盖一个方法来在子类中获取不同的参数类型(实际上,你可以,但是类型必须是原始类型的超类,而不是子类。他们说这些函数在这个reas的参数类型中是逆变,但我离题了。
如果您的Action1
和Action2
实际上可以通过特征界面实际处理它们的参数而不要求它们属于特定类型,那么解决方案很简单:只需更改您的声明重写方法为override def execute(params: Parameters)
。
如果不是这种情况,Action1
只能处理A
类型的参数,那么(1)可能(虽然,不一定)你的设计有问题,并且(2)你必须使用参数化解决方案,正如另一个答案所示。但是,你必须改变所有处理Action
的地方:
def executeAll[T <: Parameters](actions: Seq[Action[T]], params: T) = ...
此声明限制params
参数的类型以匹配列表中的操作所期望的内容,因此错误地不会使用Action1.execute
调用B
。
现在,正如你在其他答案的评论中所提到的,你不能混合不同类型的动作,因为它没有意义:它们本质上是不同的类。如果您不打算使用execute
方法,并且只想传递一些代码周围的不同操作列表,则可以使用通配符语法:
val actions: List[Action[_] = List(Action1(...), Action2(...))
再次,此列表可以作为任何类型的操作的通用容器,但是您不能使用它来一般地应用execute
方法,因为这将要求参数在同一类型中具有不同的类型时间。