问题 Scala属性问题


我还在学习Scala,但我认为有趣的一点是Scala模糊了方法和字段之间的界限。例如,我可以构建一个这样的类......

class MutableNumber(var value: Int)

这里的关键是constructor-argument中的var自动允许我像java中的getter / setter一样使用'value'字段。

// use number...
val num = new MutableNumber(5)
num.value = 6
println(num.value)

如果我想添加约束,我可以通过切换到使用方法代替实例字段来实现:

// require all mutable numbers to be >= 0
class MutableNumber(private var _value: Int) {
    require(_value >= 0)

    def value: Int = _value
    def value_=(other: Int) {
        require(other >=0)
        _value = other
    }
}

由于API不会更改,因此客户端代码不会中断:

// use number...
val num = new MutableNumber(5)
num.value = 6
println(num.value)

我的挂机是添加到Scala-2.8的命名参数功能。如果我使用命名参数,我的API  改变它  打破api。

val num = new MutableNumber(value=5)  // old API
val num = new MutableNumber(_value=5) // new API

num.value = 6
println(num.value)

这有什么优雅的解决方案吗?我应该如何设计我的MutableNumber类,以便以后可以在不破坏API的情况下添加约束?

谢谢!


11560
2017-11-11 20:20


起源



答案:


您可以使用与案例类相同的技巧:使用伴随对象。

object Example {
  class MutableNumber private (private var _value: Int) {
    require (_value >= 0)
    def value: Int = _value
    def value_=(i: Int) { require (i>=0); _value = i }
    override def toString = "mutable " + _value
  }
  object MutableNumber {
    def apply(value: Int = 0) = new MutableNumber(value)
  }
}

它在这里工作(并证明,在构造时,你必须使用对象进行创建,因为构造函数被标记为私有):

scala> new Example.MutableNumber(5)
<console>:10: error: constructor MutableNumber cannot be accessed in object $iw
   new Example.MutableNumber(5)
   ^

scala> Example.MutableNumber(value = 2)
res0: Example.MutableNumber = mutable 2

scala> Example.MutableNumber()
res1: Example.MutableNumber = mutable 0

11
2017-11-11 20:38



有趣!因此,通过隐藏构造函数,我强制每个人都使用伴随对象。如果我想让MutableInteger本身成为一个案例类怎么办?我知道如果我只是在类定义的前面加上'case',Scala会自动为我创建伴随对象...这个解决方案是否仍然有效? - shj
是的,如果你有一个明确定义的案例类的伴随对象,那么该对象的成员将合并到生成的对象中(并且可以覆盖,例如,如果你想提供一个调整 Companion.apply() 方法但保持自动生成 unapply)。 - David Winslow
@shj - 不,这不起作用,因为案例类假定直接(没有保护)访问构造函数变量。你这样做是因为你需要警卫(以。的形式) require(_value >= 0) 在这种情况下)。 - Rex Kerr
scala模式匹配是否需要直接访问构造函数?我认为Scala添加伴随对象的原因之一是您不需要显式使用构造函数。在案例课上我不应该使用警卫吗? - shj
@David - 如何覆盖自动生成的apply()方法?当我尝试这样做时,我得到'错误:方法应用被定义两次'。 - shj


答案:


您可以使用与案例类相同的技巧:使用伴随对象。

object Example {
  class MutableNumber private (private var _value: Int) {
    require (_value >= 0)
    def value: Int = _value
    def value_=(i: Int) { require (i>=0); _value = i }
    override def toString = "mutable " + _value
  }
  object MutableNumber {
    def apply(value: Int = 0) = new MutableNumber(value)
  }
}

它在这里工作(并证明,在构造时,你必须使用对象进行创建,因为构造函数被标记为私有):

scala> new Example.MutableNumber(5)
<console>:10: error: constructor MutableNumber cannot be accessed in object $iw
   new Example.MutableNumber(5)
   ^

scala> Example.MutableNumber(value = 2)
res0: Example.MutableNumber = mutable 2

scala> Example.MutableNumber()
res1: Example.MutableNumber = mutable 0

11
2017-11-11 20:38



有趣!因此,通过隐藏构造函数,我强制每个人都使用伴随对象。如果我想让MutableInteger本身成为一个案例类怎么办?我知道如果我只是在类定义的前面加上'case',Scala会自动为我创建伴随对象...这个解决方案是否仍然有效? - shj
是的,如果你有一个明确定义的案例类的伴随对象,那么该对象的成员将合并到生成的对象中(并且可以覆盖,例如,如果你想提供一个调整 Companion.apply() 方法但保持自动生成 unapply)。 - David Winslow
@shj - 不,这不起作用,因为案例类假定直接(没有保护)访问构造函数变量。你这样做是因为你需要警卫(以。的形式) require(_value >= 0) 在这种情况下)。 - Rex Kerr
scala模式匹配是否需要直接访问构造函数?我认为Scala添加伴随对象的原因之一是您不需要显式使用构造函数。在案例课上我不应该使用警卫吗? - shj
@David - 如何覆盖自动生成的apply()方法?当我尝试这样做时,我得到'错误:方法应用被定义两次'。 - shj


感谢你的回答!顺便说一句,我认为Scala人可能会意识到存在一个问题:

Scala 2.8中的新功能:命名和默认参数

... 到目前为止,参数名称对于库开发人员来说有点武断,并且不被认为是API的重要组成部分。这突然发生了变化,因此如果在更高版本中将参数sep重命名为分隔符,则对mkString(sep =“”)的方法调用将无法编译。

Scala 2.9为这个问题实现了一个简洁的解决方案,但是在我们等待的时候,如果他们的名字将来可能发生变化,请谨慎引用名称参数。


2
2017-11-11 22:50



Scala 2.9中的整洁解决方案是什么? - vossad01


class MutableNumber {
    private var _value = 0 //needs to be initialized
    def value: Int = _value
    def value_=(other: Int) {
        require(other >=0) //this requirement was two times there
        _value = other
    }
}

你可以修改花括号内任何类的所有成员

val n = new MutableNumber{value = 17}

2
2017-11-12 23:01



这确实具有为使用大括号的每个MutableNumber实例创建匿名的缺点。 - Ken Bloom