条款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不是必须的,但它可以防止在类内不小心调用构造器,或者有坏蛋用反射在外边强行构造。这种写法保证类不会被实例化,同时也不会被继承。