Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# 3. 使用私有构造方法或枚类实现 Singleton 属性


  单例是一个仅实例化一次的类[Gamma95]。单例对象通常表示无状态对象,如函数 (详见第 24 条) 或一个本质上唯一的系统组件。让一个类成为单例会使测试它的客户变得困难,因为除非实现一个作为它类型的接口,否则不可能用一个模拟实现替代单例
  单例是一个仅实例化一次的类[Gamma95]。单例对象通常表示无状态对象,如函数(条目 24) 、或一个本质上唯一的系统组件。**让一个类成为单例会使测试它的客户端变得困难**,因为需要创建一个实现该接口的类,否则无法用模拟实现替代单例
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

用(详见第 xx 条)吧,如果没有这种形式的,都是我改漏了

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unless 你这里翻译丢掉了吧


  有两种常见的方法来实现单例。两者都基于保持构造方法私有和导出公共静态成员以提供对唯一实例的访问。在第一种方法中,成员是 `final` 修饰的属性:

Expand All @@ -14,7 +14,7 @@ public class Elvis {
}
```

  私有构造方法只调用一次,来初始化公共静态 final `Elvis.INSTANCE` 属性。缺少一个公共的或受保护的构造方法,保证了全局的唯一性:一旦 Elvis 类被初始化,一个 Elvis 的实例就会存在——不多也不少。客户端所做的任何事情都不能改变这一点,但需要注意的是:特权客户端可以使用 `AccessibleObject.setAccessible` 方法,以反射方式调用私有构造方法(详见第 65 条)。如果需要防御此攻击,请修改构造函数,使其在请求创建第二个实例时抛出异常。
  私有构造方法只调用一次,来初始化`public static final Elvis.INSTANCE` 属性。不声明一个公共的或受保护的构造方法,保证了全局的唯一性:一旦 Elvis 类被初始化,一个 Elvis 的实例就会存在——不多也不少。客户端所做的任何事情都不能改变这一点,但需要注意的是:特权客户端可以使用 `AccessibleObject.setAccessible` 方法,以反射方式调用私有构造方法(详见第 65 条)。如果需要防御此攻击,请修改构造函数,使其在请求创建第二个实例时抛出异常。
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

英文字符前后加空格


  在第二个实现单例的方法中,公共成员是一个静态的工厂方法:

Expand All @@ -31,9 +31,9 @@ public class Elvis {

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

  公共属性方法的主要优点是 API 明确表示该类是一个单例:公共静态属性是 final 的,所以它总是包含相同的对象引用。 第二个好处是它更简单。
  公共属性方法的主要优点是 API 明确表示该类是一个单例:公共静态属性是 final 的,所以它总是包含相同的对象引用。 第二个好处是它更简单。
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个搞两个冒号不觉得奇怪么。

这前面本身就是一句话,如果想分隔你也要用逗号


  静态工厂方法的优势之一在于,它提供了灵活性:在不改变其 API 的前提下,我们可以改变该类是否应该为单例的想法。工厂方法返回该类的唯一实例,但是,它很容易被修改,比如,改为每个调用该方法的线程返回一个唯一的实例。 第二个好处是,如果你的应用程序需要它,可以编写一个泛型单例工厂(generic singleton factory )(详见第30 条)。 使用静态工厂的最后一个优点是,可以通过方法引用(method reference)作为提供者,例如 `Elvis::instance` 等同于 `Supplier<Elvis>`。 除非满足以上任意一种优势,否则还是优先考虑公有域(public-field)的方法。
  静态工厂方法的优势之一在于,它提供了灵活性:在不改变其 API 的前提下,我们可以改变该类是否应该为单例的想法。工厂方法返回该类的唯一实例,但是,它可能被修改,比如,改为每个调用该方法的线程返回一个唯一的实例。 第二个好处是,如果你的应用程序需要,可以编写一个泛型单例工厂(generic singleton factory )(详见第30 条)。 使用静态工厂的最后一个优点是,可以通过方法引用(method reference)作为提供者,例如 `Elvis::instance` 等同于 `Supplier<Elvis>`。 除非满足以上任意一种优势,否则还是优先考虑公有域(public-field)的方法。

  为了将上述方法中实现的单例类变成是可序列化的 (第 12 章),仅仅将 `implements Serializable` 添加到声明中是不够的。为了保证单例模式不被破坏,必须声明所有的实例字段为 `transient`,并提供一个 `readResolve` 方法(详见第 89 条)。否则,每当序列化的实例被反序列化时,就会创建一个新的实例,在我们的例子中,导致出现新的 Elvis 实例。为了防止这种情况发生,将如下的 `readResolve` 方法添加到 Elvis 类:

Expand All @@ -56,4 +56,4 @@ public enum Elvis {
}
```

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