单例的扩展性讨论

来源:互联网 发布:hmcl启动器json 编辑:程序博客网 时间:2024/05/21 17:17

在上一篇中,讨论了单例的4种基本形态,这次我们来探讨单例的变形。

1.有限个数的单例形式。即这个对象可能有多个,从这个角度上说,它其实不属于单例,但实现方式确是以单例为基础的。它通常是以带参数的getInstance(或其变型)存在。

public class MultiInstanceDemo {    private String mType;    private MultiInstanceDemo(String type) {        mType = type;    }    //单例    //每种类型限制只能有一个实例    private static final HashMap<String, MultiInstanceDemo> mInstanceMap = new HashMap<String, MultiInstanceDemo>();    public static MultiInstanceDemo getInstance(String type) {        MultiInstanceDemo instance = mInstanceMap.get(type);        if( instance == null) {            synchronized (MultiInstanceDemo.class) {                instance = mInstanceMap.get(type);                if( instance == null) { //double check                    instance = new MultiInstanceDemo(type);                    mInstanceMap.put(type,instance);                }            }        }        return instance;    }}

从上面的代码中可以看到,getInstance是带了一个参数,这个参数其实是做为Key来保证对于相同的Key,只创建同一个对象。从另一个角度来说,这个getInstance实际上是被做工厂方法来使用。

在Android中,最常用到的一个Context.getSystemService(String), 其实就是用了这种结构。

在ContextImpl.java中,我们看到有这么一个结构。


private static final HashMap<String, ServiceFetcher> SYSTEM_SERVICE_MAP =        new HashMap<String, ServiceFetcher>();

private static int sNextPerContextServiceCacheIndex = 0;private static void registerService(String serviceName, ServiceFetcher fetcher) {    if (!(fetcher instanceof StaticServiceFetcher)) {        fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;    }    SYSTEM_SERVICE_MAP.put(serviceName, fetcher);}

registerService(ACCESSIBILITY_SERVICE, new ServiceFetcher() {        public Object getService(ContextImpl ctx) {            return AccessibilityManager.getInstance(ctx);        }});registerService(CAPTIONING_SERVICE, new ServiceFetcher() {        public Object getService(ContextImpl ctx) {            return new CaptioningManager(ctx);        }});registerService(ACCOUNT_SERVICE, new ServiceFetcher() {        public Object createService(ContextImpl ctx) {            IBinder b = ServiceManager.getService(ACCOUNT_SERVICE);            IAccountManager service = IAccountManager.Stub.asInterface(b);            return new AccountManager(ctx, service);        }});registerService(ACTIVITY_SERVICE, new ServiceFetcher() {        public Object createService(ContextImpl ctx) {            return new ActivityManager(ctx.getOuterContext(), ctx.mMainThread.getHandler());        }});

@Overridepublic Object getSystemService(String name) {    ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);    return fetcher == null ? null : fetcher.getService(this);}


2. 单例的生命周期讨论

一般说来,单例的对象是存在静态变量中的,除非是进程被杀死,这个单例就会永远存在。那么,有没有必要写一个Destory方法,把这个instance设为null, 以节约内存呢?

我的意见是这种做会破坏单例的唯一性。

因为你不知道在程序的哪个角落保留了对当前这个单例的引用。一旦在单例对象(mInstance=null)设为null后,下次当别人调用getInstance时,会又重新生成单例,此时内存中其实是同时存在两个及以上的对象,这就破坏了单例的唯一性。

因此,不建议去释放单例所占据的内存,故请谨慎使用单例。


那问题来了,如果我想提高内存的使用效率,只想创建一个短命的单例对象怎么办?

通常情况下,那就不能以常规的方法来创建或是在静态变量中存放单例的实例。需要把这个“单例”的对象作为另一个带有生命周期的对象的成员。这里写了一个例子来探讨这种情况。


SingleMethod.java,这是一个接口,表明单例中用到的所有公开的方法

/*** Created by Rex on 4/12/2015.*/ //很折腾的一个接口,包含单例中所有public的方法public interface SingleMethod {    public void methodA();    public void methodB();}

ShortSingle.java,这个是真正的单例,但对外不可见,无法直接访问

/*** Created by Rex on 4/12/2015.*/ //真正的单例在这儿了class ShortSingle implements SingleMethod {    /* package */ ShortSingle() {    }    @Override    public void methodA() {    }    @Override    public void methodB() {    }}

singleVistor.java,用来访问单例的方法,提供给外部使用,这个对象可创建多次,可保留多个引用,但最关键的是传入的single必须是真正的单例。

/*** Created by Rex on 4/12/2015.*/ //单例的访问者,此对象允许存在多个public class SingleVistor implements SingleMethod {    private ShortSingle mSingle;    /* package */ SingleVistor(ShortSingle single) {         mSingle = single;     }    @Override    public void methodA() {        final ShortSingle shortSingle = mSingle;        if( shortSingle != null) {            shortSingle.methodA();        }    }    @Override    public void methodB() {        final ShortSingle shortSingle = mSingle;        if( shortSingle != null) {            shortSingle.methodB();        }    }    /* package */ void destory() {        mSingle = null;    }}


