2014年6月3日 星期二

OOP in Scala 1, Classes and constructors

前言
Class 在OOP裡面是Encapsulation最基本的原件, 也就是物件, 在Scala世界中既保有JAVA物件模型, 又更進階的加上了Mutable & Immutable 概念, 更甚者還有Function 物件來當朋友, 在與Functional演化與變異過程中, 要如何保有原先物件的優點, 這種DNA的 Design是你該好好觀察的

最基本的class宣告:

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, 底線有特殊作用
第一點你該知道的是 Setter method name是age=, 而"_"底線在這裡的特殊作用是, "_"底線允許在method name中間有空白字元, 所以"age="可變成"age =" (就是"age空白="), 因此Setter可以從p.age_=(10) 變成p.age = 10 (當Function參數列只有一個參數的時候, 括號可省略, 這裡Setter一定只有單一參數, 就是你要傳進來要Set的值)  這種Trick讓Setter看起來更直覺, 與一般的Setter method call一樣

 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一樣有這些東西:
  1. Field
  2. Method
  3. 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

沒有留言:

張貼留言