在經過C語言的長年洗禮後,前兩年因為開發Android的關係轉戰Java,剛開始寫Java時,自己還是用C語言的觀念去設計程式,在設計的時候常常會碰到一個麻煩的疑問:我的全域變數到底要放在哪裡?最後就創一個類別名叫Global,然後宣告成static method或static variable,透過呼叫靜態函式去使用全域變數,後來才明白,在物件導向的程式語言中,是沒有所謂的全域變數的,程式設計師必須先找到類別,再往下找該類別是否提供所需要的功能。
而在C語言的全域變數得概念,可以映射到一個設計模式(Design Pattern)中的獨體模式(Singleton Pattern),獨體模式顧名思義就是確保一個類別只有一個實體,而大家可以透過獨體類別的方法(Method)取得該類別的實體,使用此類別的人,無法使用new的方式自己產生實體。
先來看看獨體模式的類別圖:
圖1 取自 <<深入淺出設計模式>>
再來把類別圖實作一下:
圖2
其中第5~7行,是為了讓使用Singleton類別的人,不能用new的方式取得實例,如果要使用Singleton類別,須透過getInstance去取得實例。
第10行做了uniqueInstance的判斷,如果已經產生過實例,就把之前產生的實例直接回傳出去。
使用Singleton的程式碼如下:
圖3
這樣的設計在書中還討論到一個問題,在多線程的環境下,多個線程競爭,有兩個以上的線程同時執行圖2中的11行,使得有Singleton產生兩個實例,而線程中的操作其實是反映在不同的實例上。
在書中有提出三個解決方法:
1. 將getInstance()加上同步鎖(synchronized)
這樣做有個缺點,當getInstance被很多線程使用時,線程必須彼此等待,拖慢各自線程的執行速度。
2. 率先建立實體
在宣告uniqueInstance的同時,直接new出一個實例,但是這種方法在編譯時期就會產生實例,如果整個執行期沒有使用到此類別,就會造成不必要的記憶體浪費。
3. 利用『雙重檢查上鎖』
將getInstance中的程式碼稍作修改:
只有第一次執行的時候,才會因為同步鎖而拖慢速度,當uniqueInstance已經被建立實例後,就不會執行12-16行的程式碼。
在程式碼第4行的地方,出現volatile,我個人認為不寫volatile也可以達到一樣的效果。
書中有提到方法3只適用於Java 1.5以上的版本(含Java 1.5)。
獨體模式與一般直接使用static method的設計方法,好處在於獨體模式只有在用到的時候,才會產生實例,如果當我們的程式從頭到尾完全沒有使用到該獨體類別,就不會產生記憶體,這種特性稱為推延實體化,也就是當第一次使用的時候,才會產生實例占用記憶體空間,而static method的作法,是在編譯期就產生的實例,一執行程式就會占用記憶體。
其實在學Design Pattern之前,已經知道有這種撰寫法,學了之後才知道原來這種方法有個獨體模式的名稱。
在設計軟體的時候,難的不是叫人把功能做出來,有時候想要請分工的開發人員按照某種設計方法去撰寫程式,卻不知道要怎麼把想法表達給對方懂,或是已經照著自己的方法盡力詮釋之後,對方還是完全聽不懂,學了Design Pattern,讓程式設計師彼此有共通詞彙,在表達上可以更快速抓到對方所要的設計方式。