Java中Singleton的三种实现方式解析

一、什么是Singleton?

《设计模式》的作者、Eclipse和 Junit 的开发者 Erich Gamma 在它的理论体系中将 Singleton 定义为仅仅被实例化一次的类。在当今面向对象程序的实际开发中,Singleton 通常被用来代表一个无状态的对象,例如函数和那些本质上唯一的系统组件。

值得注意的是,使类成为 Singleton 会使得它的客户端测试变得非常困难,因为我们不可能给Singleton替换模拟实现,除非我们实现一个充当其类型的接口。

实现 Singleton 有三种常见方法,他们或是保持构造器私有并导出公有的静态成员,或是声明一个包含单个元素的枚举类型。

二、Singleton实现 —— 构造器私有

1、公有静态成员为一个final域

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

在这个类中,我们仅仅拥有一个私有的构造器,它也只在初始化final域时被调用一次。由于缺少可以使用的构造器,后续的程序无法再创建 Elvis 对象。这保证了在该Java程序的整个生命周期中, Elvis 对象有且只有一个存在。

但需要注意的是,一些高权限的客户端可以借助 AccessibleObject.setAccessible 方法通过反射机制调用私有的构造器。为了避免这样的可能的攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。

公有域方法的主要优势在于,API很清楚地表明了这个类是一个 Singleton ,毕竟这是一个公有的静态属性。另外,这个方法要更加简单。

2、公有静态成员为一个静态工厂方法

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

显然,无论怎样调用 getInstance 方法,返回的都是同一个对象的引用。注意上面提示的反射攻击问题依然存在。

静态工厂方法有两大优势

  • 第一,它提供了更多的灵活性,在不改变API的前提下,我们可以轻易地自由调整这个类是否是Singleton。工厂方法返回该类的唯一实例,但它很容易修改成别的样子,例如为每个调用该方法的线程提供唯一实例。
  • 第二,如果程序需要,我们可以编写一个泛型 Singleton 工厂。
  • 第三,我们可以通过方法引用作为提供者,比如 Elvis::instance 就是一个 Supplier< Elvis >

(注:方法引用是Java8的一个新特性)

除非我们需要上述的其中一种优势,我们还是应该选择更简单易懂的使用公有域的方法。

3、将利用上述方法实现的Singleton类变为可序列化的

使用上述两种方法实现的 Singleton ,要把他们变成可序列化的,不能仅仅在声明中加上 implements Serializable 。为了维护并保证 Singleton ,我们必须生命所有实例域都是瞬时的,并提供一个 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;
	}

三、Singleton实现 —— 声明包含单个元素的枚举类型

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

这种方法在功能上与公有域方法相似,但更加简洁,无偿地提供了序列化机制,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候。 虽然这种方法还没有广 泛采用,但是单元素的枚举类型经常成为实现 Singleton 的最佳方法。 注意,如果 Singleton 必须扩展一个超类,而不是扩展 Enum 的时候,则不宜使用这个方法(虽然可以声明枚举去实现接口)。

人已赞赏
经验教程

最小生成树---普里姆算法(Prim算法)和克鲁斯卡尔算法(Kruskal算法)代码实现

2021-2-20 22:27:00

经验教程

c/c++ 之静态库

2021-2-20 22:41:00

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
今日签到
有新私信 私信列表
搜索