简体   繁体   English

Scala - 如何定义逆变 class 的工厂方法?

[英]Scala - how to define factory method for contravariant class?

I have a trait with common parameters for a data source, with case classes for each actual source我有一个具有数据源通用参数的特征,每个实际源都有案例类

trait AbstractSource {
  val name: String
}

case class ConcreteSource(name: String) extends AbstractSource

I also have a trait for a class which acts on this data source (contravariant for the source)我还有一个 class 的特征,它作用于这个数据源(源逆变)

trait AbstractConsumer[-T <: AbstractSource] {
  def foo(inp: T): Unit
}

class ConcreteConsumer extends AbstractConsumer[ConcreteSource] {
  override def foo(inp: ConcreteSource): Unit =
    println(inp.name)
}

I want to create a factory method for my pipeline method which creates the correct consumer based on the input data source.我想为我的管道方法创建一个工厂方法,该方法根据输入数据源创建正确的使用者。 I have tried the following but both have errors我尝试了以下但都有错误

object ConsumerFactory {
  def create(inp: AbstractSource): AbstractConsumer[_ <: AbstractSource] =
    inp match {
      case _: ConcreteSource => new ConcreteConsumer()
      case _ => ???
    }
    
  def createTwo[T <: AbstractSource](inp: T): AbstractConsumer[T] =
    inp match {
      case _: ConcreteSource => new ConcreteConsumer() // errors "required: AbstractConsumer[T], found: ConcreteConsumer"
      case _ => ???
    }
}

class Main {
  def pipeline[T <: AbstractSource](consumer: AbstractConsumer[T], source: T): Unit =
    consumer.foo(source)

  def execute(): Unit = {
    val consumer: ConcreteSource = ConcreteSource("john")

    val source = ConsumerFactory.create(consumer) // errors "found: AbstractConsumer[_$1] where type _$1 <: AbstractSource, required: AbstractConsumer[ConcreteSource]"
    val source = ConsumerFactory.createTwo(consumer)

    pipeline(source, consumer)
  }
}

val x = new Main()
x.execute()

If I understand correctly, the issue is that I need to supply a subtype of AbstractConsumer[T] to the pipeline, but I don't know how to do this based on the input due to the contravariant type parameter.如果我理解正确,问题是我需要向管道提供 AbstractConsumer[T] 的子类型,但由于逆变类型参数,我不知道如何根据输入执行此操作。

IMHO, theese kinds of problems are more easily solvable with a typeclass, like this:恕我直言,这些类型的问题更容易通过类型类解决,如下所示:

trait AbstractConsumer[-S <: AbstractSource] {
  def consume(inp: S): Unit
}
object AbstractConsumer {
  sealed trait ConsumerFactory[S <: AbstractSource] {
    type Consumer <: AbstractConsumer[S]
    
    def createConsumer(): Consumer
  }
  
  type Factory[S <: AbstractSource, C <: AbstractConsumer[S]] = ConsumerFactory[S] { type Consumer = C }
  object Factory {
    def apply[S <: AbstractSource, C <: AbstractConsumer[S]](factory: => C): Factory[S, C] =
      new ConsumerFactory[S] {
        override final type Consumer = C
        
        override final def createConsumer(): Consumer =
          factory
      }
  }
  
  // Get by type.
  def apply[S <: AbstractSource](implicit factory: ConsumerFactory[S]): factory.Consumer =
    factory.createConsumer()

  // Get by value.
  def fromSource[S <: AbstractSource](source: S)(implicit factory: ConsumerFactory[S]): factory.Consumer =
    factory.createConsumer()
}

Then the concrete source will implement the typeclass, like this:然后具体源代码将实现类型类,如下所示:

final class ConcreteConsumer extends AbstractConsumer[ConcreteSource] {
  override def consume(inp: ConcreteSource): Unit =
    println(inp.name)
}
object ConcreteConsumer {
  implicit final val ConcreteConsumerFactory: AbstractConsumer.Factory[ConcreteSource, ConcreteConsumer] =
    AbstractConsumer.Factory(new ConcreteConsumer())
}

And, finally, you can use it like this:最后,您可以像这样使用它:

import ConcreteConsumer._ // Put the factory in scope.

val source = new ConcreteSource("john")

val consumer1 = AbstractConsumer[ConcreteSource]
val consumer2 = AbstractConsumer.fromSource(source)

You may adapt the code if the factory needs some arguments or something.如果工厂需要一些 arguments 或其他东西,您可以修改代码。


The code can be seen running here .可以看到代码在这里运行

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM