Class 在OOP裡面是Encapsulation最基本的原件, 也就是物件, 在Scala世界中既保有JAVA物件模型, 又更進階的加上了Mutable & Immutable 概念, 更甚者還有Function 物件來當朋友, 在與Functional演化與變異過程中, 要如何保有原先物件的優點, 這種DNA的 Design是你該好好觀察的
Primary constructor
上圖的Person class宣告跟以往在JAVA or C# 有所差異; Person不僅宣告了class也宣告了primary constructor, 透過Constructor去建造物件, 這個概念很基本, 而Scala 只是把Primary constructor的code 融入在class宣告中, 所以在class name後面的參數列, 就是Primary constructor需要的參數.
在Person Primary constructor的參數列中, 也要指定該參數是val or var, Scala就會幫你實體化該參數, 就可以進行操作
scala> val person = new Person("Tom", 25)
pserson: Person = Person@561279c8
scala> person.name
res0: String = Tom
scala> person.age
res1: Int = 25
如同JAVA一樣, 還是透過new keyword去實體化一個物件, 但有一個很顯眼的問題, 沒有class body !? 在Scala中class body是optional 可有可無, 不要覺得這樣的class很空泛, 無法實體化, 就正因為已經把Primary constructor嵌入在class宣告, 因此Scala就可以透過Primary constructor去建構Person物件
在Primary constructor參數列中的var & val 其實可以省略, 這樣的話該參數就會變成private instance, 無法被外界存取, 且該參數本身還是val
scala> class Person(name:String)
defined class Person
scala> val p1 = new Person("Tom")
p1: Person = Person@4089f3e5
scala> p1.name
<console>:7: error: value name is not a member of Person
p1.name
Getter and Setter
Scala的class中預設是幫你產生好Getter & Setter, 你可以直接用變數名稱存取, 就像Public property一般, 但實際上在很多情況下可以直接存取物件中的變數是不好的, 會在不知道的情況下錯誤改變程序, 產生Bug. 這時候你可以在變數前面加上Private keyword , 這時候就不會自動產生Getter & Setter, 你可以依照你的需求自己增加.
class Person() { // Private age variable, renamed to _age private var _age = 0 var name = "" // Getter def age = _age // Setter def age_= (value:Int):Unit = _age = value }
Getter 很簡單明瞭, 你直接回傳 _age 變數即可
等等....為什麼age變數名稱要改為 _age ?
這是Scala Setter的一個trick, 底線有特殊作用
等等....為什麼age變數名稱要改為 _age ?
這是Scala Setter的一個trick, 底線有特殊作用
第一點你該知道的是 Setter method name是age=, 而"_"底線在這裡的特殊作用是, "_"底線允許在method name中間有空白字元, 所以"age="可變成"age =" (就是"age空白="), 因此Setter可以從p.age_=(10) 變成p.age = 10 (當Function參數列只有一個參數的時候, 括號可省略, 這裡Setter一定只有單一參數, 就是你要傳進來要Set的值) 這種Trick讓Setter看起來更直覺, 與一般的Setter method call一樣
當然 this function的這個Overload constructor也不可以有任何回傳值, 再者你還得在第一行先call 其他Overload constructor or Primary constructor, 不然會有Compile error , 如下
這是因為在單一繼承中要確保, 最後的實體化路徑要走回到Primary constructor去正確執行
儘管如此 Scala 還是有別的辦法去解決這個問題, 這就得看看另一個設計概念 : Companion Object, 後續章節會解說
屬性(Field)和方法(Method)這個都已經知道, 比較特殊是Inline primary constructor code, 可以看看以下例子
在class body中直接嵌入code, 去判斷初始人名是否為空白, 可以看出這些inline code會被compiler放到Primary constructor中執行. 所以在Primary constructor中初始化的code, 就可以直接放在class body中, 不需要再開一個 code block. 這是一個更直覺的設計方式
下一章OOP in Scala 2, Packaging & Import
Overload Constructor
在Scala中要應用Overload constructor的話, 可以透過this名稱宣告, 宣告的方式跟Function一樣class Person(val name:String, val age:Int) { def this() = this("no name", 18) }
當然 this function的這個Overload constructor也不可以有任何回傳值, 再者你還得在第一行先call 其他Overload constructor or Primary constructor, 不然會有Compile error , 如下
class Person(val name:String, val age:Int) { def this() = { val defaultName = "Bob" val defaultAge = 5 this(name, age) } }
Person.scala:3: error: 'this' expected but 'val' found.
val defaultName = "Bob"
^
Person.scala:4: error: '(' expected but ';' found.
val defaultAge = 5
^
two errors found
這是因為在單一繼承中要確保, 最後的實體化路徑要走回到Primary constructor去正確執行
儘管如此 Scala 還是有別的辦法去解決這個問題, 這就得看看另一個設計概念 : Companion Object, 後續章節會解說
Class body
儘管class body可有可無, 但大多情況我們還是會需要, Scala的class body跟JAVA class一樣有這些東西:- Field
- Method
- Inline primary constructor code
屬性(Field)和方法(Method)這個都已經知道, 比較特殊是Inline primary constructor code, 可以看看以下例子
class Person(val name: String) { if(name.length <= 0 ) print("require name!!") //methods.... //fields..... }
在class body中直接嵌入code, 去判斷初始人名是否為空白, 可以看出這些inline code會被compiler放到Primary constructor中執行. 所以在Primary constructor中初始化的code, 就可以直接放在class body中, 不需要再開一個 code block. 這是一個更直覺的設計方式
下一章OOP in Scala 2, Packaging & Import
沒有留言:
張貼留言