Android设计模式——单例模式

单例模式

介绍

单例模式是应用最广的模式之一。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个全局对象。

定义

确保某一个类只有一个实例,而且自行实例化向整个系统提供这个实例。

使用场景

确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。创建一个对象需要消耗的资源过多。就需要考虑使用单例模式

使用单例模式的关键点:

  1. 构造函数不对外开放,一般为private
  2. 通过一个静态方法或者枚举返回单例对象
  3. 确保单例类的对象有且只有一个
  4. 确保单例类的对象在反序列化时不会重新构建对象

    • 解释:通过将单例类的构造函数私有化,使得客户端代码不能通过new的形式手动构造单例类的对象。单例类会暴露一个公有静态方法,客户端需要调用这个静态方法获取到单例类的唯一对象,在获取这个单例对象的过程中需要确保县城安全。即在多线程的环境下构造单例类的对象也是只有一个

实例:

模拟一个公司的员工情况,一个公司有多个员工Staff 有多个副总VP 但是只有一个CEO 无论在什么情况下,CEO总保持只有一个

  • Staff模型
/**
 * Describe:员工模型
 * wx on 2016/12/29.
 */
public class Staff {
    public void work() {
        System.out.println("staff is do something");

    }

}
  • VP模型
/**
 * Describe:VP模型
 * wx on 2016/12/29.
 */
public class VP extends Staff {
    @Override
    public void work() {
        System.out.println("vp manager staff");
    }
}
  • CEO模型
/**
 * Describe:CEO模型
 * wx on 2016/12/29.
 */
public class CEO extends Staff {
    private static CEO ceo = new CEO();

    /**
     * 私有构造方法
     */
    private CEO() {

    }

    /**
     * 静态提供实例方法
     */
    public static CEO getInstance() {
        return ceo;
    }

    @Override
    public void work() {
        System.out.println("ceo manager staff");
    }
}
  • Company模型
/**
 * Describe:公司模型
 * wx on 2016/12/29.
 */
public class Company {

    private List<Staff> allStaffs = new ArrayList<Staff>();

    public void addStaff(Staff staff) {
        allStaffs.add(staff);
    }

    public void showAllStatffs() {
        for (Staff allStaff : allStaffs) {
            System.out.println("obj" + allStaff.toString());
        }
    }



}
  • Test
/**
 * Describe:
 * wx on 2016/12/29.
 */
public class Test {
    public static void main(String[] args) {

        Company cp = new Company();
        // TODO: 创建CEO对象
        CEO ceo1 = CEO.getInstance();
        CEO ceo2 = CEO.getInstance();
        // TODO: 入职
        cp.addStaff(ceo1);
        cp.addStaff(ceo2);
        // TODO: 创建VP
        Staff vp1 = new VP();
        Staff vp2 = new VP();
        // TODO: VP入职
        cp.addStaff(vp1);
        cp.addStaff(vp2);
        Staff staff1 = new Staff();
        Staff staff2 = new Staff();
        Staff staff3 = new Staff();
        // TODO: 员工入职
        cp.addStaff(vp1);
        cp.addStaff(vp2);
        cp.addStaff(staff1);
        cp.addStaff(staff2);
        cp.addStaff(staff3);
        // TODO: 查看所有员工
        cp.showAllStatffs();

    }
}

运行结果:

objcom.individual.wx.singlemode.singleton.CEO@7d4991ad
objcom.individual.wx.singlemode.singleton.CEO@7d4991ad
objcom.individual.wx.singlemode.singleton.VP@28d93b30
objcom.individual.wx.singlemode.singleton.VP@1b6d3586
objcom.individual.wx.singlemode.singleton.VP@28d93b30
objcom.individual.wx.singlemode.singleton.VP@1b6d3586
objcom.individual.wx.singlemode.singleton.Staff@4554617c
objcom.individual.wx.singlemode.singleton.Staff@74a14482
objcom.individual.wx.singlemode.singleton.Staff@1540e19d

单例的几种实现方式

懒汉式
  • 懒汉式是声明一个静态对象,并且在用户第一次调用getInstance时进行初始化,而饿汉式是在声明静态对象时就已经初始化。
  • 懒汉式实现:

/**
 * Describe:懒汉式单例模式
 * wx on 2016/12/29.
 */
public class SingleTon {

    public static SingleTon instance;

    private SingleTon() {
    }

    public static synchronized SingleTon getInstance() {
        if (instance == null) {
            instance = new SingleTon();
        }
        return instance;
    }

}
  • 存在问题:
    懒汉式单例模式实现中,即使instance已经被初始化,调用getInstance还是会进行同步,这样会消耗不必要的资源。
Double Check Lock (DCL) 实现单例
  • DCL实现单例模式的优点是既能在需要时才初始化单例,又能保证线程安全,且单例对象初始化后调用getInstance不进行同步锁
