2010-11-11 21 views
11

J'apprends encore Scala, mais une chose que j'ai trouvée intéressante est que Scala brouille la frontière entre les méthodes et les champs. Par exemple, je peux construire une classe comme ça ...Propriétés de Scala Question

class MutableNumber(var value: Int) 

La clé ici est que le var dans le constructeur argument me permet automatiquement d'utiliser le champ « valeur » comme un getter/setter en java.

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

Si je veux ajouter des contraintes, je peux le faire en passant à l'aide de méthodes en place de l'instance champs:

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

Le code côté client ne se casse pas depuis l'API doesn « t changer:

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

Mon raccrochage est avec la fonction de paramètre nommé qui a été ajouté à Scala 2.8. Si j'utilise named-parameters, mon API change et le code casse l'API.

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

num.value = 6 
println(num.value) 

Y at-il une solution élégante à cela? Comment dois-je concevoir ma classe MutableNumber pour pouvoir ajouter des contraintes plus tard sans casser l'API?

Merci!

Répondre

11

Vous pouvez utiliser la même astuce que les classes de cas: utiliser un objet compagnon.

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

Et là, il travaille (et démontrer que, tel que construit, vous devez utiliser l'objet pour les créations, puisque le constructeur est marqué comme privé):

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

Intéressant! Donc, en cachant le constructeur, je force tout le monde à utiliser l'objet compagnon. Et si je voulais faire de MutableInteger un cas particulier?Je sais que si je mets simplement 'case' devant la définition de classe, Scala crée automatiquement l'objet compagnon pour moi ... cette solution fonctionnerait-elle toujours? – shj

+0

Oui, si vous avez un objet compagnon explicitement défini pour une classe de cas, alors les membres de cet objet seront fusionnés dans celui-ci (et peuvent remplacer, si par exemple vous voulez fournir une méthode 'Companion.apply()' modifiée mais gardez l'autogenerated 'unfapply'). –

+0

@shj - Non, cela ne fonctionnerait pas car les classes de cas assument un accès direct (non gardé) aux variables du constructeur. Vous faites cela parce que vous voulez des gardes (sous la forme de 'require (_value> = 0)' dans ce cas). –

2

Merci pour la réponse! En aparté, je pense que les Scala-les gars pourraient être conscients qu'il ya un problème:

Quoi de neuf dans Scala 2.8: Paramètres nommés et par défaut

... Jusqu'à présent, les noms des arguments ont été un choix quelque peu arbitraire pour les développeurs de bibliothèques, et n'étaient pas considérés comme une partie importante de l'API. Cela a soudainement changé, de sorte qu'un appel de méthode à mkString (sep = "") échouera à compiler si l'argument sep a été renommé en séparateur dans une version ultérieure. Scala 2.9 implémente une solution soignée à ce problème, mais pendant que nous attendons cela, soyez prudent de se référer aux arguments par nom si leurs noms peuvent changer dans le futur.

+0

Quelle est la solution soignée dans 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 
    } 
} 

vous pouvez modifier tous les membres d'une classe entre accolades

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

Cela a l'inconvénient de créer un anonyme pour chaque instanciation MutableNumber qui utilise des accolades. –