mirror of
https://github.com/sjsdfg/effective-java-3rd-chinese.git
synced 2025-01-26 20:30:36 +08:00
2. 当构造方法参数过多时使用 builder 模式
This commit is contained in:
parent
d4a7bf829b
commit
e5fec3ce9b
@ -53,11 +53,11 @@ public class NutritionFacts {
|
||||
NutritionFacts cocaCola = new NutritionFacts(240, 8, 100, 0, 35, 27);
|
||||
```
|
||||
|
||||
通常情况下,这个构造方法的调用需要许多你不想设置的参数,但是你不得不为它们传递一个值。 在这种情况下,我们为 `fat` 属性传递了 0 值。 『只有』六个参数可能看起来并不那么糟糕,但随着参数数量的增加,它会很快失控。
|
||||
通常情况下,这个构造方法的调用需要许多你不想设置的参数,但是你不得不为它们传递一个值。 在这种情况下,我们为 `fat` 属性传递了 0 值。「只有」六个参数可能看起来并不那么糟糕,但随着参数数量的增加,它会很快失控。
|
||||
|
||||
简而言之,可伸缩构造方法模式是有效的,但是当有很多参数时,很难编写客户端代码,而且很难读懂它。读者不知道这些值是什么意思,并且必须仔细地计算参数才能找到答案。一长串相同类型的参数可能会导致一些细微的 bug。如果客户端意外地反转了两个这样的参数,编译器并不会抱怨,但是程序在运行时会出现错误行为 (条目 51)。
|
||||
|
||||
当在构造方法中遇到许多可选参数时,另一种选择是 JavaBeans 模式,在这种模式中,调用一个无参数的构造函数来创建对象,然后调用 `setter` 方法来设置每个必需的参数和可选参数:
|
||||
当在构造方法中遇到许多可选参数时,另一种选择是 JavaBeans 模式,在这种模式中,调用一个无参数的构造函数来创建对象,然后调用 `setter` 方法来设置每个必需的参数和可选参数:
|
||||
|
||||
```java
|
||||
// JavaBeans Pattern - allows inconsistency, mandates mutability
|
||||
@ -94,9 +94,9 @@ cocaCola.setSodium(35);
|
||||
cocaCola.setCarbohydrate(27);
|
||||
```
|
||||
|
||||
不幸的是,JavaBeans 模式本身有严重的缺陷。由于构造方法在多次调用中被分割,所以在构造过程中 JavaBean 可能处于不一致的状态。该类没有通过检查构造参数参数的有效性来执行一致性的选项。在不一致的状态下尝试使用对象可能会导致与包含 bug 的代码大相径庭的错误,因此很难调试。一个相关的缺点是,JavaBeans 模式排除了让类不可变的可能性 (条目 17),并且需要在程序员的部分增加工作以确保线程安全。
|
||||
不幸的是,JavaBeans 模式本身有严重的缺陷。由于构造方法在多次调用中被分割,所以在构造过程中 JavaBean 可能处于不一致的状态。该类没有通过检查构造参数参数的有效性来执行一致性的选项。在不一致的状态下尝试使用对象可能会导致与包含 bug 的代码大相径庭的错误,因此很难调试。一个相关的缺点是,JavaBeans 模式排除了让类不可变的可能性(条目 17),并且需要在程序员的部分增加工作以确保线程安全。
|
||||
|
||||
当它的构造完成时,手动“冻结”对象,并且不允许它在解冻之前使用,可以减少这些缺点,但是这种变体在实践中很难使用并且很少使用。 而且,在运行时会导致错误,因为编译器无法确保程序员在使用对象之前调用 `freeze` 方法。
|
||||
通过在对象构建完成时手动「冻结」对象,并且不允许它在解冻之前使用,可以减少这些缺点,但是这种变体在实践中很难使用并且很少使用。 而且,在运行时会导致错误,因为编译器无法确保程序员在使用对象之前调用 `freeze` 方法。
|
||||
|
||||
幸运的是,还有第三种选择,它结合了可伸缩构造方法模式的安全性和 JavaBean 模式的可读性。 它是 Builder 模式[Gamma95] 的一种形式。客户端不直接调用所需的对象,而是调用构造方法 (或静态工厂),并使用所有必需的参数,并获得一个 builder 对象。然后,客户端调用 builder 对象的 `setter` 相似方法来设置每个可选参数。最后,客户端调用一个无参的 `build` 方法来生成对象,该对象通常是不可变的。Builder 通常是它所构建的类的一个静态成员类 (条目 24)。以下是它在实践中的示例:
|
||||
|
||||
@ -269,7 +269,7 @@ public class Calzone extends Pizza {
|
||||
|
||||
请注意,每个子类 builder 中的 `build` 方法被声明为返回正确的子类:`NyPizza.Builder` 的 `build` 方法返回 `NyPizza`,而 `Calzone.Builder` 中的 `build` 方法返回 `Calzone`。 这种技术,其一个子类的方法被声明为返回在超类中声明的返回类型的子类型,称为协变返回类型 ( covariant return typing)。 它允许客户端使用这些 builder,而不需要强制转换。
|
||||
|
||||
这些“分层 builder”的客户端代码基本上与简单的 `NutritionFacts` builder 的代码相同。为了简洁起见,下面显示的示例客户端代码假设枚举常量的静态导入:
|
||||
这些「分层 builder」的客户端代码基本上与简单的 `NutritionFacts` builder 的代码相同。为了简洁起见,下面显示的示例客户端代码假设枚举常量的静态导入:
|
||||
|
||||
```java
|
||||
NyPizza pizza = new NyPizza.Builder(SMALL)
|
||||
|
Loading…
Reference in New Issue
Block a user