22种设计模式——建造者模式

1. 概述

生成器模式是一种创建型设计模式, 使你能够分步骤创建复杂对象。 该模式允许你使用相同的创建代码生成不同类型和形式的对象。

在这里插入图片描述
在我看来,五种创建型模式,可以分为三种:原型模式、单例模式、其他三种创建型模式。这样分是因为原型和单例的特点和适用场景是很明显的,而其他三种创建型模式的特点和适用场景则很相似。

其他三种创建型模式,工厂模式、抽象工厂模式、建造者模式,工厂模式和抽象工厂自然不必多说,抽象工厂是工厂模式的变种,这里可以看我之前的博客。那工厂模式和建造者模式有什么区别呢?

  • 相同点:两者都是用于构建复杂对象实例的设计模式

  • 不同点:工厂模式不关心细节,它造出来的对象是一个完整的对象,它对于该对象的细节并不是很关心;建造者则恰恰相反,它很关心细节,它关心对象是怎么一步一步构建的。

    工厂方法模式注重的是整体对象的创建方法,而建造者模式注重的是部件构建的过程,旨在通过一步一步地精确构造创建出一个复杂的对象。我们举个简单例子来说明两者的差异,如要制造一个超人,如果使用工厂方法模式,直接产生出来的就是一个力大无穷、能够飞翔、内裤外穿的超人;而如果使用建造者模式,则需要组装手、头、脚、躯干等部分,然后再把内裤外穿,才能创造一个超人。

下面将来细细讲下建造者模式,还是从问题引入。例如, 我们来思考如何创建一个 房屋House对象。 建造一栋简单的房屋, 首先你需要建造四面墙和地板, 安装房门和一套窗户, 然后再建造一个屋顶。 但是如果你想要一栋更宽敞更明亮的房屋, 还要有院子和其他设施 (例如暖气、 排水和供电设备), 那又该怎么办呢?两种解决方法:

  • 最简单的方法是扩展房屋基类, 然后创建一系列涵盖所有参数组合的子类。每多出一个需求的房子,建一个新的子类,但最终你将面对相当数量的子类。 任何新增的参数 (例如门廊类型) 都会让这个层次结构更加复杂。
    在这里插入图片描述

  • 另一种方法则无需生成子类。 你可以在房屋基类中创建一个包括所有可能参数的超级构造函数, 并用它来控制房屋对象。 这种方法确实可以避免生成子类, 但它却会造成另外一个问题。

    通常情况下, 绝大部分的参数都没有使用,这些参数也不是每次都要全部用上的。 这使得对于构造函数的调用十分不简洁。 例如, 只有很少的房子有游泳池, 因此与游泳池相关的参数十之八九是毫无用处的。

在这里插入图片描述

综上,这两种解决方法都不能解决问题,那怎么解决呢?就是下文的建造者模式了。这里再提一嘴工厂模式,上面讲它的区别时可能有点抽象。

工厂模式关心的种类的差别比较大,建造者模式关心的种类的差别比较小,对于当前建房子的场景,使用工厂模式要解决的就是建普通房子和建别墅的问题,使用建造者模式要解决的就是建一扇门两扇窗带游泳池的平房和建4扇门不带窗没游泳池平房的问题。

为什么说工厂模式不关心细节呢?因为在他眼里只有别墅和平房两种,平房有什么细节他不管,它才不管平房要多少门多少窗带不带游泳池,它构建出来的对象都是一样的,也就是说工厂模式建好后的平房都是一样的。讲到这里大家应该就明白了。

总结时间到:

  • 建造者模式其实也是工厂模式的变种(我看网上好像没有这种说法,这个是我的个人理解),它关心粒度更小的场景。
  • 以工厂模式为基础,抽象工厂模式为了解决更高维度的扩展,建造者模式为了解决更小粒度的扩展。
  • 工厂模式,关注的是创建的产品,即创建什么类型的产品;建造者模式,关注的是产品的创建过程,即怎么创建。我们说 Builder 模式是将对象的构建与表示相分离。或许我们可以这样理解:Builder 模式侧重的是构建,而工厂模式侧重的是表示。

2. 特点

  • 优点

    1. 可以分步创建对象, 暂缓创建步骤或递归运行创建步骤。
    2. 生成不同形式的产品时, 你可以复用相同的制造代码。
    3. 单一职责原则。 你可以将复杂构造代码从产品的业务逻辑中分离出来。
  • 缺点

    1. 由于该模式需要新增多个类, 因此代码整体复杂程度会有所增加
  • 使用场景

    1. 使用生成器模式可避免 “重叠构造函数 (telescopic constructor)” 的出现,一般来说超过4个参数且有可选参数时就可以考虑使用建造者模式

      假设你的构造函数中有十个可选参数, 那么调用该函数会非常不方便; 因此, 你需要重载这个构造函数, 新建几个只有较少参数的简化版。 但这些构造函数仍需调用主构造函数, 传递一些默认数值来替代省略掉的参数。

      在这里插入图片描述
      生成器模式让你可以分步骤生成对象, 而且允许你仅使用必须的步骤。 应用该模式后, 你再也不需要将几十个参数塞进构造函数里了。

    2. 当你希望使用代码创建不同形式的产品 (例如石头或木头房屋) 时, 可使用生成器模式。

      如果你需要创建的各种形式的产品, 它们的制造过程相似且仅有细节上的差异, 此时可使用生成器模式。

      基本生成器接口中定义了所有可能的制造步骤, 具体生成器将实现这些步骤来制造特定形式的产品。 同时, 主管类将负责管理制造步骤的顺序。

    3. 使用生成器构造组合树或其他复杂对象。

      生成器模式让你能分步骤构造产品。 你可以延迟执行某些步骤而不会影响最终产品。 你甚至可以递归调用这些步骤, 这在创建对象树时非常方便。

      生成器在执行制造步骤时, 不能对外发布未完成的产品。 这可以避免客户端代码获取到不完整结果对象的情况。


