1. 意图(Intent)
封装一个对象的构造过程,并允许按步骤构造。
建造者(Builder)模式又称 生成器模式,属于创建型模式。在软件的设计中,我们可能经常会遇到需要构建某个复杂的对象(比如在游戏开发中,进行人物角色的构建),建造该对象的“过程”是稳定的(对于一个人设来都有身体,脸,发型,手脚等),而具体建造的“细节”是不同的(每个人设的身体,脸等各有千秋)。但对于用户来讲,我才不管这些,我只想告诉你,我现在需要某个对象(拥有某特征的人物角色),于是你就创建一个给我就行了。
如果你需要将一个复杂对象的 构建与它的表示分离,使得同样的构建过程可以创建不同的表示的意图时,我们需要应用于一个设计模式,“建造者(Builder)模式”,又叫生成器模式。建造者模式可以将一个产品的内部表象与产品的生产过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。如果我们使用了建造者模式,那么用户就只需指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需要知道了。
经常碰见的 XxxBuilder 的类,通常都是建造者模式的产物。建造者模式其实有很多的变种,但是对于客户端来说,我们的使用通常都是一个模式的:
public class Client {public static void main(String[] args) {Food food = new FoodBuilder().a().b().c().build();Food food = Food.builder().a().b().c().build();}}
套路就是先 new 一个 Builder,然后可以链式地调用一堆方法,最后再调用一次 build() 方法,我们需要的对象就有了。
来一个中规中矩的建造者模式:
class User {// 下面是“一堆”的属性private String name;private String password;private String nickName;private int age;// 构造方法私有化,不然客户端就会直接调用构造方法了private User(String name, String password, String nickName, int age) {this.name = name;this.password = password;this.nickName = nickName;this.age = age;}// 静态方法,用于生成一个 Builder,这个不一定要有,不过写这个方法是一个很好的习惯,// 有些代码要求别人写 new User.UserBuilder().a()...build() 看上去就没那么好public static UserBuilder builder() {return new UserBuilder();}public static class UserBuilder {// 下面是和 User 一模一样的一堆属性private String name;private String password;private String nickName;private int age;private UserBuilder() {}// 链式调用设置各个属性值,返回 this,即 UserBuilderpublic UserBuilder name(String name) {this.name = name;return this;}public UserBuilder password(String password) {this.password = password;return this;}public UserBuilder nickName(String nickName) {this.nickName = nickName;return this;}public UserBuilder age(int age) {this.age = age;return this;}// build() 方法负责将 UserBuilder 中设置好的属性“复制”到 User 中。// 当然,可以在 “复制” 之前做点检验public User build() {if (name == null || password == null) {throw new RuntimeException("用户名和密码必填");}if (age <= 0 || age >= 150) {throw new RuntimeException("年龄不合法");}// 还可以做赋予”默认值“的功能if (nickName == null) {nickName = name;}return new User(name, password, nickName, age);}}}
核心是:先把所有的属性设置给 Builder,然后在调用 build() 方法时,将属性全复制给实际产生的对象。
看看客户端的调用:
public class Client {public static void main(String[] args) {User d = User.builder().name("foo").password("pAss12345").age(25).build();}}
说实话,建造者模式的链式写法很吸引人,但是,多写了很多 “无用” 的 builder 的代码,感觉这个模式没什么用。不过,当属性很多,而且有些必填,有些选填的时候,这个模式会使代码清晰很多。
我们可以在 Builder 的构造方法中强制让调用者提供必填字段,还有,在 build() 方法中校验各个参数比在 User 的构造方法中校验,代码要优雅一些。
题外话,强烈建议读者使用 lombok,用了 lombok 以后,上面的一大堆代码会变成如下这样:
@Builderclass User {private String name;private String password;private String nickName;private int age;}
当然,如果你只是想要链式写法,不想要建造者模式,有个很简单的办法,User 的 getter 方法不变,所有的 setter 方法都让其 return this 就可以了,然后就可以像下面这样调用:
User user = new User().setName("").setPassword("").setAge(20);
很多人是这么用的,但是笔者觉得其实这种写法非常地不优雅,不是很推荐使用。
2. 类图(Class Diagram)
3. 实现(Implementation)
以下是一个简易的 StringBuilder 实现,参考了 JDK 1.8 源码。
public class AbstractStringBuilder {protected char[] value;protected int count;public AbstractStringBuilder(int capacity) {count = 0;value = new char[capacity];}public AbstractStringBuilder append(char c) {ensureCapacityInternal(count + 1);value[count++] = c;return this;}private void ensureCapacityInternal(int minimumCapacity) {// overflow-conscious codeif (minimumCapacity - value.length > 0)expandCapacity(minimumCapacity);}void expandCapacity(int minimumCapacity) {int newCapacity = value.length * 2 + 2;if (newCapacity - minimumCapacity < 0)newCapacity = minimumCapacity;if (newCapacity < 0) {if (minimumCapacity < 0) // overflowthrow new OutOfMemoryError();newCapacity = Integer.MAX_VALUE;}value = Arrays.copyOf(value, newCapacity);}}
public class StringBuilder extends AbstractStringBuilder {
public StringBuilder() {
super(16);
}
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
}
public class Client {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
final int count = 26;
for (int i = 0; i < count; i++) {
sb.append((char) ('a' + i));
}
System.out.println(sb.toString());
}
}
//输出: abcdefghijklmnopqrstuvwxyz
