本文由 Captain 發表在 ScalaCool 團隊博客。java
在上篇 Java 到 Scala 系列中,我想你或多或少在語言特性上對object
有了必定的掌握,在瞭解完它酷酷的語言特性——讓靜態迴歸常態並能簡單運用其衍生出的方法後,我今天就來談談在現實應用方面本身對它的理解,不知道是否是也會給你一種耳目一新的感受,畢竟「單例對象」做爲一種自然的語言特性,華而不實並非咱們想看到的。git
咱們已經知道了object
是做爲打破靜態而存在的「單例對象」,在 Scala 中,「單例對象」使用頻率之高能夠和 Java 中的 new 關鍵詞相比,又或是 Spring 中DI(Dependency Injection),因此咱們不得不考慮到一些場景——多線程和性能開銷。如今就具體來看看它和 Java 實現的單例模式有什麼不一樣。github
先來看看 Java 對於單例模式的實現:編程
public class UniqueSingleton {
//類加載時就初始化
private static uniqueSingleton instance = new uniqueSingleton();
private UniqueSingleton() {
System.out.println(("UniqueSingleton is created"));
}
public static UniqueSingleton getInstance() {
return instance;
}
}
複製代碼
單例模式就靠以上幾行代碼實現了,就是這麼簡單。可是餓漢模式有這麼一個缺點,不管你有沒有調用它,它在 JVM 加載類這個過程當中都會將單例加載好,因此它並不具有惰性傳值(在 Java 中即延遲加載的概念)這個特性。設計模式
public class UniqueSingleton {
//類加載時並未初始化
private static uniqueSingleton instance;
private UniqueSingleton() {
System.out.println(("UniqueSingleton is created"));
}
public static UniqueSingleton getInstance() {
if (instance == null) {
instance = new UniqueSingleton();
}
return instance;
}
}
複製代碼
爲了解決這個問題,咱們天然得考慮到延遲加載,解決辦法也很是簡單,相信你也一目瞭然即在在建立以前加個判斷條件。可是問題真的就所有解決了麼,其實否則,在單線程環境下,這確實是一種比較完美的方案,可是在多線程狀況下呢?試想多個實例同時被建立,咱們能想到的解決辦法一般是在整個getInstance
方法前加個synchronize
關鍵詞,可是這同時也帶來了很大的性能開銷,這並非咱們但願的。這裏不得不提到網上一個大神的問答,他提出一個解決辦法——use an enum。安全
public enum EnumExample {
INSTANCE;
private final String[] favoriteComic =
{ "fate", "Dragon Ball" };
public void printFavorites() {
System.out.println(Arrays.toString(favoriteComic));
}
}
複製代碼
以上方法除了很簡單之外,enum
還提供了序列化機制,防止從新建立新的對象,這個回答也在 Stack Overflow 上得到了最高票的回答。多線程
object 關鍵字建立一個新的單例類型,就像一個 class 只有一個被命名的實例。若是你熟悉 Java ,在 Scala 中聲明一個 object 有些像建立了一個匿名類的實例。 ——引自 Scala 函數式編程app
其實我在上面羅列了這麼多 Java 對於單例模式的實現方法以及對於不一樣場景所進行的不斷的優化,就是爲了引出object
,由於object
就沒必要考慮這麼多,不會像 Java 那樣受到場景的約束。ide
舉個例子:函數式編程
object Singleton {
def sum(l: List[Int]): Int = l.sum
}
複製代碼
看起來代碼是否是瞬間優雅了許多。若是感興趣的話,你能夠利用$ javap
反編譯一下,能夠看到全部方法前都帶上了static
關鍵詞,在這裏我就不在詳述。
由於線程安全不再用擔憂是單線程仍是多線程,又或是考慮到延遲加載也只要加上lazy
關鍵詞按需初始化便可,方不方便誰用誰知道。
衆所周知,在敲代碼中咱們作的最多的事情之一就是先去建立一個對象,這跟咱們建房子先建地基一個道理。咱們但願有一種模式能讓咱們更好去使用它,方便後期的維護,建立型模式也就應運而生,而工廠模式又是建立型模式中的主角之一,我想這設計模式對熟悉 Java 的各位來講應該是小菜一碟吧。實際上,工廠模式被分紅了三類——簡單工廠模式、工廠模式以及抽象工廠模式。但我更但願把它分爲兩類,在我看來,簡單工廠模式更像是工廠模式的一個特例,不能算是嚴格意義上的模式,可它確確實實實現了建立實例的邏輯與客戶端的解耦。
在這裏,我會經過 Scala 和 Java 兩種不一樣的語言來實現簡單工廠模式,從而加深你對object
的印象。假設如今有個電腦器材製造廠,同時生產鼠標和鍵盤,咱們用熟悉的簡單工廠模式設計來描述它的業務邏輯。先用 Java 來定義:
//定義產品接口
public interface Product{
public void show();
}
//如下實現了具體產品類
public class Mouse implements Product {
@Override
public void show() {
System.out.println("A mouse has been built");
}
}
public class Keyboard implements Product {
@Override
public void show(){
System.out.println("A keyboard has been built");
}
}
public class SimpleFactory {
public Product produce(String name) {
switch (name) {
case "Mouse":
return new Mouse();
case "Keyboard":
return new Keyboard();
default:
throw new IllegalArgumentException();
}
}
}
//簡單使用
public class Test {
public static void main(String[] args) {
SimpleFactory simpleFactory = new SimpleFactory();
Mouse mouse = simpleFactory.produce("Mouse");
mouse.show();
}
}
複製代碼
上述代碼經過調用SimpleFactory
中的 produce
方法來建立不一樣的 Product 子類對象,從而實現建立實例的邏輯與客戶端之間解耦,在這裏我採用直接判斷傳入的 key 的方式來實現了簡單工廠模式,固然還有其餘方式——經過 newInstance 反射等等。那麼有人會問,經過 Scala 該怎麼實現呢?這時候就要請咱們的主角——單例對象出場了。
要知道 Scala 支持用object
來實現Java中的單例模式,因此咱們能夠實現一個SimpleFactory
單例,而不是一個工廠類,具體代碼以下:
trait Product {
def show()
}
case class Mouse() extends Product {
def show = println("A mouse has been built")
}
case class Keyboard() extends Product {
def show = println("A keyboard has been built")
}
object SimpleFactory {//object代替class
def produce(name: String): Product = name match {
case "Mouse" => Mouse()
case "Keyboard" => Keyboard()
}
}
object Test extends App {
val mouse: Mouse = SimpleFactory.produce("Mouse")
mouse.show()
}
複製代碼
經過以上代碼,咱們能夠發現,一樣是經過判斷傳值的方式,Scala 也能夠垂手可得地實現。但這並非最重要的,值得讓咱們注意到的是在測試以前不用再去建立SimpleFactory
對象了,這正是先前講的object
靜態屬性在應用層次給咱們帶來的便利,或許你會嗤笑這小小簡化纔有多大的好處。別急,Scala 還爲咱們提供了一種語法糖 —— apply
,它本質上相似一個構造方法,這在上篇文章中也有講到,其實它也能夠應用於工廠模式,經過這種方式,咱們能夠省略工廠類,只需增長產品類接口的伴生對象就能夠實現。
咱們經過判斷傳入的 name 字符串來建立不一樣的對象,因此這裏的produce()
方法就顯得有點冗餘,如何讓工廠模式的實現更加的完美呢?用伴生對象來建立剛好能夠解決這個問題:
object Product {
def apply(name: String): Product = name match {
case "Mouse" => Mouse()
case "Keyboard" => Keyboard()
}
}
複製代碼
而後,咱們就能夠如此調用
val mouse: Product = Product("Mouse")
val keyboard: Product = Product("Keyboard")
mouse.show()
keyboard.show()
複製代碼
這樣之後,調用的體驗是否是更好了呢?能夠看到,利用object
的特性,咱們在必定程度上改進了 Java 中設計模式的實現,簡單工廠模式僅僅也是冰山一角而已。
因爲篇幅有限再次只列出簡單工廠模式,至於方法工廠模式和抽象工廠模式,有興趣的話能夠看看源碼。
最後,總結成一句話,object
做爲一種打破靜態迴歸常態、自然的語言特性,它對 Java 的部分特性進行了優化以便咱們能跟好地去理解、去使用,不知道你有沒有對此和我產生一些共鳴呢?