在 Scala 中对 mixin 成员的封装

encapsulation for mixin's members in Scala

Scala 中的特征可以用作混入和接口。它会导致一些不一致——如果我想关闭 trait 中的某些方法,我就是做不到:

object Library {
    protected trait A { def a: Int = 5 }
    trait B extends A { private override def a: Int = super.a }
    //I want to close `a` memeber for all traits extending B; it's still possible to open it in some another trait `C extends A`, or even `Z extends B with C`
}

// Exiting paste mode, now interpreting.

<console>:10: error: overriding method a in trait A of type => Int;
 method a has weaker access privileges; it should not be private
           trait B extends A { private override def a: Int = super.a }
                                                    ^

LSP 的角度来看,这样的错误完全没问题,因为我(或编译器)可能想将其转换为超类型 A。但是,如果我只是将它用作混音,我实际上永远不需要这样做,例如在某些 Cake-pattern 变体中。我会做类似的事情:

 import Library._
 object O extends B with K with L with App

就是这样。我什至不能在这里访问 trait A 。我知道,类型推断可能会上升到超类型,但它只是一个 "line of types",因此编译器可以跳过 A 并继续(当然这是非常理论化的)。另一个例子 - here 我不得不为方法提供默认实现,我并不真正需要它。

我目前使用的解决方案是 OOP 组合,但它不太灵活(因为线性化在这里不起作用)并且与混合概念不太兼容。我见过的一些项目,他们实际上做混合并且有 "over9000" 冗余和可见成员。几年前有一个想法 "mark" 通过指定 with 关键字而不是 extends 这样的 mixins 组合,但现在甚至找不到那个线程。

那么,ad-hoc成员封装有什么更好的做法吗?

这不是通用的解决方案,但可以在一个模块内关闭来自外部世界的方法:

object Library {
  protected trait A { 
    private[Library] def a: Int = 5 
    private[Library] def b: Int = 7 
  }
  trait B extends A { 
    def b = super.b 
  }
}

import Library._
object C extends B

scala> C.a
<console>:179: error: method a in trait B cannot be accessed in object C
              C.a
                ^
scala> C.b
res131: Int = 7

所以我们只是在这里反转封装。如果 A 也应该开放扩展:

object Library {
  protected trait _A { 
    private[Library] def a: Int = 5 
    private[Library] def b: Int = 7 
  }
  trait B extends A { /*...*/ }
  trait A extends _A {
    override def a = super.a 
    override def b = super.b
  }
}

所以,也许太样板了,但至少它有效。

P.S。这个想法的部分灵感来自另一个无效的已删除答案:)