LifeCycleObject.java 这是一个具有生命周期的类,init是开始, destory是结束。

/** * Created by Rex on 4/11/2015. */public class LifeCycleObject {    //真正的单例,我们要确保这个对象只能有一份    private static ShortSingle mSingle;    //单例对象的访问者,外面通过这个对象来使用单例    private static SingleVistor mSingleVistor;    //既然是短命的对象,自然就有开始与结束    public static void init() {        synchronized (LifeCycleObject.class) {               if( mSingle == null) {                mSingle = new ShortSingle();            }            if( mSingleVistor == null) {                mSingleVistor = new SingleVistor(mSingle);            }        }    }    //销毁了对象    public static void destory() {        synchronized (LifeCycleObject.class) {            mSingle = null;            if( mSingleVistor != null) {                mSingleVistor.destory();                mSingleVistor = null;            }        }    }    //返回一个包含单例方法一个接口,外面使用就不管它到底是什么对象,由于有了生命周期,则是有可能为null    public static SingleMethod getSingleObject() {        return mSingleVistor;    }    //for test only    static SingleMethod getRealSingle() {        return mSingle;    }}

在LifeCycleObject的生命周期中,从第一次调用init到destory之间,中间不管init调用多次,或是创建了多少个LifeCycleObject的对象,其中ShortSingle这个真正的单例只生一个。而且,这个单例本身对外面是隐藏的,外面无法获取这个单例的引用,外面访问的是单例的一个接口,即SingleMethod, 的另一个实现SingleVistor, 同样,这个SingleVistor的引用保持并不破坏单例的性质。因为就算保留了引用,但在destroy中,这个引用会置空。这样就达到了严格控制单例对象的目的。

在destroy之后,则单例在内存中的引用设置为null,所占用内存就释放了。到一下次的重新init, 此时单例会创建新的对象,但始终保持内存中最多只有一份单例对象。


这种设计优势是能较好的控制单例的生命周期,但使用成本较高,维护不方便,每次修改单例,需要修改三个类,而且结构复杂,读起代码比较痛苦。因此,如果不是对内存的要求特别苛刻,不推荐使用。



3.另一种带参数的单例,是需要初始化的。这种单例用起来也需要小心。最常见的场景是在Android中,有时候代码如网络,数据库等模块,需要一个applicationcontext作为参数。这种怎么处理呢,建议的写法是把参数单独提出来,做一个init或是setup的方法,然后在必要时才创建单例,这样对使用者友好,而且内存使用效率也不错。

public class SingletonWithParam {    private volatile static SingletonWithParam mInstance;    private static Object mParam;    private Object mBigObject; //lazy init    private SingletonWithParam(Object param) {        //create other object with param        mBigObject = new Object();    }    //initialize when the app start    public static void setup(Object param) {        mParam = param;    }    public static SingletonWithParam getInstance() {        if( mInstance == null) {            synchronized (SingletonWithParam.class) {                if( mInstance == null) { //double check                    mInstance = new SingletonWithParam(mParam);                }            }        }        return mInstance;    }}


4.单例的破坏

在通常情况下,我们写的单例是能正常工作的。但这世界总有一些例外,请看下面的代码。

这是一个我们常写的单例模式

public class SimpleInstance {    private volatile static SimpleInstance mInstance;    private SimpleInstance() {    }    public static SimpleInstance getInstance() {        if( mInstance == null) {            synchronized (SingletonWithParam.class) {                if( mInstance == null) { //double check                    mInstance = new SimpleInstance();                }            }        }        return mInstance;    }}

这是一个单元测试

public class SimpleInstanceTest {    @Test    public void instanceTest() {        SimpleInstance simpleInstance1 = SimpleInstance.getInstance();        SimpleInstance simpleInstance2 = null;        try {            Constructor<SimpleInstance> constructor = SimpleInstance.class.getDeclaredConstructor();            constructor.setAccessible(true);            simpleInstance2 = constructor.newInstance();        } catch (NoSuchMethodException e) {            e.printStackTrace();        } catch (InvocationTargetException e) {            e.printStackTrace();        } catch (InstantiationException e) {            e.printStackTrace();        } catch (IllegalAccessException e) {            e.printStackTrace();        }        assertNotNull(simpleInstance2);        assertNotEquals(simpleInstance1,simpleInstance2);    }}

而测试的结果是simpleInstance1并不等于simpleInstance2, 也就是说,对于通过反射的方式,是可以破坏单例的性质的。因此,通常情况下,我们的代码是防君子不防小人。那有办法防止吗? 额,有一种招术叫防御性编码,我们可以使用一个小技巧。

在构造函数加上一个assert语句。

private SimpleInstance() {    assert(mInstance == null);}

这样,想反射我的构造函数?没门。


除了反射,还有其他方式破坏吗?有,单例的序列化,网上有关这个的讨论很多,这里也不浪费篇幅了。直接上结论吧,为了防止单例的性质不被破坏,需要加上这么一个方法:

private Object readResolve() {    return mInstance;}


最后总结下,在本文中,我们讨论了关于单例的一些扩展性应用,包含生命周期,带参数的构造函数,有限个数的单例,及单例的破坏等话题,欢迎大家来拍砖。

0 0
原创粉丝点击