当Android遇上设计模式之单例(Singleton)模式

设计模式六大原则:

  • 单一职责原则:就一个类仅有一个引起它变化的原因,即类承担的职责单一性;

  • 开放封闭原则:类、模块、函数等应该是可以扩展的,但是不可修改。换句话说,就是面对需求的改变要尽可能地保证相对稳定,尽量通过扩展的方式而不是修改原有的代码来实现。

  • 里氏替换原则:所有引用基类(父类)的地方必须透明地使用其子类对象。换句话说,就是尽量把父类设计为抽象类或者接口,在运行时子类实例替换父类实例,在扩展时通过增加一个新的子类实现;

  • 依赖倒置原则:高层模块不应该依赖低层模块,两者都应该依赖于抽象。换句话说,就是模块间的依赖通过抽象发生,实现类之间不发生直接依赖关系,其依赖关系是通过接口或者抽象类产生。

  • 迪米特原则:一个软件实体应当尽可能少地与其他实体发生相互作用。换句话说,就是通过引入一个合理的第三者来降低现有对象之间的耦合度,同时在设计类时尽量降低成员的访问权限。

  • 接口隔离原则:一个类对另一个类的依赖应该建立在最小接口上。换句话说,就是为各个类建立专用的接口,而不要试图建立一个庞大的接口供所有依赖它的类调用,同时接口中的方法尽量少且少用public修饰,以提高内聚和减少对外交互。

1. 单例模式定义

 单例模式是创建型模式的一种,也就是说这种设计模式与对象的创建有关,它的定义为:保证一个类仅有一个实例,并提供一个访问它的全局访问点。单例模式UML类图如下:
在这里插入图片描述

2. 单例模式实现

 在Android开发中,单例模式可以说是我们接触使用最多的一种设计模式,在各种开源框架的源码中基本都能够看到单例模式的身影。根据对象的创建时机线程是否安全,单例模式主要有五种实现形式,即

  • 饿汉模式
  • 懒汉模式
  • 双重检查模式(DCL)
  • 静态内部类单例模式
  • 枚举模式

2.1 饿汉模式

/** 单例模式
 * author : jiangdg
 * date   : 2020/1/30 20:00
 * desc   : 饿汉模式
 * version: 1.0
 */
public class Singleton {
    // 静态变量
    private static Singleton instance = new Singleton();
    private Singleton() {}
    
    public static Singleton getInstance() {
        return instance;
    }
}

 饿汉模式的特点在于Singleton类的实例化发生在类加载时。换句话说,这种方式在类加载时就完成了初始化,即依赖于类加载机制,因此类加载较慢,但是获取对象的速度快,并且避免了多线程的同步问题。有一点需要注意的是,由于这个单例类的实例在类加载时被初始化,也就意味着已经被分配内存,如果从始至终都没有被使用过,则会造成内存的浪费。

2.2 懒汉模式

/** 单例模式
 * author : jiangdg
 * date   : 2020/1/30 20:00
 * desc   : 懒汉模式(线程不安全)
 * version: 1.0
 */
public class Singleton {
    private static Singleton instance;
    private Singleton(){}
    
    public static Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

 懒汉模式的特点是Singleton类的实例化发生在用户第一次调用时。懒汉模式生命了一个静态对象,在用户第一次调用时初始化,相比于饿汉模式,这虽然避免了从未使用时内存浪费的情况,但是由于第一次使用时需要实例化,因此返回会稍微慢一些,而且在多线程场景是线程不安全的。懒汉模式还有一种线程安全写法:

/** 单例模式
 * author : jiangdg
 * date   : 2020/1/30 20:00
 * desc   : 懒汉模式(线程不安全)
 * version: 1.0
 */
public class Singleton {
    private static Singleton instance;
    private Singleton(){}
    
    public static synchronized Singleton getInstance() {
        if(instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

 这种写法虽然能够解决上述多线程安全问题,但是每次调用getInstance方法时都需要进行同步操作,这会造成不必要的同步开销,而且大部分时候用不到同步,因此不建议使用这种模式,我们可以使用双重检查模式(DCL)来替换。

2.3 双重检查模式(DCL)

/** 单例模式
 * author : jiangdg
 * date   : 2020/1/30 20:00
 * desc   : 双重检查模式(线程安全)
 * version: 1.0
 */
public class Singleton {
    // volatile保证instance的可见性和防止指令重排序
    private volatile static Singleton instance;
    
    private Singleton(){}
    
    public static Singleton getInstance() {
        if(instance == null) {
            synchronized(Singleton.class) {
                if(instance == null) {
                 	instance = new Singleton();    
                }  
            }
        }
        return instance;
    }
}

 双重检查模式的特点在于用户在第一次使用时才初始化,且在getInstance创建单例类实例时通过两次判空来避免不必要的同步,线程安全。volatile能够保持instance的可见性和防止指令重排序,但是会或多或少地影响程序性能,但是考虑到程序的正确性还是值得的。DCL的优点是资源利用率高(即用才初始化),线程安全,缺点是第一次加载时反应稍慢,且在高并发环境下可能会出现失效。关于这个问题,我们可以使用静态内部类单例模式替代。

2.4 静态内部类单例模式

/** 单例模式
 * author : jiangdg
 * date   : 2020/1/30 20:00
 * desc   : 静态内部类单例模式
 * version: 1.0
 */
public class Singleton {
    private Singleton(){}
    
    public static Singleton getInstance() {
        return SingletonHolder.sInstance;
    }
    
    // 静态内部类
    private static class SingletonHolder {
        private static final Singleton sInstance = new Singleton();
    }
}

 静态内部类单例模式的特点在于第一次加载Singleton类时并不会初始化sInstance,只有第一次调用getInstance方法时虚拟机加载SingletonHolder并初始化sInstance。这种方式不仅能确保线程安全,也能够保证Singleton类的唯一性,因此,在日常开发中,推荐使用这种方式。

2.5 枚举单例

public enum Singleton {
    INSTANCE;
    public void doSomething() {
        
    }
}

 默认枚举单例的创建是线程安全,且能够保证任何情况下都是单例,这种方式最大的作用就是能够保证在反序列化单例类对象时不会被重新创建,因为前四种方式如果没有经过特殊处理的话,在反序列化时单例对象会被重建。枚举单例虽然简单,但是由于开发中使用很少且可读性较差,这里只作了解。

3. 单例模式的使用场景

 单例模式通常使用在要求一个类有且只有一个对象的场景,具体情况如下:

(1)整个项目需要一个共享访问点或共享数据;

(2)创建一个对象需要耗费的资源过多,比如访问I/O或者数据库等资源;

(3)工具类对象。

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 黑客帝国 设计师:白松林 返回首页