在《Java Effective》中提到构建Java对象一般选用静态工厂方法来替代构造器。主要的原因是一个类只能有一个带有指定签名的构造器,因此为避开这一限制编程人员一般通过参数列表只在参数类型顺序上有所不同来提供多个构造器,需要提供多个构造器主要是因为有时候需要构建的对象的参数不一样。
public class Person{
String name;
int age;
public Person(String name){
this.name = name;
}
public Person(String name,int age){
this(name);
this.age = age;
}
}
那么静态工厂方法与构造器相比有哪些优势呢?
1)它们有名称。客户端调用的时候见名知意,而且易于阅读。比如getInstance方法,用户调用的时候见名知意,就知道调用该方法可以获得其所在类的一个实例。当一个类有多个相同签名的构造器的时候,就用静态工厂方法代替构造器,避免了用户不知道该调用哪个构造器的问题。
2)不必每次调用它们的时候都创建一个新对象。尤其是在单实例类中使用非常广泛,比如上面列举的Watchdog.java中的getIns()方法,sWatchdog对象实际上只创建了一次,其他需要用Watchdog类的实例的地方,只需要调用getInstance()方法一次,就可以获取该对象,简单方便。
3)可以返回原返回类型的任何子类型对象。这个主要根据传入工厂静态方法的参数来实现,只要是已声名的返回类型的子类型,都是可以的。这为我们选择返回对象的类型提供了灵活性。
所谓的静态工厂方法是通过专门定义一个类来负责创建其他类的实例,其实例通常拥有共同父类,其普遍实现主要依靠Java的反射机制。
class Child{
int age = 10;
int weight = 30;
public static Child newChild(int age, int weight) {
Child child = new Child();
child.weight = weight;
child.age = age;
return child;
}
public static Child newChildWithWeight(int weight) {
Child child = new Child();
child.weight = weight;
return child;
}
public static Child newChildWithAge(int age) {
Child child = new Child();
child.age = age;
return child;
}
}
如上代码,当我们需要创建三个不同的child的实例时用静态工厂方法可以清楚知道三个方法分别具体创建的是那个实例,如果使用构造器则需要写三个参数不同的构造器但是由于名称是一样的,一旦参数多样化很难分清哪个构造器具体创建的是哪个实例,只能通过查看文档来确认返回的类型。
当我们遇到一个类有很多属性,而仅仅有部分属性是必须的时候,我们通常有如下几种解决办法:
(1)根据属性的可选性,创建很多不同的构造器或者静态工厂方法,但是这种方法将会造成大量的构造方法(静态工厂方法)。
(2)使用javaBeans模式。这种方法的缺点是不能够保证线程的安全性,对于不可变的属性,在我们使用对象的过程当中很容易造成属性的改变。
(3)使用构建器。这种方法将必须的属性直接首先封装进一个类,然后针对可变的属性再通过类的方法进行相应的设置。
但构造器与静态工厂方法都存在一个缺点,当可选参数很多是就需要写多个构造器或者静态方法,而构建器则结合了静态工厂方法以及JavaBean的优点。
先定义一个构建器Builder
/**
* 演示java构建器
* 注意:带有“*”的属性是必须的
* @author LIUTAO
* @version 2017/4/28
* @see
* @since
*/
public class User {
private int id; //用户Id(*)
private String name; //用户名(*)
private String phone; //电话
private Integer age; //年龄
private String email; //邮箱
private String address;//地址
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getPhone() {
return phone;
}
public Integer getAge() {
return age;
}
public String getEmail() {
return email;
}
public String getAddress() {
return address;
}
public static class Builder{
private int id; //用户Id(*)
private String name; //用户名(*)
private String phone; //电话
private Integer age; //年龄
private String email; //邮箱
private String address;//地址
public Builder(int id, String name) {
this.id = id;
this.name = name;
}
public Builder setPhone(String phone){
this.phone = phone;
return this;
}
public Builder setAge(int age){
this.age = age;
return this;
}
public Builder setEmail(String email){
this.email = email;
return this;
}
public Builder setAddress(String address){
this.address = address;
return this;
}
public User builder(){
return new User(this);
}
}
private User(Builder builder){
this.id = builder.id;
this.name = builder.name;
this.phone = builder.phone;
this.email = builder.email;
this.address = builder.address;
this.age = builder.age;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", phone='" + phone + '\'' +
", age=" + age +
", email='" + email + '\'' +
", address='" + address + '\'' +
'}';
}
}
调用构建器来创建实例
/**
* 演示java构建器
*
* @author LIUTAO
* @version 2017/4/28
* @see
* @since
*/
public class Test {
public static void main(String[] args) {
User user = new User.Builder(1,"liubei").setAddress("the south road of person").builder();
System.out.println(user);
}
}
构建器不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或者静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似于setter的方法,来设置每个相关的可选参数。最后,客户端调用无参的build方法来生成不可变的对象。
在这里调用一个无参的构造器,然后调用setter方法来设置每个必要的参数也可以实现。但是javabean自身有着严重的缺点,因为构造过程被分到几个调用中,在构造javabean可能处于不一致的状态,类无法仅仅通过检验构造器参数的有效性来保证一致性。另一点不足之处,javabean模式阻止了把类做成不可变的可能,这就需要程序员付出额外的努力来确保他的线程安全; build模式 既能保证像重叠构造器那样的安全,也能实现JavaBean模式那样的可读性。