浅谈设计模式--建造器模式(Builder Pattern)
建造器模式,是于创建带有大量参数的对象,并避免因参数数量多而产生的一些问题(如状态不一致-JavaBean的setter模式)。
如果参数多且有些是必须初始化的,有些是不一定需要初始化的时候,创建对象是非常麻烦的,因为不得不为每种情况都添加一个构造方法。建造器模式,就是为了解决这个问题的。
使用Builder模式并不难:
1.创造一个静态内部建造类(Builder Class. e.g. UserBuilder)
2.类的构造方法必须设置为private,防止类被正常构造
3.建造类提供public方法,来设置可选的参数,并返回Builder对象
4.最后建造类提供build()方法,真正创建原来的类的对象
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 = builder.address; } // a list of getter method @Override public String toString() { return firstName + " " + lastName + "-" + age + " , " + phone + "/" + address; } // Builder Class public static class UserBuilder { private final String firstName; private final String lastName; private int age; private String phone; private String address; public UserBuilder(String firstName, String lastName) { this.firstName = firstName; this.lastName = lastName; } public UserBuilder age(int age) { this.age = age; return this; } public UserBuilder phone(String phone) { this.phone = phone; return this; } public UserBuilder address(String address) { this.address = address; return this; } public User build() { return new User(this); } } public static void main(String[] args) { // TODO Auto-generated method stub System.out.println(new User.UserBuilder("Jhon", "Doe").age(30) .phone("1234567").address("Fake address 1234").build() .toString()); } }
另外,在build()方法里,也可以检验参数的正确性,例如:
public User build() { User user = new user(this); if (user.getAge()<120) { throw new IllegalStateException(“Age out of range”); // thread-safe } return user; }
这是线程安全的做法,因为user已经是不可变对象。下面的非线程安全做法,应该避免:
public User build() { if (age 120) { throw new IllegalStateException(“Age out of range”); // bad, not thread-safe } // This is the window of opportunity for a second thread to modify the value of age return new User(this); }
但是,这种建造器的使用方法,其实是有隐患的:
1. 它没有指引用户,一步步的进行构建对象;用户并不知道何时何地用何方法
2. 状态不一致的风险仍然存在
如果需要构建顺序的话,可以做如下修改,来使建造器模式更加人性化:
public class NewUser { 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 NewUser(UserBuilder builder) { this.firstName = builder.firstName; this.lastName = builder.lastName; this.age = builder.age; this.phone = builder.phone; this.address = builder.address; } // a list of getter method @Override public String toString() { return firstName + " " + lastName + "-" + age + " , " + phone + "/" + address; } public static interface FirstNameStep { LastNameStep firstName(String name); } public static interface LastNameStep { AgeStep lastName(String lastName); } public static interface AgeStep { PhoneStep age(int age); } public static interface PhoneStep { AddressStep phone(String phone); } public static interface AddressStep { BuildStep address(String address); } public static interface BuildStep { NewUser build(); } // Builder Class public static class UserBuilder implements FirstNameStep, LastNameStep, AgeStep, PhoneStep, AddressStep, BuildStep { private String firstName; private String lastName; private int age; private String phone; private String address; private UserBuilder() { } public static FirstNameStep newBuilder() { return new UserBuilder(); } public LastNameStep firstName(String firstName) { this.firstName = firstName; return this; } public AgeStep lastName(String lastName) { this.lastName = lastName; return this; } public PhoneStep age(int age) { this.age = age; return this; } public AddressStep phone(String phone) { this.phone = phone; return this; } public UserBuilder address(String address) { this.address = address; return this; } public NewUser build() { return new NewUser(this); } } public static void main(String[] args) { NewUser user = NewUser.UserBuilder.newBuilder().firstName("ABC") .lastName("haha").age(10).phone("123").address("wa").build(); System.out.println(user.toString()); } }
这个实现更加复杂,利用了接口的设计,使得建造器创建对象时,可以一步接着一步(firstName->lastName->age->phone->address),相当友好。缺点是,实现有点复杂,代码量比较大。
参考:
http://www.javacodegeeks.com/2013/01/the-builder-pattern-in-practice.html
http://rdafbn.blogspot.ie/2012/07/step-builder-pattern_28.html