《设计模式》的做者、Eclipse和 Junit 的开发者 Erich Gamma 在它的理论体系中将 Singleton 定义为仅仅被实例化一次的类。在当今面向对象程序的实际开发中,Singleton 一般被用来表明一个无状态的对象,例如函数和那些本质上惟一的系统组件。设计模式
值得注意的是,使类成为 Singleton 会使得它的客户端测试变得很是困难,由于咱们不可能给Singleton替换模拟实现,除非咱们实现一个充当其类型的接口。app
实现 Singleton 有三种常见方法,他们或是保持构造器私有并导出公有的静态成员,或是声明一个包含单个元素的枚举类型。函数
//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 方法经过反射机制调用私有的构造器。为了不这样的可能的攻击,能够修改构造器,让它在被要求建立第二个实例的时候抛出异常。ui
公有域方法的主要优点在于,API很清楚地代表了这个类是一个 Singleton ,毕竟这是一个公有的静态属性。另外,这个方法要更加简单。线程
//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 方法,返回的都是同一个对象的引用。注意上面提示的反射攻击问题依然存在。设计
静态工厂方法有两大优点code
(注:方法引用是Java8的一个新特性)对象
除非咱们须要上述的其中一种优点,咱们仍是应该选择更简单易懂的使用公有域的方法。接口
使用上述两种方法实现的 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; }
//Enum singleton - the preferred approach public enum Elvis{ INSTANCE; public void leaveTheBuilding(){ ... } }
这种方法在功能上与公有域方法类似,但更加简洁,无偿地提供了序列化机制,绝对防止屡次实例化,即便是在面对复杂的序列化或者反射攻击的时候。 虽然这种方法尚未广 泛采用,可是单元素的枚举类型常常成为实现 Singleton 的最佳方法。 注意,若是 Singleton 必须扩展一个超类,而不是扩展 Enum 的时候,则不宜使用这个方法(虽然能够声明枚举去实现接口)。