具有Scala中具体定义的可选参数的抽象类

时间:2018-01-24 23:16:17

标签: scala abstract-class factory

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

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

然后一个示例案例类将是:

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)
  }
}

如果我们只有两个其他可选参数,这会很有效,但是我们可以添加另一个可选参数,这会使处理这个逻辑变得有点棘手

1 个答案:

答案 0 :(得分:3)

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

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

  1. 匹配实际存在参数的输出类型
  2. 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. 您按照评论
    2. 中的建议,按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。我怀疑这两种解决方案可能都不是你真正想要的。在这种情况下,你应该更好地描述你的真实问题。