【问题标题】:Is it good practice to make case classes sealed?密封案例类是一种好习惯吗?
【发布时间】:2015-07-01 20:46:33
【问题描述】:

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

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 trait 是 sealed,因此如果我忘记了 match 语句中的一种语句类型,编译器可以警告我。但是案例类呢?案例类不能相互继承,但特征和普通类可以。那么,密封案例类是否也是一种好习惯?如果我不这样做会出什么问题?

【问题讨论】:

  • 密封你的层次结构是否是一种“好的做法”在很大程度上取决于手头的情况。要回答这个问题,您必须更多地了解您的层次结构意味着什么以及如何使用它。

标签: scala


【解决方案1】:

您不必密封案例类,但您应该将它们标记为final,因此禁止任何进一步的继承关系。仅当您希望对其子类进行详尽检查时,将它们密封起来才有用,这不太可能是用例。

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

【讨论】:

  • Making them sealed is only useful when you want exhaustiveness checking on its subclasses, which is not a very likely use case. 这就是我一直在寻找的,也是我思考了一下之后得出的结论。如果处理了所有继承的案例类,则仍然可以保证(密封)基本特征的穷举性,因为这些案例类的任何子类的任何实例也根据定义也是这些案例类之一的实例。
【解决方案2】:

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

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

密封类的优点

  • 如果我们不能概括默认值,我们可以使用密封类 案子。

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

密封类的缺点

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

【讨论】:

  • 我不明白你想说什么。 default classIn sealed classes all the cases are executed even after a exception is raised for any unknown caseif a case is unknown for non-sealed classes(even default fails) then further cases are stopped 是什么意思?
  • 很抱歉,我这样说是我的错误。
  • So, is it good practice to seal the case classes as well? 是否意味着您愿意将密封关键字添加到 AssignPrintZeroBlock 类,或者您要将这些类添加到 @ 987654331@文件。?
  • 前者。我已经封装了supertrait,正在考虑是否还要封装继承的case类。
  • 如果您有简单的代码维护,请考虑缺点。 So, is it good practice to seal the case classes as well? 不,密封案例类不是一个好习惯,我们应该只对基类或特征使用密封。 What could go wrong if I don't? 如果这样做,编译器认为所有可能出现在匹配案例中的类(查找 AssignPrint 等的子类)。你仍然会得到相同的结果。
【解决方案3】:

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

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,这是合法的。

这种事情有多重要?可能不是很。在您自己的应用程序代码中,“默认”密封所有案例类可能很好,因为您始终可以“解封”它们。在图书馆中,如果您的某个用户想要做类似上述的事情,我宁愿不密封。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-09-12
    • 2010-09-11
    • 1970-01-01
    • 2017-02-20
    • 2017-06-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多