從 Java 到 Scala (三): object 的應用

本文由 Captain 發表在 ScalaCool 團隊博客。java

在上篇 Java 到 Scala 系列中,我想你或多或少在語言特性上對object有了必定的掌握,在瞭解完它酷酷的語言特性——讓靜態迴歸常態並能簡單運用其衍生出的方法後,我今天就來談談在現實應用方面本身對它的理解,不知道是否是也會給你一種耳目一新的感受,畢竟「單例對象」做爲一種自然的語言特性,華而不實並非咱們想看到的。git

單例模式 VS 單例對象

咱們已經知道了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 實現

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 的部分特性進行了優化以便咱們能跟好地去理解、去使用,不知道你有沒有對此和我產生一些共鳴呢?

相關文章
相關標籤/搜索