第二条 遇到多个构造器参数时要考虑用构建器
大家还记得第一条的静态工厂吗?静态工厂来代替构造器有4个优点:自定义方法名;避免创建过多的实例;隐藏子类的实现细节;在泛型类里面可以节约客户端代码。
这一篇博客我们就来了解一下构建器。什么是构建器,可能大家没听说过这个名字。但我相信大家肯定在自己写代码或者看别人的代码的时候看到过build。
比如A a = new A.builder().aa().aaa().aaaa().build();这个就是我们今天要讲的构建器。提出构建器是为了弥补静态工厂和构造器共同的局限性:不能很好的扩展到大量的可选参数。
第一种重叠构造器模式:
class A
{
int a;
int aa;
int aaa;
int aaaa;
A()
{
}
A(int a)
{
this.a = a;
}
A(int a,int aa)
{
this(a);
this.aa = aa;
}
A(int a,int aa,int aaa)
{
this(a,aa);
this.aaa = aaa;
}
A(int a,int aa,int aaa,int aaaa)
{
this(a, aa, aaa);
this.aaaa = aaaa;
}
}
A类有四个可参数,那么我们写构造函数的话就要写4个加上空构造函数则为5个(我们这里还没有考虑排列组合。。)。那一旦参数变成50个或者更多,我们写构造函数的量就大大增加,而且基本代码都是一模一样的。还有一点,我们在写客户端的时候想初始化a和aaa,但我们没有编写这个特定的构造器,那我们只能A a = new A(1,0,1,0);平白无故要多写两个0。这点当参数增加的时候,客户端代码也很难写。
书中说道:重叠构造器模式可行,但是当有许多参数时,客户端代码会很难编写,并且仍然较难以阅读。因为我传进去那么多个0,我都不知道这些0是干嘛的。
另外一种模式为JavaBeans模式:
class A
{
int a;
int aa;
int aaa;
int aaaa;
public void setA(int a)
{
this.a = a;
}
public void setAA(int aa)
{
this.aa = aa;
}
public void setAAA(int aaa)
{
this.aaa = aaa;
}
public void setAAAA(int aaaa)
{
this.aaaa = aaaa;
}
}
这里我们把各个参数写成各个set方法,这样子我们在客户端实例化对象的时候就可以用什么参数set什么参数。这看起来非常不错,而且可读性大大增加。但是JavaBeans自身有严重的缺点:不能保证对象的一致性。
我们复写一个toString方法用于输出参数的情况:
public String toString()
{
return "a="+a+" aa="+aa+" aaa="+aaa+" aaaa="+aaaa;
}
在客户端里面这么写:
A a = new A();
System.out.println(a.toString());
a.setA(1);
System.out.println(a.toString());
输出结果为:
a=0 aa=0 aaa=0 aaaa=0
a=1 aa=0 aaa=0 aaaa=0
大家可以很明显看到这个对象是不安全的,尤其是在多线程环境下。如果要用JavaBeans模式,我们就应该多一些额外的努力来保证其线程安全。
接下来介绍我们的构建器模式:
public class Main {
public static void main(String args[]) {
A a = new A.ABuilder().addA(1).addAA(2).addAAAA(4).build();
System.out.println(a.toString());
}
}
class A
{
int a;
int aa;
int aaa;
int aaaa;
public static class ABuilder
{
int a;
int aa;
int aaa;
int aaaa;
public ABuilder addA(int a)
{
this.a = a;
return this;
}
public ABuilder addAA(int aa)
{
this.aa = aa;
return this;
}
public ABuilder addAAA(int aaa)
{
this.aaa = aaa;
return this;
}
public ABuilder addAAAA(int aaaa)
{
this.aaaa = aaaa;
return this;
}
public A build()
{
return new A(this);
}
}
private A(ABuilder aBuilder) {
a = aBuilder.a;
aa = aBuilder.aa;
aaa = aBuilder.aaa;
aaaa = aBuilder.aaaa;
}
public String toString()
{
return "a="+a+" aa="+aa+" aaa="+aaa+" aaaa="+aaaa;
}
}
- 先在A内部定义一个构建器的内部类ABuilder,并把A的参数一模一样在ABuilder里面写一份。
- 然后在ABuilder里面写一些设置参数的方法,不设置即为默认值。注意这些返回值为ABuilder,这样子写能帮我们节约一些代码,如A a = new A.ABuilder().addA(1).addAA(2).addAAAA(4).build();
- 在A类里面写一个构造方法,传入ABuilder的对象,并把该对象里面的参数拿出来赋值给A。
- 在ABuilder里面写一个build()方法,返回一个新的A对象,return new A(this);其实就是调用了我们第三步写的那个构造方法,传入this就是把当前的ABuilder实例传进去。
我想我应该已经写的蛮详细了吧~
总结:
构建器模式既能有构造器那样的安全性,也可以有JavaBeans的可读性,在多个参数的时候比较实用。在参数少的时候就代码量相比构造器多一些。还有一点有必要提及的是,大家会发现如果我们使用构建器的时候,如果要创建一个对象比如A,那么我们就必须先创建一个构建器对象ABuilder。这一步操作会有时间的开销,在一些特别注重效率和速度的程序里,这个可能会造成一些问题。但在大部分程序里面,我们都是可以不计这一点的时间开销的~
所以如果你编写的类需要有很多参数时,请优先考虑构建器模式!!