欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

用私有构造器或者枚举类型强化Singleton属性-03

程序员文章站 2022-07-14 09:26:03
...

术语:

Singleton:指仅仅被实例化一次的类。


        Singleton会使它的客户端测试变得十分困难,因为无法给Singleton替换模拟实现,除非它实现一个充当其类型的接口。

        实现Singleton有以下三种方法:

1、实现公有静态成员函数,并将之设置为final。例如

  1. // Singleton with public field
  2. public class Elvis {
  3. public static final Elvis INSTANCE = new Elvis();
  4. private Elvis() { ... }
  5. public void leaveTheBuilding() { ... }
  6. }
        这种方法把构造函数私有化,这样保证了Elvis的全局唯一性。但有一个问题,就是享有特权的客户端可能借助AccessibleObject.setAccessible方法,通过反射机制调用私有构造器,这样就破坏了Singleton,一种补救的办法是,修改构造器让他被要求生成第二个实例的时候抛出异常。

2、实例私有化,但通过公有的静态方法返回,例如

  1. public class Elvis {
  2. private static final Elvis INSTANCE = new Elvis();
  3. private Elvis() { ... }
  4. public static Elvis getInstance() { return INSTANCE; }
  5. public void leaveTheBuilding() { ... }
  6. }
        对于静态方法Elvis.getInstance的所有调用都会返回同一个对象的引用,这样就保证了唯一性,但是还是存在第1种方法里的反射机制进行攻击的问题。

        第一种方法的好处在于组成类的成员的声明很清楚的表明了这是类的一个Singleton,但是相比之下不再比第二种方法有什么优势,因为JVM几乎都将静态工厂方法的调用内联化。第二种方法的优势之一在于,它提供了灵活性,在不改变其API的前提下,可以改变该类是否应该是Singleton的想法,这种方法可以很容易的被修改成按不同需求返回相应的唯一实例。第二个优势在于与泛型有关,但是相比之下,第一种方法更为简单。

        以上两种方法还有一个问题,就是在实现Singleton类序列化的时候,仅仅在声名中加上“implement Serializable”是不够的。为了维护并保证Singleton,必须声名所有的实例都是瞬时的(transient),并提供一个readResolve方法。否则的话每次反序列化的时候都会创建一个新的实例,这个时候要加入以下代码

  1. // readResolve method to preserve singleton property
  2. private Object readResolve() {
  3. // Return the one true Elvis and let the garbage collector
  4. // take care of the Elvis impersonator
  5. return INSTANCE;
  6. }
3、包含单个元素的枚举类型。

  1. public enum Elvis {
  2. INSTANCE;
  3. public void leaveTheBuilding() { ... }
  4. }
        这种方法更加简洁,无偿的提供了序列化机制,绝对防止多次实例化(由Enum保证),即使是在面对复杂的序列化或者反序列化或者反射攻击的时候也可以保证唯一。这已成为实现 Singleton的最佳方法。