建造者模式实践(一)

2014-11-23 19:07:44 · 作者: · 浏览: 35

建造者模式实践


本文翻译自:http://www.javacodegeeks.com/2013/01/the-builder-pattern-in-practice.html

转载请注明出处:http://blog.csdn.net/kingviker/article/details/27058051

我不打算跳入设计模式的过多细节中,因为已经有一大堆的文章和书籍很详细的解释过了。所以我打算告诉你为什么和什么时候你应该考虑使用设计模式。然而,值得一提的是本文中的模式和设计模式可复用面向对象软件基础一书中的提出的有点不一样。因为原生模式专注于抽象构建的步骤所以通过使用不同的建造者模式的实现我们能得到不同的结果,然而在本文中解释的设计模式是讨论如何移除源自于多构造函数,多可选参数和滥用的setters方法中那些不必要的复杂的事物。

假设你有一个包含大量属性的类,就像下面的User类一样。让我们假设你想让这个类不可变(顺便说一句,除非你有一个真正的好理由,让你不必总是向着不可变这个目标奋斗。然而我们会在另一篇文章中讨论它)。

public class User {
    private final String firstName;    //required
    private final String lastName;    //required
    private final int age;    //optional
    private final String phone;    //optional
    private final String address;    //optional
    ...
}

现在想象一下,在你的类中有一些属性是必须的,有一些是可选的。你会如何创建这个类的实例?所有的属性都被声明成final类型,所以你必须在构造方法中设置它们,但是你也想让这个类的客户端有忽略可选属性的机会。

一个首先想到的可选方案是有一个构造方法是只接收必须属性作为参数,一个是接收所有必须属性和第一个可选属性,再一个是接收两个可选属性等等。这看起来什么样子那?
看起来这样:

public User(String firstName, String lastName) {
    this(firstName, lastName, 0);
}

public User(String firstName, String lastName, int age) {
    this(firstName, lastName, age, '');
}

public User(String firstName, String lastName, int age, String phone) {
    this(firstName, lastName, age, phone, '');
}

public User(String firstName, String lastName, int age, String phone, String address) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.age = age;
    this.phone = phone;
    this.address = address;
}

这种构造对象的方式的好处是它可以正常工作。然而,这种方式的问题也是显而易见的。当你只有几个属性的时候不是什么大问题,但是随着属性个数的增加,代码变的越来越难阅读和维护。更重要的是,对客户端来说代码变得越来越难使用。 客户端里我该调用那一个构造方法?有两个参数那个?三个参数那个?我没有显示传值的那些参数的默认值都是什么?如何我只想给address属性设置一个值,但是不想给age和phone设置该怎么办?这种情况下,我不得不调用能接收所有参数的构造方法并传递默认值给那些我不关心的可选参数。另外,一些参数有相同的类型很容易混淆。第一个String类型参数对应的是phone还是address?

因此我们有什么其他选择来应对这种场景?我们可以总是遵循JavaBeans惯例,有一个默认的无参构造方法并且每个属性都有getter和setter方法。就象这样:

public class User {
    private String firstName; // required
    private String lastName; // required
    private int age; // optional
    private String phone; // optional
    private String address;  //optional

public String getFirstName() {
    return firstName;
}
public void setFirstName(String firstName) {
    this.firstName = firstName;
}
public String getLastName() {
    return lastName;
}
public void setLastName(String lastName) {
    this.lastName = lastName;
}
public int getAge() {
    return age;
}
public void setAge(int age) {
    this.age = age;
}
public String getPhone() {
    return phone;
}
public void setPhone(String phone) {
    this.phone = phone;
}
public String getAddress() {
    return address;
}
public void setAddress(String address) {
    this.address = address;
}
}

这种方法看起来很容易阅读和维护。在客户端里我可以只创建一个空对象,然后只设置那些我感兴趣的属性。那么,这种方法有什么问题?这种解决方案有两个主要的问题。第一个问题是该类的实例状态不固定。如果你想创建一个User对象,该对象的5个属性都要赋值,那么直到所有的setXX方法都被调用之前,该对象都没有一个完整的状态。这意味着在该对象状态还不完整的时候,一部分客户端程序可能看见这个对象并且以为该对象已经构造完成。这种方法的第二个不足是User类是易变的。你将会失去不可变对象带来的所有优点。

幸运的是应对这种场景我们有第三种选择,建造者模式。解决方案类似如下所示:

public class User {
    private final String firstName; // required
    private final String lastName; // required
    private final int age; // optional
    private final String phone; // optional
    private final String address; // optional

private User(UserBuilder builder) {
    this.firstName = builder.firstName;
    this.lastName = builder.lastName;
    this.age = builder.age;
    this.phone = builder.phone;
    this.address = build