在Scala中创建需要不同构造函数参数的子类型的通用方法

在Scala中创建需要不同构造函数参数的子类型的通用方法,scala,scalatest,Scala,Scalatest,在我的单元测试中,我需要生成各种事件,这些事件都继承自抽象事件类,但以其他方式创建。例如,事件A和B具有以下签名: def makeEventA(a: Int, b: String): EventA def makeEventB(p: String, q: Long, r: Long): EventB 这两个事件的核心逻辑是相同的,都是在Event的任何子类上定义的,因此我想创建行为函数并重用它们。为此,我考虑创建一个“制造者”特征,从中我可以制作每个单独的事件,并在单元测试中使用结果: t

在我的单元测试中,我需要生成各种事件,这些事件都继承自抽象
事件
类,但以其他方式创建。例如,事件A和B具有以下签名:

def makeEventA(a: Int, b: String): EventA

def makeEventB(p: String, q: Long, r: Long): EventB
这两个事件的核心逻辑是相同的,都是在
Event
的任何子类上定义的,因此我想创建行为函数并重用它们。为此,我考虑创建一个“制造者”特征,从中我可以制作每个单独的事件,并在单元测试中使用结果:

trait EventMaker {
  def make[T <: Event](...): T
}

class EventAMaker extends EventMaker {
  override def make[EventA](...) = /* custom implementation */
}

// similar for EventBMaker

另一个选项是对所有参数使用
选项
,默认值为
,以便签名匹配。但是,这似乎是浪费,并不能完全提高易读性。

我认为,最好的办法是将参数打包到不透明的
元组中,然后动态或静态地检查元组是否与
事件所需的参数匹配

假设以下设置:

import scala.reflect._

sealed trait Event

case class EventA(a: Int, b: String) extends Event
object EventA {
  // this val is needed only for the dynamic checking approach
  final val tag = classTag[EventA]
}

case class EventB(p: String, q: Long, r: Long) extends Event
object EventB {
  // this val is needed only for the dynamic checking approach
  final val tag = classTag[EventB]
}
动态检查时,只需匹配参数,并检查它们是否位于具有正确长度和正确元素类型的元组中。这不是类型安全的,如果事件参数具有带有类型参数的类型(例如,
l:List[Int]
t:(Int,Double)
),则在运行时会抛出
ClassCastException
s,因此类型擦除起作用

代码可以以不同的方式组织:

def make[T <: Event, Args](obj: Class[T], args: Args)(implicit tag: ClassTag[T]): T = ((tag, args) match {
  case (EventA.tag, (a: Int, b: String)) => EventA(a, b)
  case (EventB.tag, (p: String, q: Long, r: Long)) => EventB(p, q, r)
  case (otherTag, otherArgs) => sys.error("wrong args for tag")
}).asInstanceOf[T]

scala> make(classOf[EventA], (10, "foo"))
res4: EventA = EventA(10,foo)

scala> make(classOf[EventB], ("bar", 10L, 20L))
res5: EventB = EventB(bar,10,20)

您还可以使用
无形状
库(或手动编写的宏)静态检查参数。如果提供的参数与case类所需的参数不匹配,这将导致编译错误。此外,如果单个事件由case类表示,则这种方法最为有效,但也可以对其进行调整,以支持使用
shapeless.ops.function.FnToProduct的某些函数

import shapeless._

class EventMaker3[T <: Event] {
  def apply[Args, H <: HList, H0 <: HList](args: Args)(implicit
    genObj: Generic.Aux[T, H],      // conversion between HList and case class
    genArgs: Generic.Aux[Args, H0], // conversion between HList and arguments tuple
    ev: H0 =:= H                    // assert that HList representations of the case class 
                                    // and provided arguments are the same
  ): T = genObj.from(genArgs.to(args))
}
def make3[T <: Event] = new EventMaker3[T]

scala> make3[EventA](10, "foo")
res8: EventA = EventA(10,foo)

scala> make3[EventB]("bar", 10L, 20L)
res9: EventB = EventB(bar,10,20)

scala> make3[EventA](1, 2)
<console>:21: error: Cannot prove that H0 =:= H.
       make3[EventA](1, 2)
                    ^
导入无形状_
类EventMaker3[T
class EventMaker[T <: Event] {
  def apply[Args](args: Args)(implicit tag: ClassTag[T]): T = ((tag, args) match {
    case (EventA.tag, (a: Int, b: String)) => EventA(a, b)
    case (EventB.tag, (p: String, q: Long, r: Long)) => EventB(p, q, r)
    case (otherTag, otherArgs) => sys.error("wrong args for tag")
  }).asInstanceOf[T]
}
def make2[T <: Event] = new EventMaker[T]

scala> make2[EventA](10, "foo")
res6: EventA = EventA(10,foo)

scala> make2[EventB]("bar", 10L, 20L)
res7: EventB = EventB(bar,10,20)
import shapeless._

class EventMaker3[T <: Event] {
  def apply[Args, H <: HList, H0 <: HList](args: Args)(implicit
    genObj: Generic.Aux[T, H],      // conversion between HList and case class
    genArgs: Generic.Aux[Args, H0], // conversion between HList and arguments tuple
    ev: H0 =:= H                    // assert that HList representations of the case class 
                                    // and provided arguments are the same
  ): T = genObj.from(genArgs.to(args))
}
def make3[T <: Event] = new EventMaker3[T]

scala> make3[EventA](10, "foo")
res8: EventA = EventA(10,foo)

scala> make3[EventB]("bar", 10L, 20L)
res9: EventB = EventB(bar,10,20)

scala> make3[EventA](1, 2)
<console>:21: error: Cannot prove that H0 =:= H.
       make3[EventA](1, 2)
                    ^