在 GitHub 上编辑此页面

开放类

类上的 open 修饰符表示该类计划进行扩展。示例

// File Writer.scala
package p

open class Writer[T]:

  /** Sends to stdout, can be overridden */
  def send(x: T) = println(x)

  /** Sends all arguments using `send` */
  def sendAll(xs: T*) = xs.foreach(send)
end Writer

// File EncryptedWriter.scala
package p

class EncryptedWriter[T: Encryptable] extends Writer[T]:
  override def send(x: T) = super.send(encrypt(x))

开放类通常带有描述类的方法之间的内部调用模式以及可以覆盖的钩子的文档。我们称之为类的扩展契约。它不同于类及其用户之间的外部契约

非开放类仍然可以扩展,但前提是满足两个备选条件之一

  • 扩展类与被扩展类在同一个源文件中。在这种情况下,扩展通常是一个内部实现问题。

  • 扩展类启用了语言特性 adhocExtensions。这通常通过扩展源文件中的导入子句启用

    import scala.language.adhocExtensions
    

    或者,可以通过编译器选项 -language:adhocExtensions 启用此特性。如果未启用此特性,编译器将发出“特性”警告。例如,如果类 Writer 上的 open 修饰符被删除,编译 EncryptedWriter 将产生警告

    -- Feature Warning: EncryptedWriter.scala:6:14 ----
      |class EncryptedWriter[T: Encryptable] extends Writer[T]
      |                                              ^
      |Unless class Writer is declared 'open', its extension
      | in a separate file should be enabled
      |by adding the import clause 'import scala.language.adhocExtensions'
      |or by setting the compiler option -language:adhocExtensions.
    

动机

在编写类时,有三种可能的可扩展性预期

  1. 该类允许扩展。这意味着应该为该类制定一个精心制定且有据可查的扩展契约。

  2. 禁止扩展该类,例如为了做出正确性或安全性保证。

  3. 无论哪种方式,都没有做出明确的决定。该类先验不打算扩展,但如果其他人发现以临时方式扩展很有用,就让他们继续。但是,在这种情况下,他们只能靠自己。没有记录的扩展契约,该类的未来版本可能会破坏扩展(例如,通过重新排列内部调用模式)。

通过对 (1) 使用 open,对 (2) 使用 final,对 (3) 不使用修饰符,可以清楚地区分这三种情况。

最好避免在代码库中进行临时扩展,因为它们往往会导致难以发展的脆弱系统。但仍然有一些情况,这些扩展是有用的:例如,在测试中模拟类,或应用添加功能或修复库类中的错误的临时补丁。这就是允许临时扩展的原因,但前提是通过语言特性导入明确选择加入。

详细信息

  • open 是软修饰符。它被视为普通标识符,除非它处于修饰符位置。
  • open 类不能是 finalsealed
  • 特性或 abstract 类始终是 open,因此 open 对它们来说是多余的。

sealed 的关系

既不是 abstract 也不是 open 的类类似于 sealed 类:它仍然可以扩展,但只能在同一源文件中扩展。不同之处在于,如果尝试在另一个源文件中扩展类,将会发生什么。对于 sealed 类,这是一个错误,而对于简单的非开放类,只要启用了 adhocExtensions 特性,仍然允许这样做,否则会发出警告。

迁移

open 是 Scala 3 中的新修饰符。为了在 Scala 2.13 和 Scala 3.0 之间进行交叉编译而不发出警告,只有在 -source future 下才会针对临时扩展发出特性警告。它将从 Scala 3.4 开始 默认生成。