将案例类密封起来是一种好习惯吗?

时间:2015-06-30 02:02:24

标签: scala

密封类的主要原因似乎是,这允许编译器在对这些类进行模式匹配时进行彻底搜索。假设我有用于模式匹配的数据类型。玩具示例:

sealed trait Statement
case class Assign(name: String, value: Int) extends Statement
case class Print(name: String) extends Statement
case class IfZero(name: String, thenn: Statement, els: Option[Statement]) extends Statement
case class Block(statements: List[Statement]) extends Statement

这些类的用例是通过模式匹配来使用它们:

def execute(statement: Statement): Unit = statement match {
    case Assign(name, value)      => ???
    case Print(name)              => ???
    case IfZero(name, thenn, els) => ???
    case Block(statements)        => statements foreach { execute(_) }
  }

为此,Statement特征为sealed,以便编译器可以在匹配语句中忘记一种语句时向我发出警告。但案例类怎么样?案例类不能相互继承,但特征和普通类可以。那么,密封案例类也是一种好的做法吗?如果我不这样做可能会出错?

3 个答案:

答案 0 :(得分:6)

您不必密封案例类,但您应将其标记为final,因此禁止任何进一步的继承关系。使它们密封只有在你想要对其子类进行穷举检查时才有用,这不是一个非常可能的用例。

默认情况下将所有类标记为final是一件好事,因为它禁止API的用户在覆盖其方法时更改这些类的行为。如果你没有专门设计你的类被子类化,那么子类化可能会导致你的应用程序中出现错误,因为子类化的类不再按照它的意图去做了。

答案 1 :(得分:1)

这个答案留给程序员 并非所有时候都可以为默认类定义合理的行为。

这些类型问题的替代方法是使用密封类(公共基类)。

密封班的优点

  • 如果我们无法概括默认值,我们可以使用密封类 案件。

  • 在使用案例类时,不需要默认,因为我们涵盖了所有可能性。如果您错过任何一个,我们将收到编译警告match may not be exhaustive。在密封类中,如果不存在案例,那么我们得到“scala.MatchError:”。

密封班的缺点

  • 如果层次结构频繁更改,请避免使用sealed案例类层次结构。由于必须在同一文件中声明整个层次结构,因此修改现有代码,重置它(使用它的其他代码)并重新部署它可能代价高昂。

答案 2 :(得分:0)

我偶尔发现扩展案例类以启用特定实例子集的专门处理非常有用:

case class Ellipse(major: Int, minor: Int) {
  def draw() = //general drawing code
}
class Circle(radius: Int) extends Ellipse(radius, radius) {
  override def draw() = //faster specialized code for drawing circles
}

我们也可以将它们用于模式匹配:

def draw(e: Ellipse) = {
  case c: Circle => //fast path for drawing a circle
  case _ => //general case - note that this case must be able to handle
            //ellipses that happen to be circular
}

只要子类符合LSP,这是合法的。

这种事情有多重要?可能不是很好。在您自己的应用程序代码中,“默认”密封所有案例类可能很好,因为您始终可以“解封”它们。在图书馆中,如果您的某个用户想要执行上述操作,我会错误地保留未密封的内容。