2011年5月3日 星期二

3-14. Singleton 以及 Companion object / class

在 Java 我們有時會使用 static data field,或 static data method 來製作該 class 所有 instance 的共通行為。這並非好現象,因為使用到 class 共通的部份,這將破壞 Object Oriented 的完整性。

在 Java 這些共通的部份,也可能以 class singleton 的方式來表達。class singleton 的意思是該 class 只會有一個 instance,大家共通的 behavior 會以呼叫這個 single instance 的 method 來達成。

製作 singleton 程式時,通常在程式中需要宣告一個 static data field,來保存該 class 這個唯一的 instance。使用 static data field 來放置 singleton object,已幾乎成為共識。
通常我們不會在程式啟動時,就將 「singleton object」 create 出來,會延遲到程式需要該 instance 時才 create 該 instance。這導致另一個麻煩,不可重複 create instance,否則,singleton 的唯一性會被破壞,可能造成其他問題。

我們常用 singleton 來替代「共通需求」,在 Scala,更將這個概念發揮到極致。
在 Scala 每個東西都是一個 object,沒有所謂大家共通的概念,所以共通行為是不可行的。Scala 已將 static 移除,以 singleton 取而代之。S

由於 singleton 的需求眾多,為避免 programmer 沒掌握住 singleton 的 creation,因此 Scala 直接代勞。我們只需宣告 singleton object,Scala 負責幫我們處理該 object 的 creation。

在 Scala 中使用 object 這個 keyword 來宣告 singleton object,讓我們看例子
object S {
  def print(name: String) = println(name)
}
上例宣告一個 object S,請把這樣的宣告看成「在我們的系統中,有一個 object,名叫 S」。這個 object 與「使用變數宣告,然後 create instance 的 object」二者一樣。
讓我們用例子說明
object S { def print(name: String) = println(name)}
var x = "ABC"
上例,我們可以看成「程式有一個 object S,也有一個 object x」,S 與 x 沒有位階上的差異。程式可以直接呼叫 x 做工作,也可呼叫 S 做工作。
object S {
  def print(name: String) = println(name)
}
class UseS {
  def m1(name: String) = S.print(name) // 使用 S 這個 object
}
上例的 UseS 的 m1 method 呼叫 S 這個 object 做事。

技術性來講,object keyword 所做的事是定義一個 class ,並且產生與該 class 同名的一個 instance。所以 object keyword 所定義的東西與使用 class keyword 所定義出來的東西一模一樣,我們不需因為 object 這個 keyword 而有不必要的害怕或想像。

在 Java ,一個 class 可能包含 static 與 non-static 的部份。
若你將該 Java class 改寫成 Scala,原來 static 的部份,我們應該把它放入 object 的宣告,non-static 的部份我們應該把它放入 class 的宣告中。
原來的 Java class
class C {
  static void m_static(){System.out.println("static_m");}
  void m_instance(){System.out.println("instance_m");}
}
改寫成的 Scala class
object C {
  def m_static = println("static_m")
}
class C {
  def m_instance() = println("instance_m")
}

同名的 class 與 object 關係非常密切, Scala 把他們互稱為 companion(陪伴)。所以 class C 是 object C 的 companion class,object C 是 class C 的 companion object。

object keyword 所做的事,比只產生該 class 的 instance 還多
我們舉以下的例子說明
object S {
  def print(name: String) = println(name)
}
1. object keyword 主要會產生一個 companion object 的 classfile,companion object 的名稱,是原名稱後面加上$。比如上例,object S 的宣告會產生 S$ 的 classfile

2. 若程式沒宣告同名的 class(即沒有 companion class),object keyword 自動會產生 companion class 的 classfile。

companion class 具有 companion object 中的所有 method,但會變成 static method,這些 method 會 forward 給 companion object 對應的 method。

如上例,object S 會產生 S 的 classfile,且 S classfile 中有 static print 這個 method。這個 method 會 forward 給 S$ 的 print。

產生 companion class 對應的 static method,主要原因是讓 Java 程式可以使用 companion class 的 static method 呼叫到 Scala 中 singleton object 的 method,這樣一來 singleton object 扮演 static 角色的感覺就更加強烈。

範例:只有 object,沒有 companion class時

可以清楚看到 object S2,產生S2$.class與S2.class。S2$.class 是 object S的本體。S2.class 將 S2$.class 的 method 放置一份過去,並且變為 static。














範例:有 object,也有 companion class時

class S2 產生 S2.class。
object S2 產生 S2$.class,且將一些 method 放入 S2.class。


我們仔細觀看 S2.class,S2有 m1 method,這是 S2 本身定義的。S2也有 sayHello,這是 object S2定義的 method。

在本例中 S2.class 的 m1 method,呼叫 object S2 的 syaHello method,我們來看如何呼叫

在 m1 method 中,可以很清楚看到呼叫 S2$.sayHello,印證 object S2 被編譯成 S2$。

對於 singleton 我們還有一點需要特別加以說明,在 OO 裡面,static method 是不能被 override 的,這個你應該要認識,因為 override 是 instance 才會有的動作,在 static level 做 override 是沒有意義的。有些語言,甚至將 static method 編譯為 final,表示不能再被 override。

Java 的 static method 也是同樣的情況,不能被 override 的。
若你在 subclass 宣告一個與 parent class 相同名稱的 static method,Java 允許你這樣做,可是其實是危險的。因為 Java compiler 其實認定你在 subclass 重新宣告了一個新的 method,只是名稱與 parent class 的相同,所以效果並不是 override。下例中,我們使用 @Override 來驗證這個推論。
範例:
class J {
  static void m1() {
    System.out.println("J.m1");
  }
  static void m2() {
    System.out.println("J.m2");
  }
}
class J11 extends J1 {
  static void m1() { // 注意本 method 並非 override J.m1,真正的效果與定義一個新的 method 完全相同
    System.out.println("J11.m1");
  }
  @Override //我們使用 @Override 來宣告要 override J.m2,這裡會產生 compile error。
  static void m2() {
    System.out.println("J11.m2");
  }
}
Singleton object 主要是處理原來 Java 所需要的 static 的部份,以剛剛的論述我們知道 static 的部份不應該加以 override,或者說 static 的部份做繼承是沒有意義的動作。基於這個理由,Scala 的 singleton object,也是不能繼承的,這件事你應該樣記住。
object S
class S1 extends S//class 繼承 object,錯誤!!
object S
object S1 extends S//object 繼承 object,錯誤!!
注意:但 object 可以繼承 class。這是因為 object 取的是 parent class 的 instance 部份,當然允許繼承
class S
object S1 extends S//object 繼承 class,這是很正確的作法!!

沒有留言:

張貼留言