03.使用私有构造方法或者枚举类型实现单例Singleton属性【Effective Java】

Character 2 Creating and Destroying objects

使用私有构造方法或者枚举类型执行单例singleton属性

文章目录

    • 问题场景
    • 方法一:公共属性 public field
    • 方法二:静态工厂方法
    • 方法三:单一元素枚举类

问题场景

单例就是一个仅实例化一次的类(item95)。单例通常代表无状态对象,比如函数(item24)或者一个本质上唯一的系统组件。让一个类成为单例会使测试它的客户变的困难,因为除非实现一个作为它类型的接口,否则不可能用一个模拟实现替代单例。

方法一:公共属性 public field

这有两种比较常见的方式实现单例。两者都基于保持构造方法私有和到处公共静态成员从而提供对唯一实例的访问。在第一种方法中,用final修饰成员:

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

private私有构造方法只调用一次,用来初始化公共静态final Elvis.INSTANCE属性。public公共或者protected受保护的构造方法的缺失保证了全局的唯一性:一旦Elvis类被初始化,Elvis实例就会存在,有且仅一个,不多也不少。客户端所做的任何事情都不能改变,但有一点警告:特权客户端可以使用AcessibleObject.setAccessible方法以反射reflectively方式调用私有构造方法(item65)。如果需要防御这种攻击,需要修改构造函数,让构造函数在第二次调用时候抛出异常。

方法二:静态工厂方法

第二种方法就是,公共成员是一个静态的工厂方法:

//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() {...}
}

所有调用Elvis.getInstance()返回的都是相同的对象引用,并没有创建其他的Elvis的实例(和前面提到的警告相同)。

公共属性(public field)方法的最主要的优点就是API明确该类是单例:公共静态filed是final的,所以它总是包含相同的对象引用。第二个好处就是它更简单。

静态工厂方法的好处就是有了灵活性,不需要改变API的前提下,我们可以改变该类是否是单例的。静态工厂方法返回的是唯一实例,但是很容易被修改,比如改为调用每个调用该方法的线程返回一个唯一的实例。第二个好处就是如果你的应用程序需要它,你可以写出泛型单例工厂(generic singleton factory)(item30)。最后一个优点就是可以通过方法引用作为提供者,例如Elivis::instance等同于Supplier< Elivis >。除非满足上述任意一种优势,否则还是考虑公共属性方法。

为了让单例类变成可序列化的(12章),仅仅将implemente serializable添加到声明中是不够的。为了保证单例模式不被破坏,必须声明所有的实例字段是transient,并提供一个readResolve方法(item89).否则每次序列化实例被反序列化时,就会创建一个新的实例,在下面的例子中,就会初夏新的Elvis实例。为了避免这种情况发生,将如下的ReadResole方法添加到Elvis类中:

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

方法三:单一元素枚举类

第三种实现单例的方式就是声明单一元素的枚举类

// Enum singleton - the preferred approach
public enum Elvis {
	INSTANCE;
	public void leaveTheBuilding() {...}
}

这种方法类似公共属性方法,但是更简洁,免费的提供了序列化机制,并提供了防止多个实例化的坚固保证,及时在复杂的序列化或反射攻击的情况下。这种方法可能感觉不自然,但是单一元素枚举类通常是实现单例的最佳方式。注意如果单例必须继承别的Enum以外的父类(尽管你可以声明一个Enum来实现接口),那么就不能使用这种方法。

你可能感兴趣的:(java)