本文由 Rhyme 發表在 ScalaCool 團隊部落格。
object
是一種讓靜態迴歸常態、打破模式、天然的語言特性。
其實在寫這篇文章之前,我思緒萬千,遲遲不能落筆,總想著自己會不會遺漏了某個知識點,或者有講得不太那麼準確的地方,但是後來我想明白了,學習一樣東西,最重要的並不是要了解它的每一個細節,而是要了解它的核心思想。如果你能夠理解上面講的那句話,我想你或許也就掌握了object
。
其實,object
較之Java,更多的是一種思想設計理念上的昇華,就如同我本來就該有這東西,你卻需要我手動費很大地勁自己造出來一樣。
在object
當中,我們可以輕鬆的構建出一個「單例物件」,而在Java當中我們通常得遵守一套「單例模式」的應用才能構建出一個單例物件。但是請你細想一番,「單例物件」難道不該是一種更加自然的存在嗎?
另外,和「單例物件」一樣,object
當中的另一個重量級的語言特性:「伴生物件」也同樣遵循這一套邏輯,讓「靜態」以一種更為常態的形式存在。
單例物件(Singleton Object)
// 構建一個單例物件Singleton
object Singleton {
var hello = "Hello World"
def sayHello:String = {
hello
}
}
複製程式碼
在Scala中,我們使用關鍵字object
來構建單例物件。任何用到單例模式的地方,你都可以用Scala的object
來實現。以下程式碼向你展示了在Scala中單例物件的基本使用。我們只需要在我們用到的地方引入我們需要的「單例物件」即可。
import Singleton._ //引入Singleton物件中的所有方法
class Test {
def test:String = sayHello
}
複製程式碼
伴生物件(Companion Object)
在Java中,我們通常會用到既有例項方法又有靜態方法的類,在Scala中我們可以通過「伴生類」和「伴生物件」來實現。剛開始,你可能會對這兩個詞感到困惑,其實不用慌張,你完全可以在一開始用Java中的普通的不包含靜態的類來代替「伴生類」,用類中的所有的靜態來代替「伴生物件」。說的簡單一點,就是將Java類中的靜態搬到一個單例物件中。
下面分別用Java和伴生物件來實現下面這個簡單的售票功能。
使用Java實現的這段程式碼,這裡就不再多說,我想你肯定比我還要熟悉。
public class TicketJava {
private static int ticket = 10;
private String name;
public TicketJava(String name){
this.name = name;
}
public static void sellTicketTo(String name){
System.out.println(name + " get ticket " + ticket);
}
public void buyTicket() {
sellTicketTo(name);
}
}
複製程式碼
以下是使用「伴生物件」實現的版本。你只需要關注一個細節,靜態所在的位置。
與類同名且與類在同一個原始檔下的object
物件就是「伴生物件」,與之相對的類就叫做「伴生類」。
伴生物件與靜態實現的功能沒有二異,原來靜態所具備的特性,在「伴生物件」中都能找到。在Java中,靜態特性包裹在Class類中,它們可以互訪私有特性,因此你也就能明白接下來的這句話:「伴生物件」與「伴生類」能夠互訪私有特性。
class Ticket(name: String) {// name 是類Ticket的成員變數
// 在Scala中import語句可以寫在任意地方
// 因此你可以控制import語句的作用範圍,使得程式碼更加靈活
import Ticket._
def buyTicket(): Unit = {
// 呼叫伴生物件中的方法
sellTicketTo(name)
}
}
// 伴生物件,和類同名,且在同一個原始檔下,可以互訪私有屬性
object Ticket {
private var ticket: Int = 10
def sellTicketTo(name: String): Unit = {
println(f"$name get ticket $ticket")
ticket = ticket - 1
}
}
複製程式碼
擴充套件類或特質的物件
特質:可以理解為Java中的介面
在Scala中,所有用object修飾的都是「單例物件」,我們以後將用「單例物件」代替Scala中的物件這個概念。
關於單例物件,你需要記住一個核心的理念:共享。單例物件、單例模式,所有和單例相關的都離不開共享這個概念。或者說的更準確一點,是共享物件。
物件在本質上可以具備類的所有特性,因為類是物件的模板,物件不過是類的一個例項而已。而類有一個重要的特性:繼承或實現。在Scala中我們會將繼承或實現稱作擴充套件類或特質。擴充套件使得類能夠進行更好的抽象。在Java中我們通常會對類執行擴充套件操作,但是在Scala中,自從單例物件被作為一種更常態的特性,單例物件也可以擴充套件類或特質來實現更多的功能。其中一個很典型的應用就是藉助單例物件的擴充套件來表示一些公共的狀態。
例如以下程式碼:
我們可以通過用「單例物件」繼承指定的抽象結構來共享一些預設的狀態和物件。其實,關鍵的核心思想還是兩個字共享
abstract class Status(var status: Int) {
def operation()
}
object Ok extends Status(200) {
override def operation(): Unit = println("Ok operation")
}
object Error extends Status(500) {
override def operation(): Unit = println("Error operation")
}
複製程式碼
與此類似的,我們通過繼承Enumeration
介面可以在Scala中實現列舉的功能。Scala預設沒有實現列舉功能的。具體的這裡不進行講述,感興趣的可以上網進行查詢。
總而言之,凡是有共享這個概念的地方,你都可以考慮使用「單例物件」來實現。
apply方法
首先來看一段程式碼
new Array[Person](new Person("Rhyme"),new Person("Captain"))
Array[Person](Person("Rhyme"),Person("Captain"))
複製程式碼
你覺得哪種形式更加簡潔直觀呢?第一種方式這裡不再解釋,第二種方式,你會發現,我們省去了new
關鍵字。
我們先不管具體的語言特性,單從程式碼本身來看,你會更傾向於哪一種?
很明顯,我會選擇第二種省去new
關鍵字的方式。原因很簡單:更加自然,更加簡潔。首先,作為同行,想必new
關鍵字我們再熟悉不過了。敲了它不下一萬遍有沒有!敲了這麼多,你就沒有感覺過煩嗎?
其實煩這個字很關鍵,一旦你寫某段程式碼寫得煩了,就說明這段程式碼本身就是存在題的。然而如果你覺得它並沒有什麼,那隻能說明你已經麻木了,作為一個好的程式設計師,不應該麻木,我們要想法設法,寫最關鍵的程式碼。
首先作為一個全民公認的new
操作,為什麼我不可以將new
這個關鍵字從類名前面移去呢?注意我們關注的應該是最關鍵的程式碼。new
關鍵字對我來說,已經是再熟悉不過的東西了,它對我已經沒有價值了,就讓他預設存在就好了,我不需要再看到它!
而這種可以幫你省去new
操作的語言特性我們就可以使用apply
方法來實現。
我們通常會在伴生物件中定義一個apply()
方法,例如如下程式碼:
class Person(name: String) {
}
object Person {
def apply(name: String):Person = new Person(name)
}
複製程式碼
apply
方法將為我們返回一個「伴生類」的物件。當我門在執行Person("Rhyme")
的時候,預設就會呼叫「伴生物件」中的apply()
放回,併為你返回一個建立好的物件。如果你不想使用apply()
方法,自己定義一個更適合自己的工廠方法也是一個不錯的選擇。
class Person(name: String) {
}
object Person {
def createPerson(name:String):Person = {
new Person(name)
}
}
複製程式碼
迴歸常態
其實,以上這些特性,你發現沒有,Scala在底層的技術實現上,並沒有做太多的改變,將這些Scala程式碼編譯,用javap來檢視編譯之後的程式碼,你會發現它利用的依然是原先Java中的那些程式碼邏輯,只不過它一直在做優化,一直在簡化,讓單例,靜態,物件建立等語言特性迴歸它們應有的常態,讓他們成為一種天然的語言特性存在著,就像魚兒之於大海一樣自然的存在著。
最後,再讓我們回到最開始講的那句話:object
是一種讓靜態迴歸常態、打破模式、天然的語言特性。 仔細再品味品味,你是不是又有一些新的體會呢!
在下一篇中,我們會繼續探索object更加具體的應用,比如如何去改造Java傳統的工廠方法和模式。