建造者模式的目的是为了分离对象的属性与创建过程。用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。

建造者模式会让开发者指定一些比较重要的属性或者让开发者指定某几个对象类型,然后让建造者去实现复杂的构建对象的过程,这就是对象的属性与创建分离。这样对于开发者而言隐藏了复杂的对象构建细节,降低了学习成本,同时提升了代码的可复用性。

核心是通过一个类“Builder”来实现的。

  1. class Food {
  2. String name;
  3. int price;
  4. float weight;
  5. // 可选的,也可以暴露出来让外部的调用者去通过new的方式创建对象
  6. private Food() {}
  7. private Food(Builder builder) {
  8. this.name = build.name;
  9. this.price = build.price;
  10. this.weight = build.weight;
  11. }
  12. public static class Builder {
  13. String name;
  14. int price;
  15. float weight;
  16. public Builder(int price) {
  17. this.price = price;
  18. }
  19. public Builder setName(String name) {
  20. this.name = name;
  21. return this;
  22. }
  23. public Builder setWeight(float weight) {
  24. this.weight = weight;
  25. return this;
  26. }
  27. public Food build() {
  28. return new Food(this);
  29. }
  30. }
  31. }

工厂模式和建造者模式的区别

工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。

顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨。

应用场景

如果一个类中有很多属性,为了避免构造函数的参数列表过长,影响代码的可读性和易用性,我们可以通过构造函数配合 set() 方法来解决。但是,如果存在下面情况中的任意一种,我们就要考虑使用建造者模式了。

  • 我们把类的必填属性放到构造函数中,强制创建对象的时候就设置。如果必填的属性有很多,把这些必填属性都放到构造函数中设置,那构造函数就又会出现参数列表很长的问题。如果我们把必填属性通过 set() 方法设置,那校验这些必填属性是否已经填写的逻辑就无处安放了。
  • 如果类的属性之间有一定的依赖关系或者约束条件,我们继续使用构造函数配合 set() 方法的设计思路,那这些依赖关系或约束条件的校验逻辑就无处安放了。
  • 如果我们希望创建不可变对象,也就是说,对象在创建好之后,就不能再修改内部的属性值,要实现这个功能,我们就不能在类中暴露 set() 方法。构造函数配合 set() 方法来设置属性值的方式就不适用了。

大家知道一辆车是很复杂的,有发动机、变速器、轮胎、挡风玻璃、雨刮器、气缸、方向盘等等无数的部件。

用户买车的时候不可能一个一个去指定我要那种类型的变速器、我要一个多大的轮胎、我需要长宽高多少的车,这是不现实的

通常用户只会和销售谈我需要什么什么样的类型的车,马力要不要强劲、空间是否宽敞,这样销售就会根据用户的需要去推荐一款具体的车

这就是一个典型建造者的场景:车是复杂对象,销售是建造者。我告诉建造者我需要什么,建造者根据我的需求给我一个具体的对象

优缺点

优点:

  • 客户端不比知道产品内部细节,将产品本身与产品创建过程解耦,使得相同的创建过程可以创建不同的产品对象
  • 可以更加精细地控制产品的创建过程,将复杂对象分门别类抽出不同的类别来,使得开发者可以更加方便地得到想要的产品

缺点:

  • 产品属性之间差异很大且属性没有默认值可以指定,这种情况是没法使用建造者模式的,我们可以试想,一个对象20个属性,彼此之间毫无关联且每个都需要手动指定,那么很显然,即使使用了建造者模式也是毫无作用