前言

建造者模式,又称 Builder 模式,是一个比较常用的创建型设计模式。建造者模式的原理和代码实现非常简单,掌握起来并不难,难点在于应用场景。比如,有没有考虑过这样的问题:直接使用构造函数或者配合 set 方法就能创建对象,为什么还需要建造者模式来创建呢?建造者模式和工厂模式都可以创建对象,那它们两个的区别在哪?

为什么需要建造者模式

在平常的开发中,创建一个对象最常用的方式,就是使用 new 关键字调用类的构造函数来完成,再者,会配合 set 函数给成员变量赋值。但是这样会有以下的一些问题:

  • 对于创建对象中的一些配置项是必填项,我们把它们放到构造函数中,强制创建对象的时候就设置。如果必填项有很多的话,放到构造函数中设置,那么构造函数的参数列表就会很长,代码的可读性和易错性都会变差,也容易搞错各参数的顺序。如果放到 set 函数中设置,校验这些必填项的逻辑就无处安放。
  • 对于配置项之间有一定依赖关系的,如设置了某一个配置项,那么就必须要把跟它有依赖关系或约束关系的配置项也显式地设置。那这些配置项之间的依赖关系或约束关系的校验逻辑就无处安放了。
  • 对于创建不可变对象,就是在对象创建好之后,就不能修改内部的属性值这种情况,就不能使用 set 方法了。
  • 对于一些必须要设置某几个配置项,才能起作用的对象,在通过 set 方法设置完这几个配置项前,对象实例会处于一种不可用的状态。

而 Builder 模式能很好的解决上面的问题,对于配置项校验的问题,我们可以把逻辑都放到 build() 函数中处理。
代码实例:

  1. public class ResourcePoolConfig {
  2. private String name;
  3. private int maxTotal;
  4. private int maxIdle;
  5. private int minIdle;
  6. private ResourcePoolConfig(Builder builder) {
  7. this.name = builder.name;
  8. this.maxTotal = builder.maxTotal;
  9. this.maxIdle = builder.maxIdle;
  10. this.minIdle = builder.minIdle;
  11. }
  12. //...省略getter方法...
  13. //我们将Builder类设计成了ResourcePoolConfig的内部类。
  14. //我们也可以将Builder类设计成独立的非内部类ResourcePoolConfigBuilder。
  15. public static class Builder {
  16. private static final int DEFAULT_MAX_TOTAL = 8;
  17. private static final int DEFAULT_MAX_IDLE = 8;
  18. private static final int DEFAULT_MIN_IDLE = 0;
  19. private String name;
  20. private int maxTotal = DEFAULT_MAX_TOTAL;
  21. private int maxIdle = DEFAULT_MAX_IDLE;
  22. private int minIdle = DEFAULT_MIN_IDLE;
  23. public ResourcePoolConfig build() {
  24. // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
  25. if (StringUtils.isBlank(name)) {
  26. throw new IllegalArgumentException("...");
  27. }
  28. if (maxIdle > maxTotal) {
  29. throw new IllegalArgumentException("...");
  30. }
  31. if (minIdle > maxTotal || minIdle > maxIdle) {
  32. throw new IllegalArgumentException("...");
  33. }
  34. return new ResourcePoolConfig(this);
  35. }
  36. public Builder setName(String name) {
  37. if (StringUtils.isBlank(name)) {
  38. throw new IllegalArgumentException("...");
  39. }
  40. this.name = name;
  41. return this;
  42. }
  43. public Builder setMaxTotal(int maxTotal) {
  44. if (maxTotal <= 0) {
  45. throw new IllegalArgumentException("...");
  46. }
  47. this.maxTotal = maxTotal;
  48. return this;
  49. }
  50. public Builder setMaxIdle(int maxIdle) {
  51. if (maxIdle < 0) {
  52. throw new IllegalArgumentException("...");
  53. }
  54. this.maxIdle = maxIdle;
  55. return this;
  56. }
  57. public Builder setMinIdle(int minIdle) {
  58. if (minIdle < 0) {
  59. throw new IllegalArgumentException("...");
  60. }
  61. this.minIdle = minIdle;
  62. return this;
  63. }
  64. }
  65. }
  66. // 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdle
  67. ResourcePoolConfig config = new ResourcePoolConfig.Builder()
  68. .setName("dbconnectionpool")
  69. .setMaxTotal(16)
  70. .setMaxIdle(10)
  71. .setMinIdle(12)
  72. .build()

与工厂模式有何区别

用一个网上经典的例子解释:
顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比 如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起 司,我们通过建造者模式根据用户选择的不同配料来制作披萨。
工厂模式是用来 创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定 创建哪种类型的对象。建造者模式是用来创建一种类型的复杂对象,可以通过设置不同的可 选参数,“定制化”地创建不同的对象。
实际上,我们也不要太学院派,非得把工厂模式、建造者模式分得那么清楚,我们需要知道 的是,每个模式为什么这么设计,能解决什么问题。只有了解了这些最本质的东西,我们才 能不生搬硬套,才能灵活应用,甚至可以混用各种模式创造出新的模式,来解决特定场景的 问题。