条款2:当构造器有很多参数时考虑采用建造者模式(Builder Pattern)
静态工厂和构造器都有一个缺点:他们都不能很好地处理大量可选参数。
当遇到多个可选参数时,一种传统的方式是使用重叠构造器模式(telescoping constructor pattern):
public class Telescoping {
private final int f1; // 必选参数
private final int f2; // 必选参数
private final int f3; // 可选参数
private final int f4; // 可选参数
private final int f5; // 可选参数
public Telescoping(int f1, int f2) {
this(f1, f2, 0, 0, 0);
}
public Telescoping(int f1, int f2, int f3) {
this(f1, f2, f3, 0, 0);
}
public Telescoping(int f1, int f2, int f3, int f4) {
this(f1, f2, f3, f4, 0);
}
public Telescoping(int f1, int f2, int f3, int f4, int f5) {
this.f1 = f1;
this.f2 = f2;
this.f3 = f3;
this.f4 = f4;
this.f5 = f5;
}
}
但是这种方式会强制传入一些不需要的参数。
Telescoping telescoping = new Telescoping(1, 2, 0, 0, 5);
f3和f4是用不到的参数,但必须要传入0才行。
因此,虽然重叠构造器模式也能用,但客户端调用时非常的不方便而且也很难读懂。而且在面对大量参数时,需要添加的构造器也会多到怀疑人生。
另一种方法是JavaBean模式:
public class JavaBean {
private int f1 = -1; // 无默认值
private int f2 = -1; // 无默认值
private int f3 = 0; // 默认值
private int f4 = 0; // 默认值
private int f5 = 0; // 默认值
public JavaBean() {
}
public int getF1() {
return f1;
}
public void setF1(int f1) {
this.f1 = f1;
}
public int getF2() {
return f2;
}
public void setF2(int f2) {
this.f2 = f2;
}
public int getF3() {
return f3;
}
public void setF3(int f3) {
this.f3 = f3;
}
public int getF4() {
return f4;
}
public void setF4(int f4) {
this.f4 = f4;
}
public int getF5() {
return f5;
}
public void setF5(int f5) {
this.f5 = f5;
}
}
这种方式比重叠构造器模式好一些了,但还是有缺陷:
1. 由于构造被拆分成多次调用,JavaBean可能会在构造过程中处于不一致状态。
2. 无法构建不可变类。
这时,我们的主角建造者模式(Builder Pattern)就要登场了:
public class MyClass {
private final int f1; // 必选参数
private final int f2; // 必选参数
private final int f3; // 可选参数
private final int f4; // 可选参数
private final int f5; // 可选参数
private MyClass(Builder builder) {
f1 = builder.f1;
f2 = builder.f2;
f3 = builder.f3;
f4 = builder.f4;
f5 = builder.f5;
}
public static class Builder {
private final int f1; // 必选参数
private final int f2; // 必选参数
private int f3; // 可选参数
private int f4; // 可选参数
private int f5; // 可选参数
public Builder(int f1, int f2) {
this.f1 = f1;
this.f2 = f2;
}
public Builder setF3(int f3) {
this.f3 = f3;
return this;
}
public Builder setF4(int f4) {
this.f4 = f4;
return this;
}
public Builder setF5(int f5) {
this.f5 = f5;
return this;
}
public MyClass builder() {
return new MyClass(this);
}
}
}
当然,这种模式也有缺陷:
1. 当Builder创建开销很大时会影响性能表现。
2. 用Builder创建对象时代码会很长,所以最好在有足够多的参数时使用该模式,大概四个及以上。但如果开发过程中会添加很多参数的话,最好还是以Builder模式起手,而非用了其他的方法半路改成Builder模式。
总结一下,当构造器有很多参数,特别是有部分可选参数时,建造者模式会是一个很好的选择。
条款3:强制单例模式使用私有构造器或枚举类
对于单例模式,有两种简单又使用的实现方式。
第一种,使用枚举类:
public enum Singleton {
INSTANCE;
public void doSomething() {
}
}
第二种,使用静态内部类:
public class Singleton {
private Singleton() {
}
private static class InstanceHolder {
private static Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return InstanceHolder.INSTANCE;
}
public void doSomething() {
}
}
条款4:不可实例化类一定要用私有构造器
在Java程序设计中会有一些类只有静态方法和静态字段,比如java.lang.Math、java.util.Collections,还有自己设计的一些工具类。这些类不应该被实例化,实例化了也没啥用。但如果没有显式的构造器,编译器会自动生成一个公有的无参构造器,对用户也是可见且难以区分的。设计成抽象类又会误导用户去继承它。于是,需要将构造器设置为私有来使类不可实例化。
public class MyUtil {
private MyUtil() {
throw new AssertionError();
}
}
抛AssertionError不是必须的,但它可以防止在类内不小心调用构造器,或者有坏蛋用反射在外边强行构造。这种写法保证类不会被实例化,同时也不会被继承。