Java模式設計之單例模式
在Java程序設計中經常會用到單例模式,但是很多時候程序員卻不知道什么時候該使用單例模式,或者怎么使用單例模式。
我們總結分析單例模式的時候,了解到單例模式的要點有三個:一是某個類只能有一個實例;二是它必須自行創建這個事例;三是它必須自行向整個系統提供這個實例。在下面的對象圖中,有一個"單例對象",而"客戶甲"、"客戶乙"和"客戶丙"是單例對象的三個客戶對象。可以看到,所有的客戶對象共享一個單例對象。而且從單例對象到自身的連接線可以看出,單例對象持有對自己的引用。
單例模式在資源管理器中應用。
一些資源管理器常常設計成單例模式。
在計算機系統中,需要管理的資源包括軟件外部資源,譬如每臺計算機可以有若干個打印機,但只能有一個Printer Spooler, 以避免兩個打印作業同時輸出到打印機中。每臺計算機可以有若干傳真卡,但是只應該有一個軟件負責管理傳真卡,以避免出現兩份傳真作業同時傳到傳真卡中的情況。每臺計算機可以有若干通信端口,系統應當集中管理這些通信端口,以避免一個通信端口同時被兩個請求同時調用。
需要管理的資源包括軟件內部資源,譬如,大多數的軟件都有一個(甚至多個)屬性(properties)文件存放系統配置。這樣的系統應當由一個對象來管理一個屬性文件。
需要管理的軟件內部資源也包括譬如負責記錄網站來訪人數的部件,記錄軟件系統內部事件、出錯信息的部件,或是對系統的表現進行檢查的部件等。這些部件都必須集中管理,不可政出多頭。
這些資源管理器構件必須只有一個實例,這是其一;它們必須自行初始化,這是其二;允許整個系統訪問自己這是其三。因此,它們都滿足單例模式的條件,是單例模式的應用。
分析單例模式的結構。
單例模式有以下的特點:
1、單例類只可有一個實例。
2、單例類必須自己創建自己這惟一的實例。
3、單例類必須給所有其他對象提供這一實例。雖然單例模式中的單例類被限定只能有一個實例,但是單例模式和單例類可以很容易被推廣到任意且有限多個實例的情況,這時候稱它為多例模式(Multiton Pattern) 和多例類(Multiton Class)。
由于Java 語言的特點,使得單例模式在Java 語言的實現上有自己的特點。這些特點主要表現在單例類如何將自己實例化上。
餓漢式單例類
這是在Java 語言里實現得最為簡便的單例類,下面所示的類圖描述了一個餓漢式單例類的典型實現。
從圖中可以看出,此類已經自已將自己實例化。
代碼清單1:餓漢式單例類
public class EagerSingleton { private static final EagerSingleton m_instance = new EagerSingleton(); /** * 私有的默認構造子 */ private EagerSingleton() { } /** * 靜態工廠方法 */ public static EagerSingleton getInstance() { return m_instance; } } |
讀者可以看出,在這個類被加載時,靜態變量m_instance 會被初始化,此時類的私有構造子會被調用。這時候,單例類的惟一實例就被創建出來了。
Java 語言中單例類的一個最重要的特點是類的構造子是私有的,從而避免外界利用構造子直接創建出任意多的實例。值得指出的是,由于構造子是私有的,因此,此類不能被繼承。
懶漢式單例類
與餓漢式單例類相同之處是,類的構造子是私有的。與餓漢式單例類不同的是,懶漢式單例類在第一次被引用時將自己實例化。如果加載器是靜態的,那么在懶漢式單例類被加載時不會將自己實例化。如下圖所示,類圖中給出了一個典型的餓漢式單例類實現。
代碼清單2:懶漢式單例類
package com.javapatterns.singleton.demos; public class LazySingleton { private static LazySingleton m_instance = null; /** * 私有的默認構造子,保證外界無法直接實例化 */ private LazySingleton() { } /** * 靜態工廠方法,返還此類的惟一實例 */ synchronized public static LazySingleton getInstance() { if (m_instance == null) { m_instance = new LazySingleton(); } return m_instance; } } |
同樣,由于構造子是私有的,因此,此類不能被繼承。餓漢式單例類在自己被加載時就將自己實例化。即便加載器是靜態的,在餓漢式單例類被加載時仍會將自己實例化。單從資源利用效率角度來講,這個比懶漢式單例類稍差些。
從速度和反應時間角度來講,則比懶漢式單例類稍好些。然而,懶漢式單例類在實例化時, 必須處理好在多個線程同時首次引用此類時的訪問限制問題,特別是當單例類作為資源控制器,在實例化時必然涉及資源初始化,而資源初始化很有可能耗費時間。這意味著出現多線程同時首次引用此類的機率變得較大。
餓漢式單例類可以在Java 語言內實現, 但不易在C++ 內實現,因為靜態初始化在C++ 里沒有固定的順序,因而靜態的m_instance 變量的初始化與類的加載順序沒有保證,可能會出問題。這就是為什么GoF 在提出單例類的概念時,舉的例子是懶漢式的。他們的書影響之大,以致Java 語言中單例類的例子也大多是懶漢式的。實際上,本書認為餓漢式單例類更符合Java 語言本身的特點。
使用單例模式必要條件:
下面我們探討一下使用單例模式的必要條件。
在一個系統要求一個類只有一個實例時才應當使用單例模式。反過來說,如果一個類可以有幾個實例共存,那么就沒有必要使用單例類。但是有經驗的讀者可能會看到很多不當地使用單例模式的例子,可見做到上面這一點并不容易,下面就是一些這樣的情況。
例子一
問:我的一個系統需要一些"全程"變量。學習了單例模式后,我發現可以使用一個單例類盛放所有的"全程"變量。請問這樣做對嗎?
答:這樣做是違背單例模式的用意的。單例模式只應當在有真正的"單一實例"的需求時才可使用。
一個設計得當的系統不應當有所謂的"全程"變量,這些變量應當放到它們所描述的實體所對應的類中去。將這些變量從它們所描述的實體類中抽出來,放到一個不相干的單例類中去,會使得這些變量產生錯誤的依賴關系和耦合關系。
例子二
問:我的一個系統需要管理與數據庫的連接。學習了單例模式后,我發現可以使用一個單例類包裝一個Connection對象,并在finalize()方法中關閉這個Connection對象。這樣的話,在這個單例類的實例沒有被人引用時,這個finalize() 對象就會被調用,因此,Connection 對象就會被釋放。這多妙啊。
答:這樣做是不恰當的。除非有單一實例的需求,不然不要使用單例模式。在這里Connection 對象可以同時有幾個實例共存,不需要是單一實例。
單例模式有很多的錯誤使用案例都與此例子相似,它們都是試圖使用單例模式管理共享資源的生命周期,這是不恰當的。
下面簡單說一下,筆者現在學習安卓,寫了一個很小的音樂播放播放器,自己寫了一個application。Application和Actovotu,Service一樣是android框架的一個系統組件,當android程序啟動時系統會創建一個 application對象,用來存儲系統的一些信息。通常我們是不需要指定一個Application的,這時系統會自動幫我們創建,如果需要創建自己的Application,也很簡單創建一個類繼承 Application并在manifest的application標簽中進行注冊(只需要給Application標簽增加個name屬性把自己的 Application的名字定入即可)
package com.frewen.ttplayer; importjava.util.ArrayList; importjava.util.LinkedList; importjava.util.List; importandroid.app.Activity; importandroid.app.Application; importandroid.content.Context; importandroid.graphics.Bitmap; importandroid.graphics.BitmapFactory; importandroid.media.MediaPlayer; importandroid.util.Log; import com.frewen.ttplayer.entry.Music; import com.frewen.ttplayer.entry.impl.Musicdata; import com.frewen.ttplayer.util.MusicPreference; public classMyApplication extends Application { public static MediaPlayer mediaPlayer; public static MusicPreferencemusicPreference; public static ArrayList<Music> musics= new ArrayList<Music>(); public static boolean isStart = false; public List<Activity> activityList =new LinkedList<Activity>(); public static Bitmap bitmap_l; public static Bitmap bitmap_s; public static MyApplication instance; public ArrayList<Music> getMusics() { return musics; } public static Context context; @Override public void onCreate() { super.onCreate(); context = getApplicationContext(); new Thread(new Runnable() { @Override public void run() { setMusics(Musicdata.getMultiDatas(context)); bitmap_l = BitmapFactory.decodeResource(context.getResources(), R.drawable.default_bg_l); bitmap_s =BitmapFactory.decodeResource(context.getResources(), R.drawable.default_bg_s); } }).start(); mediaPlayer = new MediaPlayer(); musicPreference = new MusicPreference(context); } public void setMusics(ArrayList<Music>ms) { musics.clear(); musics = ms; Log.i("test", "列表長度" + this.musics.size()); } public MyApplication() { } // 單例模式中獲取唯一的MyApplication實例 public static MyApplication getInstance() { if (null == instance) { instance = new MyApplication(); } return instance; } // 添加Activity到容器中 public void addActivity(Activity activity) { activityList.add(activity); } // 遍歷所有Activity并finish public void exit() { for (Activity activity : activityList) { activity.finish(); } System.exit(0); } /** * 向musics集合中追加一組miusic信息 * *@param musics */ public void append(ArrayList<Music>musics) { if (musics != null) { this.musics.addAll(musics); } } public void append(Music music) { if (music != null) { this.musics.add(music); } } } |
android系統會為每個程序運行時創建一個Application類的對象且僅創建一個,所以Application可以說是單例 (singleton)模式的一個類.application對象的生命周期是整個程序中最長的,它的生命周期就等于這個程序的生命周期。因為它是全局的單例的,所以在不同的Activity,Service中獲得的對象都是同一個對象。所以通過Application來進行一些,數據傳遞,數據共享等,數據緩存等操作。
posted on 2014-04-22 18:32 順其自然EVO 閱讀(338) 評論(0) 編輯 收藏 所屬分類: 測試學習專欄