7月
27
2010

采用Builder模式构造对象

通常构造对象时,我们会采用构造函数的方式来对对象的参数进行初始化,例如:

Student stu = new Student(2010, "Hesey", 0, 10, "Software College");

但这种方式带来的问题是可读性很差,程序员必须很清楚构造函数中各个参数是什么及其顺序,如果光看上面这条语句,程序员或许会知道这可能是在创建一个学生对象并作其参数的初始化,但是参数究竟意味着什么,就必须去看源代码或是查阅文档了。

所幸的是,JavaBeans规范给我们提供了一个可读性更强的解决方案,通过一连串的get,set方法对参数进行获取或设置,此时对Student的创建可以改为如下代码:

Student stu = new Student();
stu.setId(2010);
stu.setName("Hesey");
stu.setSex(0);
stu.setDoor(10);
stu.setAddress("Software College");

运用JavaBeans的这种方式,大大增强了对象参数初始化的可读性,程序员很清楚地可以知道正在设置的是什么参数,并且不必遵循类似构造函数那样的参数顺序。

问题在于,在对对象初始化的过程中,显而易见,各个参数的初始化被放到了不同的方法调用中,这会导致严重的线程不安全问题(构造函数则不存在这个问题)。对象在一连串的set方法中,可能会出现状态不一致的情况,这是应该尽量避免的。

而Builder模式则是兼具了构造函数的线程安全性和JavaBeans可读性优点。其主要原理是在类的内部构造一个内部类——Builder类,Builder类通过类似set的方法对参数进行初始化,最后调用build()方法创建其所属类的新对象并将其自身返回给这个新对象,一次性完成构造工作。

代码如下:
Student.java:

public class Student {
	private int id;
	private String name;
	private int sex;
	private int door;
	private String address;

	public Student(Builder builder) {
		id = builder.id;
		name = builder.name;
		sex = builder.sex;
		door = builder.door;
		address = builder.address;
	}

	public void print() {
		System.out.print("Student " + name + " 's id is " + id + " , sex is " + (sex == 0 ? "Male" : "Female") + " , door is " + door + " , address is " + address + ".");
	}

	public static class Builder {
		//Unchangeable Parameters
		private final int id;
		private final String name;

		//Changeable Parameters
		private int sex;
		private int door;
		private String address;

		public Builder(int id, String name) {
			this.id = id;
			this.name = name;
			sex = 0;
			door = 0;
			address = null;
		}

		public Builder sex(int sex) {
			this.sex = sex;
			return this;
		}

		public Builder door(int door) {
			this.door = door;
			return this;
		}

		public Builder address(String address) {
			this.address = address;
			return this;
		}

		public Student build() {
			return new Student(this);
		}
	}
}

BuildStudent.java:

public class BuildStudent {
	public static void main(String[] args) {
		Student stu = new Student.Builder(2010, "Hesey").sex(0).door(10).address("Software College").build();
		stu.print();
	}
}
//Output:
//Student Hesey 's id is 2010 , sex is Male , door is 10 , address is Software College.

观察这条语句:

Student stu = new Student.Builder(2010, "Hesey").sex(0).door(10).address("Software College").build();

可以很清楚的看到,我们在用构造函数构造了两个必要的参数(学号和姓名)之后,可以用类似于JavaBeans的set方法来初始化参数,事实上这种方式比set更为清晰明了,有如直接访问对象参数一般(实际还是方法调用)。

在构造复杂对象时,建议采用这种方式。

Written by Hesey Wang in: Java,技术,面向对象 |

3 Comments »

  • serenity

    Student的构造方法定义为私有更好

    [回复]

    Comment | 2014 年 08 月 07 日
  • 匿名

    为什么javaBean的set的方式会有线程安全问题呢?
    如果是局部变量的话,是分配在堆栈上面的,而堆栈是线程私有的,没有共享资源啊。
    求解惑

    [回复]

    雨帆 回复:

    我也很奇怪,这里为何会有线程安全的问题。不过Builder的Fluent API方式很有爱。

    [回复]

    Comment | 2014 年 11 月 22 日

RSS feed for comments on this post. TrackBack URL

Leave a comment

©2006 - 2016 Hesey (舒)