Abstract class 带有 Scala 中具体定义的可选参数

Abstract class with optional parameters to concrete definitions in Scala

我真的很喜欢 Scala 的抽象工厂模式,但我很难让它适用于我的用例。我有 'n' 个可选参数,我想用它们来创建一个通用的 class,可以通过我的工厂来识别具体类型。这是我正在使用的特征和模型类型的示例:

trait Animal {
     def id: Long
}
trait Pet {
     def name: String
}
trait Pest {
     def hasDisease: Boolean
}

然后示例案例 class 将是:

case class Dog(id: Long, name: String) extends Animal with Pet
case class Cat(id: Long, name: String) extends Animal with Pet
case class Rat(id: Long, hasDisease: Boolean) extends Animal with Pest
case class Cow(id: Long) extends Animal

然后工厂看起来像这样:

object Animal {
  def apply(id: Long, name: String) = name match {
    case "Lassie" => Dog(id, name)
    case "Garfield" => Cat(id, name)
  }
  def apply(id: Long, hasDisease: Boolean) = Rat(id, hasDisease)
  def apply(id: Long) = Cow(id)

所以在 REPL 中这很好用,我可以做到:

Animal(id=2, name="Lassie")
res5: Dog = Dog(2,"Lassie")
Animal(id=1)
res6: Cow = Cow(1)

但是在我的资源中,因为参数是可选的(name、hasDisease),所以我需要能够像这样构造我的抽象动物对象:

Animal(id=1, name=None, hasDisease=None)
res7: Cow = Cow(1)

知道如何进行这项工作吗?

编辑

我不一定致力于这种模式,但这只是我的第一次尝试。总体对象是我有 'n' 个可选查询参数,根据存在的参数,我想映射到具体表示

编辑 2 正如 SergGr 指出的那样,一种可能性是根据参数的存在进行大小写匹配

object Animal {
  def apply(id: Long, nameOpt: Option[String] = None, hasDiseaseOpt: Option[Boolean] = None) = (nameOpt, hasDiseaseOpt) match {
    case (Some(_), Some(_)) => throw new IllegalArgumentException("Animal can't have both name and disease")
    case (None, Some(hasDisease)) => Rat(id, hasDisease)
    // different approaches to match values
    case (Some("Lassie"), None) => Dog(id, "Lassie") 
    case (Some(name), None) if "Garfield".equals(name)  => Cat(id, name) 
    case (Some(name), None) => throw new IllegalArgumentException(s"Unknown car or dog name '$name'")
    case (None, None) => Cow(id)
  }
}

如果我们只有两个其他可选参数,这会很好用,但我们有可能添加另一个可选参数,这会使处理这个逻辑有点棘手

我仍然不确定我是否正确理解了你的问题。首先,在 Scala 中表示可选内容的典型方式是 scala.util.Option

我不知道有什么简单的方法可以使此类代码在编译时安全且仍然可用。由于运行时错误取决于您的实际目标,我看到两种方法:

  1. 您匹配实际存在参数的输出类型
object Animal {
  def apply(id: Long, nameOpt: Option[String] = None, hasDiseaseOpt: Option[Boolean] = None) = (nameOpt, hasDiseaseOpt) match {
    case (Some(_), Some(_)) => throw new IllegalArgumentException("Animal can't have both name and disease")
    case (None, Some(hasDisease)) => Rat(id, hasDisease)
    // different approaches to match values
    case (Some("Lassie"), None) => Dog(id, "Lassie") 
    case (Some(name), None) if "Garfield".equals(name)  => Cat(id, name) 
    case (Some(name), None) => throw new IllegalArgumentException(s"Unknown car or dog name '$name'")
    case (None, None) => Cow(id)
  }
}
  1. 您按照评论中的建议id匹配输出类型
object Animal {
  def apply(id: Long, nameOpt: Option[String] = None, hasDiseaseOpt: Option[Boolean] = None) = (id, nameOpt, hasDiseaseOpt) match {
    case (1, None, None) => Cow(id)
    case (2, Some(name), None) => Dog(id, name)
    case (3, Some(name), None) => Cat(id, name)
    case (4, None, Some(hasDisease)) => Rat(id, hasDisease)
    case (id, _, _) if id >= 5 => throw new IllegalArgumentException(s"Unknown id = $id")
    case _ => throw new IllegalArgumentException(s"Unepxected combination of parameters: id = $id, name = $nameOpt, hasDisease = $hasDiseaseOpt")
  }
}

在这两种情况下,您都可以添加辅助非可选方法,例如

  // note that String might be null so it makes sense to use Option(name) rather than Some(name)
  def apply(id: Long, name: String) = apply(id, nameOpt = Option(name))
  def apply(id: Long, hasDisease: Boolean) = apply(id, hasDiseaseOpt = Some(hasDisease))

P.S。我怀疑这两种解决方案可能都不是您真正想要的。在那种情况下,你应该更好地描述你真正的问题。