3. Java实现

  • UML类图
    在这里插入图片描述

  • 角色说明

    • 产品:这里就是类图中的CommonHouseSpecialHouse,对应一个具体产品对象
    • 客户端:就是客户端Client,调用指挥者Director创建对象
    • 抽象建造者:这个建造者就是创建对象的类,定义了创建产品的功能,它是一种规范,所以它可以是接口或者抽象类,对应类图中的Builder
    • 具体建造者:实际上创建产品的类,继承了抽象建造者,有多个不同的具体建造者,生产不同的对象,对应了图中的两个类CommonHouseBuilderSpecialHouseBuilder
    • 指挥者:对应图中的Director,顾名思义,用于指挥具体建造者建造对象,这个可能比较难理解,看代码!!!
  • Java代码

    1. 产品对象

      /**
       * @Author: chy
       * @Description: 产品类:普通平房,没有特殊操作
       * @Date: Create in 14:05 2021/3/5
       */
      public class CommonHouse {
          // 门,必选参数
          private String door;
          // 窗,必选参数
          private String window;
          // 游泳池,非必选参数
          private String pool;
      
          public void setDoor(String door) {
              this.door = door;
          }
      
          public void setWindow(String window) {
              this.window = window;
          }
      
          public void setPool(String pool) {
              this.pool = pool;
          }
      
          @Override
          public String toString() {
              return "CommonHouse{" +
                      "door='" + door + '\'' +
                      ", window='" + window + '\'' +
                      ", pool='" + pool + '\'' +
                      '}';
          }
      }
      

      下面这个也是产品对象,但是它多了一个方法fun()。用以区分两个不同的产品(这里的区别可能有点小,大家意会即可)

      /**
       * @Author: chy
       * @Description: 产品类:特色房子,有特殊功能
       * @Date: Create in 14:19 2021/3/5
       */
      public class SpecialHouse {
          // 门,必选参数
          private String door;
          // 窗,必选参数
          private String window;
          // 游泳池,非必选参数
          private String pool;
      
          public void setDoor(String door) {
              this.door = door;
          }
      
          public void setWindow(String window) {
              this.window = window;
          }
      
          public void setPool(String pool) {
              this.pool = pool;
          }
      
          @Override
          public String toString() {
              return "CommonHouse{" +
                      "door='" + door + '\'' +
                      ", window='" + window + '\'' +
                      ", pool='" + pool + '\'' +
                      '}';
          }
      
          void fun(){
              System.out.println("特殊功能~~~");
          }
      }
      
    2. 抽象建造者

      /**
       * @Author: chy
       * @Description: 抽象建造者
       * @Date: Create in 14:08 2021/3/5
       */
      public abstract class Builder {
          protected abstract Builder setDoor(String door);
          protected abstract Builder setWindow(String window);
          protected abstract Builder setPool(String pool);
          protected abstract Object getProduct();
      }
      
    3. 具体建造者

      不同的产品有不同的具体建造者,需要把它们内置到它们对应的具体建造者里面。

      /**
       * @Author: chy
       * @Description: 具体建造者:普通平房建造者,生产普通平房
       * @Date: Create in 14:09 2021/3/5
       */
      public class CommonHouseBuilder extends Builder{
          // 内置产品对象
          private CommonHouse commonHouse;
      
          public CommonHouseBuilder() {
              this.commonHouse = new CommonHouse();
          }
      
          @Override
          protected Builder setDoor(String door) {
              commonHouse.setDoor(door);
              return this;
          }
      
          @Override
          protected Builder setWindow(String window) {
              commonHouse.setWindow(window);
              return this;
          }
      
          @Override
          protected Builder setPool(String pool) {
              commonHouse.setPool(pool);
              return this;
          }
      
          @Override
          protected CommonHouse getProduct() {
              return commonHouse;
          }
      }
      
      /**
       * @Author: chy
       * @Description: 具体建造者:特殊房子建造者,生产特殊房子
       * @Date: Create in 14:21 2021/3/5
       */
      public class SpecialHouseBuilder extends Builder{
          // 内置产品对象
          private SpecialHouse specialHouse;
      
          public SpecialHouseBuilder(){
              specialHouse = new SpecialHouse();
          }
      
          @Override
          protected Builder setDoor(String door) {
              specialHouse.setDoor(door);
              return this;
          }
      
          @Override
          protected Builder setWindow(String window) {
              specialHouse.setWindow(window);
              return this;
          }
      
          @Override
          protected Builder setPool(String pool) {
              specialHouse.setPool(pool);
              return this;
          }
      
          @Override
          protected SpecialHouse getProduct() {
              return specialHouse;
          }
      }
      
    4. 指挥者

      指挥者内置了抽象建造者,我们构造指挥者的时候,只需要把具体建造者传给他就OK,指挥者里面也写了一系列建造的方法。

      /**
       * @Author: chy
       * @Description: 指挥者
       * @Date: Create in 14:13 2021/3/5
       */
      public class Director {
          private Builder builder;
      
          public Director(Builder builder) {
              this.builder = builder;
          }
      
          /**
           * 建一个4个窗2个门0游泳池的房子
           */
          void buildHouse_2D_4W_0P(){
              builder.setDoor("两个门").setWindow("四个窗").setPool("没有游泳池");
          }
      
          /**
           * 建一个2个窗2个门1游泳池的房子
           */
          void buildHouse_2D_2W_1P(){
              builder.setDoor("两个门").setWindow("两个个窗").setPool("一个游泳池");
          }
      }
      
    5. 客户端

      /**
       * @Author: chy
       * @Description: 客户端
       * @Date: Create in 16:30 2021/3/4
       */
      public class Client {
          public static void main(String[] args) {
              // 建平房
              CommonHouseBuilder commonHouseBuilder = new CommonHouseBuilder();
              Director director = new Director(commonHouseBuilder);
              director.buildHouse_2D_4W_0P();
              CommonHouse commonHouse = commonHouseBuilder.getProduct();
              System.out.println(commonHouse);
      
              System.out.println("====================");
      
              // 建特殊房子
              SpecialHouseBuilder specialHouseBuilder = new SpecialHouseBuilder();
              Director director1 = new Director(specialHouseBuilder);
              director1.buildHouse_2D_2W_1P();
              SpecialHouse specialHouse = specialHouseBuilder.getProduct();
              specialHouse.fun();
              System.out.println(specialHouse);
          }
      }
      
  • 说明

    如上面的代码,我们在客户端中只要新建一个Director(),然后传入一个具体建造者,调用指挥方法就可以获得我们所要的对象。

    用一个例子来比喻,产品类就相当于产品,具体建造者相当于工人,指挥者相当于包工头,客户端相当于甲方。所以逻辑就是:甲方提出需求(客户端调用Director)—> 包工头指挥具体工人工作(Director指挥Builder) --> 工人建房子(具体建造者构建对象)。

    回到最上面的建房子的问题,建造者能怎么解决?和其他两种方式又有什么不一样?

    • 对比第一种,第一种我们可以很清楚的看出来类爆炸的问题,在建造者模式中,要扩展一个对象又该怎么做呢?其实只需要在指挥者中写一个指挥方法即可。当然,如果你要新建一种新的产品,还是需要写一个新的具体建造者的。
    • 对比第二种,由于产品类具有可选参数的存在,所以构造函数难以书写,建造者模式也解决了这个问题,使用建造者模式创建实例化对象的时候,无需使用构造器,也就不存在这个问题,其实建造者模式构建对象更像是JavaBean的构建方法,即使用get和set来设置属性,但是它把这一步转移到了指挥者中,这样做即减少了耦合,也允许在设置属性的同时做出一些别的操作,不仅仅只是单纯的set和get(比如在指挥方法中我们可以随便做操作)

