【问题标题】:Managing trait instance consumers in a generic way以通用方式管理 trait 实例消费者
【发布时间】:2019-05-20 22:27:59
【问题描述】:

假设我有以下基本特征和案例类

sealed trait BaseTrait
case class Foo(x: Integer = 1) extends BaseTrait
case class Bar(x: String = "abc") extends BaseTrait

我想为可以处理 BaseTrait 实例的类创建一个通用接口,如下所示

class FooProcessor(val model: FooModel) extends BaseProcessor[Foo] {
    val v: Option[Foo] = model.baseVar
}
class BarProcessor(val model: BarModel) extends BaseProcessor[Bar] {
    val v: Option[Bar] = model.baseVar
}

为此,我有以下特点

trait BaseModel[T <: BaseTrait] {
    var baseVar: Option[T] = None
}
trait BaseProcessor[T <: BaseTrait] {
    def model: BaseModel[T]
    def process(x: T): Unit = model.baseVar = Option(x)
}

模型定义如下

class FooModel extends BaseModel[Foo]
class BarModel extends BaseModel[Bar]

现在让我们想象一下,我的应用中有以下处理器

val fooProcessor = new FooProcessor(new FooModel)
val barProcessor = new BarProcessor(new BarModel)

我想以某种通用的方式处理它们,像这样

def func[T <: BaseTrait](p: T) {
    val c/*: BaseProcessor[_ >: Foo with Bar <: BaseTrait with Product with Serializable]*/ = p match {
        case _: Foo => fooProcessor
        case _: Bar => barProcessor
    c.process(p)
}

编译器对最后一行不太满意,它说

类型不匹配;
发现:T
必需:_1

如果我理解正确,这基本上是编译器试图阻止 barProcessor.process(Foo()) 发生。我尝试了几种解决方案来解决这个问题并实现所需的行为:

  • 解决此问题的最简单方法是在匹配案例中使用正确的 BaseTrait 实例调用正确的 *Processor.process,这似乎违背了以某种通用方式处理它们的全部意义
  • 在 BaseModel 和 BaseProcessor 中使用抽象类型,一方面摆脱了 BaseModel 中有些不需要的类型参数,但编译器的抱怨仍然有效,我无法弄清楚是否有可能让它工作
  • 摆脱 BaseModel 中的类型参数和约束,只需在处理器中进行类型转换以获得正确的类型,但显式类型转换也不是我真正希望的

像这样:

trait BaseModel {
    var baseVar: Option[BaseTrait] = None
}
trait BaseProcessor[T <: BaseTrait] {
    def model: BaseModel
    def process(x: T): Unit = model.baseVar = Some(x)
    def getBaseValue: T = model.baseVar.map(_.asInstanceOf[T])
}

我想人们也可以以某种方式让编译器相信这两种类型(处理器的 T 和 func 参数 p 的 T)是等价的,但这似乎也有点矫枉过正(而且我也不确定它是怎么做到的可以)。

所以我的问题如下:是否有可能以某种简单的方式完成我在这里想要实现的目标(以统一的方式管理处理器 + 每个处理器都知道其特定的 BaseTrait 类型)?有没有更好的模型我错过了?

更新

根据Tim's answer,使控制器隐式可以解决问题,但是,如果您想要一个定义控制器的类+在其接口上具有“func”,编译器似乎不再正确地解决隐式问题。所以如果我尝试做这样的事情

class ProcessorContainer {
    implicit val fooProcessor = new FooProcessor(new FooModel)
    implicit val barProcessor = new BarProcessor(new BarModel)
    def func[T <: BaseTrait](p: T) = typedFunc(p)
    private def typedFunc[T <: BaseTrait](p: T)(implicit processor: BaseProcessor[T]) =
        processor.process(p)
}
    
class Test {
    val processorContainer = new ProcessorContainer
    processorContainer.func(Foo())
    processorContainer.func(Bar())
}

我收到以下编译错误(一个用于 Foo,一个用于 Bar):

找不到参数处理器的隐含值:BaseProcessor[Foo]
方法参数不足

有没有办法解决这个问题?我当然可以公开控制器,以便可以隐式传递它们,但是我不想这样做。

【问题讨论】:

    标签: scala generics types


    【解决方案1】:

    您可以通过将处理器设为implicit 并将它们作为额外参数传递给func 来创建一个简单的类型类:

    implicit val fooProcessor = new FooProcessor(new FooModel)
    implicit val barProcessor = new BarProcessor(new BarModel)
    
    def func[T <: BaseTrait](p: T)(implicit processor: BaseProcessor[T]) =
      processor.process(p)
    

    如果您将Foo 传递给func,它将调用FooProcessor.process,如果您将Bar 传递给func,它将调用BarProcessor

    【讨论】:

    • 您的解决方案似乎正是我所需要的,但是如果控制器被包含在一个类中而 'func' 被暴露,似乎隐式解析没有正确完成。我已经更新了我的问题,您是否有办法解决这个问题?
    猜你喜欢
    • 2016-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-05-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多