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

前言

Singleton 是指仅仅被实例化一次的类。通常被用来代表一个无状态的对象。
使类成为 Singleton 会使它的客户端测试变得十分困难, 因为不可能给 Singleton 替换模拟实现,除非实现一个充当其类型的接口。

实现 Singleton 的方法

比较常见的两种方法都要保持构造器为私有的,并导出公有的静态成员,以便允许客户端能够访问该类的唯一实例。

公有静态成员是个 final 域

public class Elvis {
    public static final Elvis INSTANCE = new Elvis();

    private Elvis() { }

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

私有构造器仅被调用一次,用来实例化公有的静态 finalElvis.INSTANCE 。由于缺少公有的或者受保护的构造器,所以保证了 Elvis 的全局唯一性:一旦 Elvis 类被实例
化,将只会存在一个 Elvis 实例。

注意,客户端可以借助 AccessibleObject.setAccessible 方法,通过反射机制调用私有构造器 。 如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常 。

公有的成员是个静态工厂方法

public class Elvis {
    private static final Elvis INSTANCE = new Elvis();
    private Elvis() { }
    public static Elvis getInstance() { return INSTANCE; }

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.getInstance();
        elvis.leaveTheBuilding();
    }
}

对于静态方法 Elvis.getinstance 的所有调用,都会返回同一个对象引用,所以,永远不会创建其他的 Elvis 实例。

声明一个包含单个元素的枚举类型

public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() {
        System.out.println("Whoa baby, I'm outta here!");
    }

    // This code would normally appear outside the class!
    public static void main(String[] args) {
        Elvis elvis = Elvis.INSTANCE;
        elvis.leaveTheBuilding();
    }
}

这种方法在功能上与公有域方法相似,但更加简洁,无偿地提供了序列化机制,绝对防止多次实例化,即使是在面对复杂的序列化或者反射攻击的时候 。 虽然这种方法还没有广泛采用,但是单元素的枚举类型经常成为实现 Singleton 的最佳方法

注意,如果 Singleton必须扩展一个超类,而不是扩展 Enum 的时候,则不宜使用这个方法(虽然可以声明枚举去实现接口) 。

公有域方法的优势

  • API 很清楚的表明了这个类是一个 Singleton
    公有的静态域是 final 的,所以该域总是包含相同的对象引用。
  • 它更简单

静态工厂方法的优势

  • 提供了灵活性
    在不改变其 API 的前提下,我们可以改变该类是否应该为 Singleton 的想法。工厂方法返回该类的唯一实例,但是,它很容易被修改, 比如改成为每个调用该方法的线程返回一个唯一的实例 。
  • 可以编写一个 泛型 Singleton 工厂
  • 可以通过 方法引用 作为提供者
    比如 Elvis::instance 就是一个 Supplier<Elvis>
除非满足以上任意一种优势 , 否则还是优先考虑公有域的方法 。

 上一篇
生成指定范围的随机数(Java&JS) 生成指定范围的随机数(Java&JS)
需求有时,在我们的开发中可能会需要生成 [min,max] 的随机数,这里提供 Java 和 JS 版本的公式。 Java前提Math.random() : 返回带有正号的 double 值,大于或等于 0.0 且小于 1.0。返回值是伪随
2019-07-11
下一篇 
使用frp实现内网穿透 使用frp实现内网穿透
简介对于没有公网 IP 的内网用户来说,远程管理或在外网访问内网机器上的服务是一个问题。通常解决方案就是用内网穿透工具将内网的服务穿透到公网中,便于远程管理和在外部访问。内网穿透的工具很多,如花生壳等等。今天给要介绍的是另一款好用内网穿透工
2019-06-20
  目录