其实还存在最后一个问题,就是我们可能构建的对象的属性都是随时变化的,那使用建造者模式又必须让Director去调用具体建造者,这样就每次都要在Director新建一个新的方法,这样谁顶得住,那我们换个角度,能不能把客户端和指挥者两个角色融合,这样就可以直接在客户端中构建对象。

答案是可以,其实我上面的代码并非真正的建造者模式,而是对他做了一个修改,这样我们既可以使用Director调用具体建造者构造对象,也可以在客户端中直接构建对象。

/**
 * @Author: chy
 * @Description: 客户端
 * @Date: Create in 16:30 2021/3/4
 */
public class Client {
    public static void main(String[] args) {
        // 使用director
        SpecialHouseBuilder specialHouseBuilder = new SpecialHouseBuilder();
        Director director1 = new Director(specialHouseBuilder);
        director1.buildHouse_2D_2W_1P();
        SpecialHouse specialHouse = specialHouseBuilder.getProduct();
        specialHouse.fun();
        System.out.println(specialHouse);

        System.out.println("====================");

        // 客户端变成director
        SpecialHouse product = (SpecialHouse)specialHouseBuilder.setDoor("三个门")
                                                                .setPool("一个游泳池")
                                                                .setWindow("不要窗")
                                                                .getProduct();
        System.out.println(product);
    }
}

理解:可以把Director里的指挥方法看成是一个模板,如果要大量创建相同的对象,就可以直接使用Director里的方法;如果是要构建大量不同的对象,就可以直接在客户端中构建对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值