Skip to content

Commit 05218b1

Browse files
committed
docs: update effective-java.md
* 第 2 条:遇到多个构造器参数时要考虑用构建器 * 第 3 条:用私有构造器或者枚举类型强化 Singleton 属性
1 parent 865ac8b commit 05218b1

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed

docs/effective-java.md

+191
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,194 @@ Map<String, List<String>> m = HashMap.newInstance();
3131
1. 类如果只包含私有构造器,那么就不能被子例化(继承)。但这样也许也会因祸得福,因为它鼓励使用复合,而不是继承;
3232
1. 静态工厂方法与其它静态方法没什么区别,无法像构造器一样在 API 文档中明确标识出来。但是,静态工厂方法有一些惯用名称,如 `valueOf`, `of`, `getInstance`, `newInstance`......
3333

34+
### 第 2 条:遇到多个构造器参数时要考虑用构建器
35+
考虑用一个类表示包含食品外面显示的营养成分标签。这些标签有几个域是必需的,还有超过 20 个可选域。大多数产品在某几个可选域中都会有非零的值。
36+
37+
对于这样的类,应该用哪种构造器或者静态方法来编写呢?
38+
39+
1. 重叠构造器模式
40+
41+
第一种方式是**重复构造器模式**。先提供一个只有必要参数的构造器,再提供一个有一个可选参数的构造器,接着是两个可选参数的构造器,依次类推,最后一个构造器包含所有可选参数。
42+
43+
```java
44+
public class NutritionFacts {
45+
private final int servingSize;
46+
private final int servings;
47+
private final int calories;
48+
private final int fat;
49+
private final int sodium;
50+
private final int carbohydrate;
51+
52+
public NutritionFacts(int servingSize, int servings) {
53+
this(servingSize, servings, 0);
54+
}
55+
56+
public NutritionFacts(int servingSize, int servings, int calories) {
57+
this(servingSize, servings, calories, 0);
58+
}
59+
60+
public NutritionFacts(int servingSize, int servings, int calories, int fat) {
61+
this(servingSize, servings, calories, fat, 0);
62+
}
63+
64+
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
65+
this(servingSize, servings, calories, fat, sodium, 0);
66+
}
67+
68+
public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
69+
this.servingSize = servingSize;
70+
this.servings = servings;
71+
this.calories = calories;
72+
this.fat = fat;
73+
this.sodium = sodium;
74+
this.carbohydrate = carbohydrate;
75+
}
76+
}
77+
```
78+
79+
想要创建实例的时候,就利用参数列表最短的构造器,但该列表中包含了要设置的所有参数:
80+
81+
```java
82+
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
83+
```
84+
85+
这个构造器通常需要许多你本不想设置的参数,但还是不得不为它们传递值。随着参数数目的增多,很快就会失去了控制。客户端代码也会很难编写,可读性也不好。
86+
87+
88+
2. JavaBean 模式
89+
90+
第二种模式是 **JavaBean** 模式,在这种模式下,创建一个无参构造器来创建对象,然后调用 setter 方法设置每个必要的参数。这种模式,弥补了重叠构造器模式的不足,代码读起来也很容易,很多读者应该都很熟悉了。
91+
92+
```java
93+
NutritionFacts cocaCola = new NutritionFacts();
94+
cocaCola.setServingSize(200);
95+
cocaCola.setServings(8);
96+
cocaCola.setCalories(100);
97+
cocaCola.setSodium(35);
98+
cocaCola.setCarbohydrate(27);
99+
```
100+
101+
遗憾的是,JavaBean 模式自身有很严重的缺点。因为构造过程被分到了几个调用中,在构造过程中 JavaBean 可能处于不一致的状态。若试图使用处于不一致状态的对象,将会导致失败,调试起来也十分困难。程序员需要付出额外的努力来确保它的线程安全。
102+
103+
3. Builder 模式
104+
105+
有第三种替代方法,既能保证像重叠构造器模式那样的安全性,也能保证像 JavaBean 模式一样,有很好的可读性,就是 **Builder** 模式。
106+
107+
```java
108+
public class NutritionFacts {
109+
private final int servingSize;
110+
private final int servings;
111+
private final int calories;
112+
private final int fat;
113+
private final int sodium;
114+
private final int carbohydrate;
115+
116+
public static class Builder {
117+
// Required params
118+
private final int servingSize;
119+
private final int servings;
120+
121+
// Optional params
122+
private int calories = 0;
123+
private int fat = 0;
124+
private int sodium = 0;
125+
private int carbohydrate = 0;
126+
127+
public Builder(int servingSize, int servings) {
128+
this.servingSize = servingSize;
129+
this.servings = servings;
130+
}
131+
132+
public Builder calories(int val) {
133+
this.calories = val;
134+
return this;
135+
}
136+
public Builder fat(int val) {
137+
this.fat = val;
138+
return this;
139+
}
140+
public Builder sodium(int val) {
141+
this.sodium = val;
142+
return this;
143+
}
144+
public Builder carbohydrate(int val) {
145+
this.carbohydrate = val;
146+
return this;
147+
}
148+
public NutritionFacts build() {
149+
return new NutritionFacts(this);
150+
}
151+
}
152+
153+
// 私有构造器
154+
private NutritionFacts(Builder builder) {
155+
servingSize = builder.servingSize;
156+
servings = builder.servings;
157+
calories = builder.calories;
158+
fat = builder.fat;
159+
sodium = builder.sodium;
160+
carbohydrate = builder.carbohydrate;
161+
}
162+
}
163+
```
164+
165+
客户端代码就很容易编写了,更为重要的是,易于阅读。
166+
167+
```java
168+
NutritionFacts cocaCola = new NutritionFacts.Builder(240, 8)
169+
.calories(100)
170+
.sodium(35)
171+
.carbohydrate(27)
172+
.build();
173+
```
174+
175+
Builder 模式也有它自身的不足。为了创建对象,必须先创建它的构建器。虽然创建构建器的开销在实践种可能不那么明显,但是在某些十分注重性能的情况下,可能就成了问题了。Builder 模式还比重叠构造器模式更加冗长,因此它只在有很多参数的时候才使用,比如 4 个或者更多个参数。
176+
177+
简而言之,如果类的构造器或者静态工厂中具有多个参数,设计这种类时,Builder 模式就是种不错的选择,特别是当大多数参数都是可选的时候。它较传统的重叠构造器模式相比,更易于阅读;而较 JavaBean 模式,更加安全。
178+
179+
### 第 3 条:用私有构造器或者枚举类型强化 Singleton 属性
180+
1. 公有静态成员
181+
182+
```java
183+
public class Singleton {
184+
public static final Singleton INSTANCE = new Singleton();
185+
private Singleton() {}
186+
}
187+
```
188+
189+
2. 静态工厂方法
190+
191+
```java
192+
public class Singleton {
193+
private static final Singleton INSTANCE = new Singleton();
194+
private Singleton() {}
195+
196+
public static Singleton getInstance() {
197+
return INSTANCE;
198+
}
199+
}
200+
```
201+
202+
这两种方法都能保证 Singleton 的全局唯一性。但是,享有特权的客户端可以借助 `AccessibleObject.setAccessible` 方法,通过反射机制调用私有构造器。如果需要抵御这种攻击,可以修改构造器,让它在被要求创建第二个实例的时候抛出异常。
203+
204+
为了使利用这其中一种方法实现的 Singleton 类变成可序列化的,仅仅在声明中加上 `implements Serializable` 是不够的。为了维护并保证 Singleton,必须声明所有实例域都是瞬时(transient)的,并提供一个 `readResolve` 方法。否则,每次反序列化一个序列化的实例时,都会创建一个新的实例。
205+
206+
```java
207+
priavte Object readResolve() {
208+
return INSTANCE;
209+
}
210+
```
211+
212+
3. 单元素枚举
213+
214+
Java 1.5 版本开始,实现 Singleton 有了第三种方法。只需编写一个包含单个元素的枚举类型:
215+
216+
```java
217+
public enum Singleton {
218+
INSTANCE;
219+
220+
public void otherMethods() {...}
221+
}
222+
```
223+
224+
这种方法更加简洁,无偿提供了序列化机制,绝对防止多次实例化,是实现 Singleton 的最佳方式。

0 commit comments

Comments
 (0)