/**
 * Describe:Double Check Lock 实现单例模式
 * wx on 2016/12/29.
 */
public class DCLSingleTon {


    private static DCLSingleTon instance = null;

    private DCLSingleTon() {
    }

    public static DCLSingleTon getInstance() {
        // TODO: 第一次判空为了避免不必要的同步
        if (instance == null) {

            synchronized (SingleTon.class) {
                // TODO: 第二次判空为了没有实例时创建实例对象
                if (instance == null) {
                    instance = new DCLSingleTon();
                }
            }
        }
        return instance;
    }

}

explain: 可见整个程序进行了两次非空判断,第一次的非空判断是为了避免不必要的同步,也就是说在instance为空的时候,也就是需要的时候才进行同步锁定。第二次非空判断,是为了实例并不存在的时候初始化。

DCL单例模式的有缺点
  • 缺点:第一次加载的时候反应会稍微慢,也会由于java内存模型的原因偶尔会失败
  • 优点:资源利用率高,在第一次执行getInstance()时单例对象才会被实例化,效率高
静态内部类实现单例模式
  • DLC虽然在一定程度上解决了资源消耗、多余同步、线程安全等问题,但是,在某些情况下还是会出现失效的问题。这个问题被称为双重检查锁定失效,在《Java并发编程实践》中给出优化方案:
/**
 * Describe:静态内部类实现单例模式
 * wx on 2016/12/29.
 */
public class InnerSingleTon {
    private InnerSingleTon(){}
    public static InnerSingleTon getInstance(){
        return InnerSingleTonHolder.sInstance;
    }

    private static class InnerSingleTonHolder {

        public static InnerSingleTon sInstance = new InnerSingleTon();
    }
}
  • 第一次加载InnerSingle类时并不会初始化sInstance,只有在第一次调用getInstance()的时候才会导致sInstance被初始化。

反序列化的问题

通过序列化可以将一个单例的实例对象写到磁盘,然后再读回来,从而有效地获得一个实例,即使构造函数是私有的,反序列化时依然可以通过特殊的途径去创建类的一个新的实例,相当于用该类的构造函数,反序列化操作提供了一个很特别的钩子函数,类中具有一个私有的、被实例化方法readResolve(),这个方法可以让开发人员控制对象的反序列化。例如要杜绝单例对象在被反序列化时重新生成对象。那么必须加入如下方法:

private Object readResolve() throws ObjectStreamException{
    return sInstance;

}
枚举单例

使用枚举单例是好处是,写法简单,默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。枚举在反序列化时也不会重新生成新的实例

/**
 * Describe:枚举单例
 * wx on 2016/12/29.
 */
public enum  EnumSingleTon {
    INSTANCE;

    public void doSomeThing(){
        System.out.println("enm do sth.");
    }

}
使用容器实现单例
/**
 * Describe:容器单例
 * wx on 2016/12/29.
 */
public class SingleTonManager {
    private static Map<String, Object> objMap = new HashMap<String, Object>();

    private SingleTonManager() {
    }

    public static void regiserService(String key, Object instance) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, instance);
        }
    }

    public static Object getService(String key) {
        return objMap.get(key);
    }
}
  • 在程序的开始,将许多单例类型注入到一个统一的管理类中,在使用时根据key获取对象对应类型的对象。这种方式可以让我们管理多种类型的单例。并且在使用时可以通过统一的接口进行获取操作

Android源码中的单例模式

  • 在Android系统,我们会经常会通过Context获取系统级别的服务,如WindowManagerService、ActivityManagerService等。更常用的是一个LayoutInflater的类。这些服务会在合适的时候以单例的形式注册在系统中,在我们需要的时候就通过Context的getSystemService(String name)获取。
  • 在Android系统中,还有类似于LayoutInflate也是在运行时先注册到系统服务器管理中,然后在使用时直接根据名字来进行使用的单例模式
  • 由于水平比较菜,所以还不不贴源码了,但是在Android源码中,还是有很多单例的设计模式的使用地方的,一定要去看看啊

总结

虽然在客户端中没有太多的高并发的情况,因此单例模式的使用与否并不会有太大的影响。在使用单例模式时一定要注意由于系统中只存在一个对象,那么对于这个对象的赋值和操作会影响到整个系统的使用,在这点还是应该注意的。一般对于资源消耗大的类,方便程序的性能提升 还是比较推荐使用单例模式来进行对象的管理的。

优点:

1.由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建和销毁时,单例模式就非常明显了
2. 由于单例模式只生成一个实例,所以减少了系统性能的开销。当一个对象的产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个对象,然后永久驻留内存的方式来解决
3. 单例模式可以避免对资源的多重占用。
4. 单例模式可以在系统设置全局的访问点,优化和共享资源访问。

缺点

1.单例模式一般没有接口,拓展比较困难
2.单例对象如果持有Context,那么很容易发生内存泄漏,需要注意传递给单例对象的Context最好是Application Context.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值