EffectiveJava3 item3:强制单例类构造函数私有或是枚举类型

翻译

      单实例类是只实例化一次的类,单例通常代表一个无状态的对象,比如说一个本质上唯一的功能组件或者系统组件。让一个类成为单例使得客户端很难测试它,因为不能用一个模拟的实现代替单例,除非它实现了一个某类型的接口。 

      有两种常见的方法实现单例,两种都是基于保持构造函数私有,然后引入一个静态的成员来提供访问这个唯一实例,其中一种方法,成员是final字段;

// Singleton with public final field
public class Elvis {
  public static final Elvis INSTANCE = new Elvis();
  private Elvis() { ... }
  public void leaveTheBuilding() { ... } 
}

      私有的构造函数只被调用一次,来实例化public static final field Elvis.INSTANCE , 缺少public或者protectd修饰的构造函数保证了单精灵宇宙:当Elvis类被初始化的时候只有一个Elvis实例存在,没有更多或者更少。没有客户端能改变这一点,警告:(一个有特权的客户端可以调用私有构造方法,通过AccessbleObject.setAccessible()方法绕过限制;)如果你需要防御这个攻击,修改构造方法让当他被要求创造第二个实例的时候抛出一个异常;

       第二种方法实现了单例,公共成员是一个静态工厂方法;所有调用Elvis.getInstance()方法返回同一个对象引用,没有任何其他的Elvis实例会被创建(跟之前有同样的警告),

      第一个实现单例的方法最主要的好处是很清晰的知道这个类是单例,public static 的成员是final的,所以它总是包含同一个对象引用。

      第二个实现单例的方法优点是简单。

优点1:是它让你灵活改变主意是否这个类是单例而不用改变它的API。工厂方法返回唯一的实例,但是它可以被修改之后返回,或者说,每个线程调用都返回单独实例;

优点2:如果你的应用需要它,你可以写一个通用的单例工厂。

优点3:静态工厂是一个方法引用,可以被用来当成supplier,例如Elvis::instance 就是一个提供者,Supplier, 除非需要这三个优点;否则第一种实现单例的方式应该被优先选择;

// Singleton with static factory
public class Elvis {
  private static final Elvis INSTANCE = new Elvis();
  private Elvis() { ... }
  public static Elvis getInstance() {
    return INSTANCE; 
  }
  public void leaveTheBuilding() { ... } 
}

      为了使得单例类使用这些方法可串行化,仅仅增加 implements Serializable 是不够的,为了保证单例,申明所有的实例成员为 transient , 并提供一个 readResolve方法,否则,每次一个串行化的实例被反序列化,一个新的实例将会被创建;为了引导,在这个例子中,伪造了一个Elvis目击者,为了防止这个发生,添加了一个readResolve方法到该类中;


// readResolve method to preserve singleton property
private Object readResolve() {
  // Return the one true Elvis and let the garbage collector // take care of the Elvis impersonator.
  return INSTANCE; 
}

      第三种实现一个单例方法是申明一个单元素的枚举;

public enum Elvis { 
  INSTANCE;
  public void leaveTheBuilding() { ... }
}

        这个方法跟公共成员的方法很类似,但是更简洁,提供了自由的机器序列化,提供了牢不可破的保证防御多实例化,甚至在面临常见的序列化和反射攻击场景。这种方式可能会感觉一点不自然,但是一个单元素的枚举通常是最好的实现单例的方式,注意,如果你的单例必须extend 一个除了枚举之外的超类你不能使用这种方式,(尽管你可以申明一个枚举去实现接口)

 

快速记忆

 

EffectiveJava3 item3:强制单例类构造函数私有或是枚举类型_第1张图片

 

 

我的看法

      单例的实现方式,优先使用单元素枚举,因为它天然可以防御序列化和反射的攻击,除非它必须扩展一个非枚举类型的父类,当有这些需求的时候(在返回的时候可以添加处理逻辑,可以当成一个supplier使用,修改逻辑不用改变API)使用静态工厂方法实现的单例,因为它更灵活,如果没有这些需求,使用公共成员变量实现的单例,但是要有防御反射和序列化攻击的处理代码;

你可能感兴趣的:(java,effectivejava3)