2010-11-11 15 views
11

Todavía estoy aprendiendo sobre Scala, pero una cosa que me pareció interesante es que Scala difumina la línea entre métodos y campos. Por ejemplo, puedo construir una clase como esta ...Propiedades de Scala Pregunta

class MutableNumber(var value: Int) 

La clave aquí es que el var en el constructor-argumento permite automáticamente que yo use el campo 'valor' como un captador/definidor en Java.

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

Si quiero agregar restricciones, puedo hacerlo por el cambio a usar métodos en lugar de la instancia-campos:

// 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 
    } 
} 

El código del lado del cliente no se rompe desde la API doesn 't change:

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

Mi cuelgue es con la función de parámetro nombrado que se agregó a Scala-2.8. Si uso named-parameters, mi API cambia y hace rompe la API.

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

num.value = 6 
println(num.value) 

¿Hay alguna solución elegante para esto? ¿Cómo debería diseñar mi clase MutableNumber para poder agregar restricciones más adelante sin romper la API?

Gracias!

Respuesta

11

Puede utilizar el mismo truco que las clases case: usar un objeto complementario.

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) 
    } 
} 

Y aquí se está trabajando (y demostrando que, tal como se construyó, se debe utilizar el objeto de creaciones, ya que el constructor se marca privada):

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 
+0

Interesante! Así que al esconder el constructor obligo a todos a usar el objeto compañero. ¿Qué sucede si quiero hacer que MutableInteger sea una clase de caso?Sé que si coloco 'caso' delante de la definición de clase, Scala crea automáticamente el objeto complementario para mí ... ¿esta solución funcionaría? – shj

+0

Sí, si tiene un objeto complementario explícitamente definido para una clase de caso, los miembros de ese objeto se fusionarán en el generado (y puede anular, si, por ejemplo, desea proporcionar un método 'Companion.apply()' ajustado) pero mantén el autogenerado 'unapply'). –

+0

@shj - No, eso no funcionaría porque las clases de casos suponen un acceso directo (no protegido) a las variables del constructor. Estás haciendo esto porque quieres guardias (en la forma de 'require (_value> = 0)' en este caso). –

2

Gracias por la respuesta! Como acotación al margen, creo que los Scala-chicos podrían ser conscientes de que hay un problema:

¿Qué hay de nuevo en Scala 2.8: El nombre y los parámetros por defecto

... Hasta ahora, los nombres de los argumentos eran una elección un tanto arbitraria para los desarrolladores de bibliotecas, y no se consideraron una parte importante de la API. Esto ha cambiado repentinamente, por lo que una llamada a método a mkString (sep = "") no se compilará si el argumento sep se renombró a separador en una versión posterior.

Scala 2.9 implementa una solución clara para este problema, pero mientras esperamos eso, tenga cuidado al referirse a argumentos por nombre si sus nombres pueden cambiar en el futuro.

+0

¿Cuál es la solución ordenada en Scala 2.9? – vossad01

2
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 
    } 
} 

puede modificar todos los miembros de cualquier clase dentro de llaves

val n = new MutableNumber{value = 17} 
+0

Eso tiene la desventaja de crear un anónimo para cada instancia de MutableNumber que use llaves. –