2010-11-11 6 views
11

Ich lerne immer noch Scala, aber eine Sache, die ich interessant fand, ist, dass Scala die Grenze zwischen Methoden und Feldern verwischt. Zum Beispiel habe ich eine Klasse wie folgt bauen ...Scala Eigenschaften Frage

class MutableNumber(var value: Int) 

Der Schlüssel hier ist, dass der var im Konstruktor-Argumente automatisch erlaubt es mir, das ‚Wert‘ Feld wie ein Getter/Setter in Java zu verwenden.

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

Wenn ich Einschränkungen hinzufügen möchten, kann ich so durch die Umstellung auf Methoden anstelle der instanz Feldern:

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

Die Client-Seite Code nicht bricht, da die API doesn 't change:

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

Mein Auflegen ist mit der Named-Parameter-Funktion, die Scala-2.8 hinzugefügt wurde. Wenn ich benannte Parameter verwende, ändert sich meine API und es tut brechen die API.

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

num.value = 6 
println(num.value) 

Gibt es eine elegante Lösung? Wie sollte ich meine MutableNumber-Klasse entwerfen, damit ich später Einschränkungen hinzufügen kann, ohne die API zu unterbrechen?

Danke!

Antwort

11

Sie können den gleichen Trick verwenden, den Fallklassen verwenden: Verwenden Sie ein Begleitobjekt.

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

Und hier ist es funktioniert (und was zeigt, dass, wie es aufgebaut ist, können Sie das Objekt für Kreationen verwenden müssen, da der Konstruktor privat markiert):

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

Interessante ändern! Indem ich den Konstruktor verberge, zwinge ich alle dazu, das Companion-Objekt zu verwenden. Was, wenn ich MutableInteger selbst zu einer Fallklasse machen wollte?Ich weiß, dass Scala, wenn ich nur 'case' vor die Klassendefinition setze, automatisch das Begleitobjekt für mich erstellt ... würde diese Lösung noch funktionieren? – shj

+0

Ja, wenn Sie ein explizit definiertes Companion-Objekt für eine Fallklasse haben, werden die Elemente dieses Objekts mit dem generierten Objekt verschmolzen (und können überschrieben werden, wenn Sie beispielsweise eine optimierte Methode 'Companion.apply()' bereitstellen möchten aber behalte das automatisch generierte 'unapply'). –

+0

@shj - Nein, das würde nicht funktionieren, weil Fallklassen direkten (nicht geschützten) Zugriff auf die Konstruktorvariablen annehmen. Sie tun dies, weil Sie Wächter (in diesem Fall 'require (_value> = 0)) wollen. –

2

Danke für die Antwort! Als Nebenwirkung, ich glaube, die Scala-Jungs bewusst sein, könnte das ein Problem gibt es:

Was ist neu in Scala 2.8: Benannte und Standardparameter

... Bisher waren die Namen der Argumente eine etwas willkürliche Wahl für Bibliotheksentwickler und wurde nicht als wichtiger Teil der API betrachtet. Dies hat sich plötzlich geändert, so dass ein Methodenaufruf an mkString (sep = "") nicht kompiliert werden kann, wenn das Argument sep in einer späteren Version in separator umbenannt wurde.

Scala 2.9 implementiert eine saubere Lösung für dieses Problem, aber während wir darauf warten, seien Sie vorsichtig bei der Argumentation nach Namen, wenn sich ihre Namen in der Zukunft ändern könnten.

+0

Was ist die saubere Lösung in 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 
    } 
} 

können Sie alle Mitglieder einer Klasse in geschweiften Klammern

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

Das hat den Nachteil, dass für jede Instanz von MutableNumber, die geschweifte Klammern verwendet, ein anonymes Objekt erstellt wird. –