如何正确的使用单例

通常,我们的单例模式都需要有一个静态的函数来获取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;
} 

在使用的时候,我们可以:

DataManager.getInstnace(context).doSomething();

讨论:两种方式看起来都是正确的,我们应该如何取舍呢,哪种方式更好?
支持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 - 2)
                .denyCacheImageMultipleSizesInMemory()
                .diskCacheFileNameGenerator(new Md5FileNameGenerator())
                .diskCacheSize(50 * 1024 * 1024) // 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[4];
    public DigitsKeyListener() {
        this(false, false);
    }

    public DigitsKeyListener(boolean sign, boolean decimal) {
        mSign = sign;
        mDecimal = decimal;

        int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);
        mAccepted = CHARACTERS[kind];
    }

    public static DigitsKeyListener getInstance() {
        return getInstance(false, false);
    }

    public static DigitsKeyListener getInstance(boolean sign, boolean decimal) {
        int kind = (sign ? SIGN : 0) | (decimal ? DECIMAL : 0);

        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;
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值