限制可以扩展scala特征的类

时间:2018-09-19 11:46:05

标签: scala inheritance traits self-type

似乎有三种(或更多)方法可以限制哪些类可以混入给定的scala特征:

  1. 使用共同祖先[trait]
  2. 使用抽象声明
  3. 在特征中使用自我类型

通用祖先方法需要其他限制,而且似乎不是最佳选择。同时,自定义和抽象声明似乎是相同的。有人愿意解释这些区别和用例(尤其是2和3之间的用例)吗?

我的例子是:

val exampleMap = Map("one" -> 1, "two" -> 2)
class PropsBox (val properties : Map[String, Any])    

// Using Common Ancestor
trait HasProperties {
  val properties : Map[String, Any]
}
trait KeysAsSupertype extends HasProperties {
  def keys : Iterable[String] = properties.keys
}
class SubProp(val properties : Map[String, Any]) extends HasProperties
val inCommonAncestor = new SubProp(exampleMap) with KeysAsSupertype
println(inCommonAncestor.keys)
// prints: Set(one, two)


// Using Abstract Declaration
trait KeysAsAbstract {
  def properties : Map[String, Any]
  def keys : Iterable[String] = properties.keys
}
val inAbstract = new PropsBox(exampleMap) with KeysAsAbstract
println(inSelfType.keys)
// prints: Set(one, two)    


// Using Self-type
trait KeysAsSelfType {
  this : PropsBox => 
  def keys : Iterable[String] = properties.keys
}
val inSelfType = new PropsBox(exampleMap) with KeysAsSelfType
println(inSelfType.keys)
// prints: Set(one, two)    

1 个答案:

答案 0 :(得分:3)

在您的示例中,PropsBox并未对properties施加任何有趣的约束-它仅具有成员properties: Map[String, Any]。因此,无法检测到从PropsBox继承和仅需要def properties: Map[String, Any]继承之间的区别。

请考虑以下示例,其中实际存在差异。假设我们有两个类GoodBoxBadBox

  1. GoodBox具有properties,并且所有键都是仅包含数字的短字符串
  2. BadBox仅包含properties,不能保证键的结构

在代码中:

/** Has `properties: Map[String, Any]`, 
  * and also guarantees that all the strings are
  * actually decimal representations of numbers 
  * between 0 and 99.
  */
class GoodBox(val properties: Map[String, Any]) {
  require(properties.keys.forall {
    s => s.forall(_.isDigit) && s.size < 3
  })
}


/** Has `properties: Map[String, Any]`, but 
  * guarantees nothing about the keys.
  */
class BadBox(val properties: Map[String, Any])

现在假设由于某种原因,我们希望将Map[String, Any]转换为人口稀疏的Array[Any],并使用键作为数组索引。同样,这里有两种方法可以做到这一点:一种带有self类型的声明,另一种带有抽象的def properties成员声明:

trait AsArrayMapSelfType {
  self: GoodBox =>
  def asArrayMap: Array[Any] = {
    val n = 100
    val a = Array.ofDim[Any](n)
    for ((k, v) <- properties) {
      a(k.toInt) = v
    }
    a
  }
}

trait AsArrayMapAbstract {
  def properties: Map[String, Any]
  def asArrayMap: Array[Any] = {
    val n = 100
    val a = Array.ofDim[Any](n)
    for ((k, v) <- properties) {
      a(k.toInt) = v
    }
    a
  }
}

现在尝试一下:

val goodBox_1 = 
  new GoodBox(Map("1" -> "one", "42" -> "fourtyTwo"))
  with AsArrayMapSelfType

val goodBox_2 = 
  new GoodBox(Map("1" -> "one", "42" -> "fourtyTwo"))
  with AsArrayMapAbstract

/* error: illegal inheritance
val badBox_1 = 
  new BadBox(Map("Not a number" -> "mbxkxb"))
  with AsArrayMapSelfType
*/

val badBox_2 = 
  new BadBox(Map("Not a number" -> "mbxkxb"))
  with AsArrayMapAbstract

goodBox_1.asArrayMap
goodBox_2.asArrayMap
// badBox_1.asArrayMap - not allowed, good!
badBox_2.asArrayMap // Crashes with NumberFormatException, bad

使用goodBox,这两种方法都将起作用并产生相同的结果。但是,对于badBox,自类型与抽象定义的行为有所不同:

  1. 自类型版本不允许编译代码(在编译时捕获错误)
  2. 抽象定义版本在运行时因NumberFormatException而崩溃(运行时发生错误)

那是区别。