使用反射来验证特征的所有实例都具有唯一字段

Using reflection to verify all instances of a trait have a unique field

考虑以下代码。动物应该有一个唯一的 ID。我想动态验证(在测试中)Animal 的所有具体子类型都具有唯一 ID。我希望我的测试失败,例如,如果 Cat 和 Fish 都试图通过 uniqueId = 2.

sealed trait Animal {
  val uniqueId: Int
}

abstract class Mammal(val uniqueId: Int) extends Animal

abstract class Fish(val uniqueId: Int) extends Animal


case class Dog(age: Int, name: String) extends Mammal(1)

case class Cat(favouriteFood: String) extends Mammal(2)

case class Salmon(isCute: Boolean) extends Fish(3)

我正在使用 reflections 获取 classes。

    import org.reflections.Reflections
    val reflections                 = new Reflections("package.blah")
    val allSubtypes: Seq[Class[_ <: Animal]] = reflections.getSubTypesOf(classOf[Animal]).asScala.toList
    val concreteSubtypes: Seq[Class[_ <: Animal]] = allSubtypes.filter(c => !Modifier.isAbstract(c.getModifiers))

我开始怀疑这可能是不可能的,但很想是错的!我有 classes 但无法实例化它们的实例,因为所有构造函数都不同,我不确定是否可以仅从 class.[=15 访问 uniqueId =]

正如 Tomer Shetah 的评论所说,这似乎不可能如前所述。但如果您可以拆分 AnimalAnimalCompanion,并使每个 AnimalCompanion:

的 id 实际上唯一,它可能会起作用
abstract class AnimalCompanion(val uniqueId: Int)

sealed trait Animal {
  def companion: AnimalCompanion
  def uniqueId = companion.uniqueId
}

abstract class Mammal(val companion: AnimalCompanion) extends Animal

abstract class Fish(val companion: AnimalCompanion) extends Animal

case class Dog(age: Int, name: String) extends Mammal(Dog)

object Dog extends AnimalCompanion(1)

case class Cat(favouriteFood: String) extends Mammal(Cat)

object Cat extends AnimalCompanion(2)

case class Salmon(isCute: Boolean) extends Fish(Salmon)

object Salmon extends AnimalCompanion(3)

然后你会以相同的方式找到 AnimalCompanion 的所有子类型,并且因为它们都是 object,所以它们将 have the MODULE$ field 持有实例并且该实例将具有 uniqueId 字段和 getter 方法。

未经测试,像这样的东西应该可以工作:

val allCompanionClasses = reflections.getSubTypesOf(classOf[AnimalCompanion]).asScala
val allCompanionInstances = allCompanionClasses.map(_.getField("MODULE$").get(null))
val allUniqueIds = allCompanionInstances.map(x => x.getClass().getMethod("uniqueId").invoke(x))

然后需要检查它们是否真的是独一无二的,例如通过

allUniqueIds.toSet.size == allUniqueIds.size

在不改变结构的情况下,我会考虑一种不同的方法:自己提供实例,只需验证它们包含所有 类.

val instances = Set(Dog(0, ""), Cat(""), Salmon(true))

// allSubtypes defined in the question
assert(allSubtypes.toSet == instances.map(_.getClass))
// same logic as above but we already have a Set
assert(instances.map(_.uniqueId).size == instances.size)