0

给定代码

object Renderer {

  sealed abstract class BasicRender

  case class RenderImages(img: Array[File]) extends BasicRender

  case class RenderVideo(video: File) extends BasicRender

  def rendererFor[T <: BasicRender : Manifest, Z <: Render.RenderingContext](ctx: Z): Option[Render[T]] = {
    val z = manifest[T].erasure
    if (z == classOf[RenderImages]) {
      Some(new ImagesRenderer(ctx.asInstanceOf[ImagesContext])) // .asInstanceOf[Render[T]])
    } else
    if (z == classOf[RenderVideo]) {
      Some(new VideoRenderer(ctx.asInstanceOf[VideoContext])) // .asInstanceOf[Render[T]])
    } else {
      None
    }
  }

  private class ImagesRenderer(ctx: ImagesContext) extends Render[RenderImages] {

    override def renderJSON(json: String)(implicit jsCtx: PhantomJsContext) = {
      None
    }

  }

  private class VideoRenderer(ctx: VideoContext) extends Render[RenderVideo] {

    override def renderJSON(json: String)(implicit jsCtx: PhantomJsContext) = {
      None
    }

  }


}

trait Render[+Out] {

  def renderJSON(json: String)(implicit jsCtx: PhantomJsContext): Option[Out]

}

我为它的类型参数制作了渲染特征协变,所以如果

RenderImages <: BasicRender 

然后

ImagesRenderer <: Render[RenderImages]

但看起来编译器无法推断 rendererFor 中的渲染器类型,所以我需要添加显式类转换,如

Some(new ImagesRenderer(ctx.asInstanceOf[ImagesContext]).asInstanceOf[Render[T]])

我的推理有什么问题?

4

2 回答 2

6

正如 Daniel C. Sobral 所解释的,您的问题是您正在动态地实例化不同的渲染器,其方式不会在类型系统中ctx捕获rendererFor. 解决此类问题的一种常见方法是使用类型类

import java.io.File

class PhantomJsContext

trait Renderer[+Out] {
  def renderJSON(json: String)(implicit jsCtx: PhantomJsContext): Option[Out]
}

trait RendererFactory[ContextType, ResultType] {
  def buildRenderer( ctx: ContextType ): Renderer[ResultType]
}

object Renderer {
  case class RenderImages(img: Array[File])
  case class RenderVideo(video: File)

  trait ImagesContext  
  trait VideoContext

  def rendererFor[ContextType, ResultType](ctx: ContextType)( implicit factory: RendererFactory[ContextType, ResultType] ): Renderer[ResultType] = {
    factory.buildRenderer( ctx )
  }

  class ImagesRenderer(ctx: ImagesContext) extends Renderer[RenderImages] {
    def renderJSON(json: String)(implicit jsCtx: PhantomJsContext) = ???
  }
  implicit object ImagesRendererFactory extends RendererFactory[ImagesContext, RenderImages] {
    def buildRenderer( ctx: ImagesContext ) = new ImagesRenderer( ctx )
  }

  class VideoRenderer(ctx: VideoContext) extends Renderer[RenderVideo] {
    def renderJSON(json: String)(implicit jsCtx: PhantomJsContext) = ???
  }
  implicit object VideoRendererFactory extends RendererFactory[VideoContext, RenderVideo] {
    def buildRenderer( ctx: VideoContext ) = new VideoRenderer( ctx )
  }
}

您可以轻松地检查 REPL 是否返回了正确的类型:

scala> lazy val r1 = Renderer.rendererFor( new Renderer.ImagesContext {} )
r1: Renderer[Renderer.RenderImages] = <lazy>

scala> :type r1
Renderer[Renderer.RenderImages]

scala> lazy val r2 = Renderer.rendererFor( new Renderer.VideoContext {} )
r2: Renderer[Renderer.RenderVideo] = <lazy>

scala> :type r2
Renderer[Renderer.RenderVideo]
于 2013-05-20T12:47:39.980 回答
5

T不能推断:它是一个传递给方法的参数,并不能保证返回的是一个Option[Render[T]]. 例如,假设您通过RenderImages并且它返回 a VideoRenderer,那么它显然是错误的。

现在,if您放入其中的条件可能会阻止这种情况发生,但编译器不会使用这些条件来确定您是否返回了正确的类型。

于 2013-05-20T06:28:06.113 回答