我们写代码时,随时都要创建对象。 通常我们使用new关键字创建对象,这就涉及调用构造函数来初始化我们的对象。
假设我们有一个描述耳机的类,当中有两个属性:颜色和价格,默认的情况下,耳机是黑色的,售价150美元,于是我们有了以下代码
public class Headphones {
private final Color color;
private final BigDecimal priceInDollars;
public Headphones() {
this.color = Color.BLACK;
this.priceInDollars = BigDecimal.valueOf(150.0);
}
//other methods
}
一段时间后,我们决定制作不同颜色的耳机了,但是价格维持不变,对于这种情况,我们增加了一个新的构造函数:价格设置为默认值,但颜色通过参数传入构造方法:
public Headphones(final Color color) {
this.color = color;
this.priceInDollars = BigDecimal.valueOf(150.0);
}
再过一段时间后,我们决定不同颜色的耳机可以有不同的价格。 于是我们再次添加了一个新的构造函数:
public class Headphones {
private final Color color;
private final BigDecimal priceInDollars;
public Headphones() {
this.color = Color.BLACK;
this.priceInDollars = BigDecimal.valueOf(150.0);
}
public Headphones(final Color color) {
this.color = color;
this.priceInDollars = BigDecimal.valueOf(150.0);
}
public Headphones(final Color color, final BigDecimal priceInDollars) {
this.color = color;
this.priceInDollars = priceInDollars;
}
//other methods
}
我们可以看到,以上的实现导致编码重复。 每个构造函数都必须自己设置所有字段,如果我们想要更改默认值,我们必须改变所有构造函数。 那我们该怎么做呢?
想到的第一个解决方案是我们可以使用默认值:
public class Headphones {
private Color color = Color.BLACK;
private BigDecimal priceInDollars = BigDecimal.valueOf(150.0);
public Headphones() {
}
public Headphones(final Color color) {
this.color = color;
}
public Headphones(final Color color, final BigDecimal priceInDollars) {
this.color = color;
this.priceInDollars = priceInDollars;
}
//other methods
}
现在,我们的构造函数只设置他们想要更改的字段。
我们也可以用另一种方式完成它并使用初始化程序设置默认值:
public class Headphones {
private Color color;
private BigDecimal priceInDollars;
{
this.color = Color.BLACK;
this.priceInDollars = BigDecimal.valueOf(150.0);
}
//other
}
当然,这三种方式可以混合使用。 你可以通过构造函数设置一个字段,通过默认值设置第二个字段,并在初始化程序中设置第三个字段,但有什么好处呢?
我没有看到任何好处,反而发现很多问题:
我们没有一个统一的实现, 每个构造方法的实现都完全独立。
不够简洁,由于有多个实现,使用者需要关心每一个实现
After transformation, our fields cannot be final. We are setting default values regardless of whether we change them or not, so we have to make the possibility of changing it
Unnecessary field initialization is creating an unused object. This is not really a problem until you are creating millions of objects
我们怎么解决这些问题呢?
答案是使用链式构造器。
链式构造器意味着我们以一种方式编写构造函数,构造函数调用其他构造函数,最终到达统一的构造函数。 这个统一的构造函数称为主构造函数,所有其他构造函数称为辅助构造函数。
主构造函数将是我们创建和初始化对象的单一点,因此我们可以将所有常见行为放入其中。 为了保持一致性,我们只有一个主要的构造函数; 理想情况下,此构造函数将设置所有类字段。 此外,重要的是,如果我们不想对外公开主构造函数,那么主构造函数可以设置成私有的。
使用链式构造器,你不必担心代码重复。 因此,它将使你的代码更简单,更易于维护。 所有构造函数最终都调用主构造函数,这是一个可以共享公共代码的单点创建。
我建议你将主构造函数编写为类中的最后一个构造函数,并将其作为所有辅助函数编写,就像我在下面的Headphones所做的那样:
public class Headphones {
private final Color color;
private final BigDecimal priceInDollars;
public Headphones() {
this(Color.BLACK);
}
public Headphones(final Color color) {
this(color, BigDecimal.valueOf(150.0));
}
public Headphones(final Color color, final BigDecimal priceInDollars) {
this.color = color;
this.priceInDollars = priceInDollars;
}
//other methods
}
尽管Java提供了不同的方式实现初始化,我的建议是
- 永远不要使用初始化程序块,永远不要使用默认值初始化成员,始终链接构造函数。
在创建类时,请始终考虑如何正确设计它。 公开许多构造函数将提供不同的创建方式,从而增加代码的可用性。
设计类不仅意味着提供一个易单漂亮的API,而且还意味着保持所有代码内部的可维护性和可读性。