通常,我們的單例模式都需要有一個靜态的函數來擷取instance,如:
public static synchronized DataManager getInstance() {
if (null == sInstance) {
sInstance = new DataManager();
}
return sInstance;
}
在使用的時候,我們可以:
DataManager.getInstnace().initialize(context);
DataManager.getInstnace().doSomething();
但是,也有這種寫法,如:
public static synchronized DataManager getInstance(Context context) {
if (null == sInstance) {
sInstance = new DataManager(context);
}
return sInstance;
}
在使用的時候,我們可以:
讨論:兩種方式看起來都是正确的,我們應該如何取舍呢,哪種方式更好?
支援getInstance(context)的觀點:
1. 在初始化單執行個體時,需要必要的參數來建立對象,DataManager.getInstnace().initialize(context),那就會有一個疑問,每次getInstance()之後,我到底要不要再調用一個函數來初始化?它無法保障DataManager已被正确的初始化。
支援getInstance()的觀點:
1. 單例的設計,一般不需要初始化參數來建立,如果需要一個或者多個參數來初始化,那為什麼要考慮使用單例呢?使用一個普通的類不是更好嗎?單例提供的是一個唯一的通路據點,更多的用來做管理類(XXXManager),維護全局資料,一般它不需要額外的參數來初始化本身。
2. 既然是單例,就不要産生歧義,如果還需要參數的話,不同的參數(不一定是context,可以是其它資料)是否會産生不同的執行個體?
3. 使用getInstance(context),每次傳進去context,都會重新初始化(或重置)我的單例子參數嗎?
4. 使用getInstance(context),在調用層次很深的類裡面,我還得要緩存個context,或者調用函數裡面都得加上context參數?
結論:從上來的觀點來看,個人更加傾向于使用無參的getInstnace()方式。
如果你真的需要參數(可能不止一個)來構造或者初始化你的instance,可以考慮提供initialize方法,并在程式的入口處初始化,這樣,在其它任何地方使用時,不需要考慮初始化問題,看上去似乎有些牽強,不過我們可以找一個具體的使用執行個體。我們看看大名鼎鼎的Android-Universal-Image-Loader的使用方式:
public class UILApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
initImageLoader(getApplicationContext());
}
public static void initImageLoader(Context context) {
// This configuration tuning is custom. You can tune every option, you may tune
// some of them, or you can create default configuration by
// ImageLoaderConfiguration.createDefault(this); method.
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context)
.threadPriority(Thread.NORM_PRIORITY - )
.denyCacheImageMultipleSizesInMemory()
.diskCacheFileNameGenerator(new Md5FileNameGenerator())
.diskCacheSize( * * ) // 50 Mb
.tasksProcessingOrder(QueueProcessingType.LIFO)
.writeDebugLogs() // Remove for release app
.build();
// Initialize ImageLoader with configuration.
ImageLoader.getInstance().init(config);
}
}
在Android源碼中,有無數單例,我們也一起看看其使用方式:
\frameworks\base\core\java\android\view\WindowManagerGlobal.java
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
像以下類(僅列出幾個),都是采用的上面的方式,這種類型的使用是用得最廣泛的:
\frameworks\base\core\java\android\app\ResourcesManager.java
\frameworks\base\core\java\android\hardware\display\DisplayManagerGlobal.java
\frameworks\base\core\java\android\webkit\CookieManager.java
\frameworks\base\core\java\android\view\accessibility\AccessibilityInteractionClient.java
\frameworks\base\core\java\android\net\http\CertificateChainValidator.java
private static class NoPreloadHolder {
private static final CertificateChainValidator sInstance = new CertificateChainValidator();
private static final HostnameVerifier sVerifier = HttpsURLConnection
.getDefaultHostnameVerifier();
}
public static CertificateChainValidator getInstance() {
return NoPreloadHolder.sInstance;
}
帶參數的getInstance(Context context),也有不少,但數量要小得多:
\frameworks\base\core\java\android\view\accessibility\AccessibilityManager.java
\frameworks\base\core\java\android\hardware\location\GeofenceHardwareImpl.java
\frameworks\base\core\java\android\appwidget\AppWidgetManager.java
Context mContext;
public static AppWidgetManager getInstance(Context context) {
synchronized (sManagerCache) {
if (sService == null) {
IBinder b = ServiceManager.getService(Context.APPWIDGET_SERVICE);
sService = IAppWidgetService.Stub.asInterface(b);
}
WeakReference<AppWidgetManager> ref = sManagerCache.get(context);
AppWidgetManager result = null;
if (ref != null) {
result = ref.get();
}
if (result == null) {
result = new AppWidgetManager(context);
sManagerCache.put(context, new WeakReference<AppWidgetManager>(result));
}
return result;
}
}
private AppWidgetManager(Context context) {
mContext = context; // 緩存context
mDisplayMetrics = context.getResources().getDisplayMetrics();
}
public void updateAppWidget(int[] appWidgetIds, RemoteViews views) {
try {
sService.updateAppWidgetIds(appWidgetIds, views, mContext.getUserId());
}
catch (RemoteException e) {
throw new RuntimeException("system server dead?", e);
}
}
上面的這個例子,每次在擷取單例子時,需要帶上context,用起來麻煩。但也有好處,執行個體在初始化後并将context緩存起來,當内部的函數需要使用context(如updateAppWidget函數),直接使用緩存的mContext即可。
如果讓我來設計這個類,我有兩種改造方式:
- 不緩存context,在需要使用context的地方,讓調用者以參數的形式表現出來,如下:
public void updateAppWidget(Context context, int[] appWidgetIds, RemoteViews views)
- 緩存context,提供ininalize方法來設定context。但很明顯這有比較大的漏洞,因為在使用其它方法前,我必須要確定initialize方法已被調用過,否則在調用像updateAppWidget這種需要context的方法時,其行為就不正确了。
(寫到這裡,其實我對我之前的觀點也動搖了,因為各有優缺點,是以,還是得需要根據實際情況來做判斷)
在源碼中搜了一下,還有一些另類的getInstance,支援多參數,仔細看一下,發現它就起到了一個緩存的作用,嚴格的來講,已經不是單例的範疇了。
\frameworks\base\core\java\android\text\method\DigitsKeyListener.java
public class DigitsKeyListener extends NumberKeyListener {
private static DigitsKeyListener[] sInstance = new DigitsKeyListener[];
public DigitsKeyListener() {
this(false, false);
}
public DigitsKeyListener(boolean sign, boolean decimal) {
mSign = sign;
mDecimal = decimal;
int kind = (sign ? SIGN : ) | (decimal ? DECIMAL : );
mAccepted = CHARACTERS[kind];
}
public static DigitsKeyListener getInstance() {
return getInstance(false, false);
}
public static DigitsKeyListener getInstance(boolean sign, boolean decimal) {
int kind = (sign ? SIGN : ) | (decimal ? DECIMAL : );
if (sInstance[kind] != null)
return sInstance[kind];
sInstance[kind] = new DigitsKeyListener(sign, decimal);
return sInstance[kind];
}
}
最後,推薦一篇經典的文章,它教你如何構造一個完美的單例。
http://www.iteye.com/topic/575052
另外,再記錄一個《Head First Design Patterns》中推薦的單例:
public class Singleton {
/**
* The volatile keyword ensures that multiple threads
* handle the sInstance variable correctly when it
* is being initialized to the Singleton instance.
*/
private volatile static Singleton sInstance;
private Singleton() {}
public static Singleton getInstance() {
if(sInstance == null) {
synchronized (Singleton.class) {
if(sInstance == null) {
sInstance = new Singleton();
}
}
}
return sInstance;
}
}