1. 意图(Intent)

封装一个对象的构造过程,并允许按步骤构造。

建造者(Builder)模式又称 生成器模式,属于创建型模式。在软件的设计中,我们可能经常会遇到需要构建某个复杂的对象(比如在游戏开发中,进行人物角色的构建),建造该对象的“过程”稳定的(对于一个人设来都有身体,脸,发型,手脚等),而具体建造的“细节”不同的(每个人设的身体,脸等各有千秋)。但对于用户来讲,我才不管这些,我只想告诉你,我现在需要某个对象(拥有某特征的人物角色),于是你就创建一个给我就行了。

如果你需要将一个复杂对象的 构建与它的表示分离,使得同样的构建过程可以创建不同的表示的意图时,我们需要应用于一个设计模式,“建造者(Builder)模式”,又叫生成器模式。建造者模式可以将一个产品的内部表象与产品的生产过程分割开来,从而可以使一个建造过程生成具有不同的内部表象的产品对象。如果我们使用了建造者模式,那么用户就只需指定需要建造的类型就可以得到它们,而具体建造的过程和细节就不需要知道了。

经常碰见的 XxxBuilder 的类,通常都是建造者模式的产物。建造者模式其实有很多的变种,但是对于客户端来说,我们的使用通常都是一个模式的:

  1. public class Client {
  2. public static void main(String[] args) {
  3. Food food = new FoodBuilder().a().b().c().build();
  4. Food food = Food.builder().a().b().c().build();
  5. }
  6. }

套路就是先 new 一个 Builder,然后可以链式地调用一堆方法,最后再调用一次 build() 方法,我们需要的对象就有了。

来一个中规中矩的建造者模式:

  1. class User {
  2. // 下面是“一堆”的属性
  3. private String name;
  4. private String password;
  5. private String nickName;
  6. private int age;
  7. // 构造方法私有化,不然客户端就会直接调用构造方法了
  8. private User(String name, String password, String nickName, int age) {
  9. this.name = name;
  10. this.password = password;
  11. this.nickName = nickName;
  12. this.age = age;
  13. }
  14. // 静态方法,用于生成一个 Builder,这个不一定要有,不过写这个方法是一个很好的习惯,
  15. // 有些代码要求别人写 new User.UserBuilder().a()...build() 看上去就没那么好
  16. public static UserBuilder builder() {
  17. return new UserBuilder();
  18. }
  19. public static class UserBuilder {
  20. // 下面是和 User 一模一样的一堆属性
  21. private String name;
  22. private String password;
  23. private String nickName;
  24. private int age;
  25. private UserBuilder() {
  26. }
  27. // 链式调用设置各个属性值,返回 this,即 UserBuilder
  28. public UserBuilder name(String name) {
  29. this.name = name;
  30. return this;
  31. }
  32. public UserBuilder password(String password) {
  33. this.password = password;
  34. return this;
  35. }
  36. public UserBuilder nickName(String nickName) {
  37. this.nickName = nickName;
  38. return this;
  39. }
  40. public UserBuilder age(int age) {
  41. this.age = age;
  42. return this;
  43. }
  44. // build() 方法负责将 UserBuilder 中设置好的属性“复制”到 User 中。
  45. // 当然,可以在 “复制” 之前做点检验
  46. public User build() {
  47. if (name == null || password == null) {
  48. throw new RuntimeException("用户名和密码必填");
  49. }
  50. if (age <= 0 || age >= 150) {
  51. throw new RuntimeException("年龄不合法");
  52. }
  53. // 还可以做赋予”默认值“的功能
  54. if (nickName == null) {
  55. nickName = name;
  56. }
  57. return new User(name, password, nickName, age);
  58. }
  59. }
  60. }

核心是:先把所有的属性设置给 Builder,然后在调用 build() 方法时,将属性全复制给实际产生的对象。
看看客户端的调用:

  1. public class Client {
  2. public static void main(String[] args) {
  3. User d = User.builder().name("foo").password("pAss12345").age(25).build();
  4. }
  5. }

说实话,建造者模式的链式写法很吸引人,但是,多写了很多 “无用”builder 的代码,感觉这个模式没什么用。不过,当属性很多,而且有些必填,有些选填的时候,这个模式会使代码清晰很多。

我们可以在 Builder 的构造方法中强制让调用者提供必填字段,还有,在 build() 方法中校验各个参数比在 User 的构造方法中校验,代码要优雅一些。

题外话,强烈建议读者使用 lombok,用了 lombok 以后,上面的一大堆代码会变成如下这样:

  1. @Builder
  2. class User {
  3. private String name;
  4. private String password;
  5. private String nickName;
  6. private int age;
  7. }

当然,如果你只是想要链式写法,不想要建造者模式,有个很简单的办法,User 的 getter 方法不变,所有的 setter 方法都让其 return this 就可以了,然后就可以像下面这样调用:

  1. User user = new User().setName("").setPassword("").setAge(20);

很多人是这么用的,但是笔者觉得其实这种写法非常地不优雅,不是很推荐使用。

2. 类图(Class Diagram)

image.png

3. 实现(Implementation)

以下是一个简易的 StringBuilder 实现,参考了 JDK 1.8 源码。

  1. public class AbstractStringBuilder {
  2. protected char[] value;
  3. protected int count;
  4. public AbstractStringBuilder(int capacity) {
  5. count = 0;
  6. value = new char[capacity];
  7. }
  8. public AbstractStringBuilder append(char c) {
  9. ensureCapacityInternal(count + 1);
  10. value[count++] = c;
  11. return this;
  12. }
  13. private void ensureCapacityInternal(int minimumCapacity) {
  14. // overflow-conscious code
  15. if (minimumCapacity - value.length > 0)
  16. expandCapacity(minimumCapacity);
  17. }
  18. void expandCapacity(int minimumCapacity) {
  19. int newCapacity = value.length * 2 + 2;
  20. if (newCapacity - minimumCapacity < 0)
  21. newCapacity = minimumCapacity;
  22. if (newCapacity < 0) {
  23. if (minimumCapacity < 0) // overflow
  24. throw new OutOfMemoryError();
  25. newCapacity = Integer.MAX_VALUE;
  26. }
  27. value = Arrays.copyOf(value, newCapacity);
  28. }
  29. }
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

4. JDK