目录

  • 什么是建造者模式
  • 为什么要使用建造者模式
  • 构造函数创建对象
  • set方式构建对象
  • java实现建造者模式
    • 第一种实现方式
    • 第二种方式
  • 建造者模式与构造函数的对比
  • 建造者模式与工厂模式的对比
  • 总结

什么是建造者模式

建造者模式是设计模式的一种,将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

其实建造者模式是被翻译过来的,他原名叫builder模式,也被称为生成器模式,这种模式的实现非常的简单,只是在使用方面可能会有点摸不着方向,它主要解决复杂的对象创建,比如参数过长、校验过多等等。

为什么要使用建造者模式

我们都知道,创建对象的方法有很多,new是我们最常见也是最熟悉的一种,我们为什么不使用我们最熟悉的而选用建造者模式呢?虽然new是我们最熟悉的,但不一定是最合适的,为什么这么说呢?我们举个例子来说明一下。

我们现在定义一个对象:ThreadConfig,ThreadConfig有5个属性:核心线程数(corePoolSize)、最大线程数(maxPoolSize)、队列数(queueCapacity)、空闲时间退出(keepAliveTime)、是否允许线程退出(allowCoreThreadTimeout)。属性有必填、有选填。

属性名 必填 默认值 注释
threadName 线程名
corePoolSize 4 核心线程数
maxPoolSize 核心线程数
queueCapacity 最大线程数
keepAliveTime 当线程空闲时间达到keepAliveTime,该线程会退出
allowCoreThreadTimeout 是否允许核心线程数空闲时退出

创建对象的时候要满足以下要求:
1.最大线程数不传,默认为核心线程数的大小。
2.最大线程数不能小于核心线程数。
3.如果填写队列数,队列书不能小于等于0。
4.如果填写keepAliveTime,不能小于等于0。

看到这样的一个对象,如果是你,你会怎么设计他的对象创建呢?

构造函数创建对象

大家想到的第一种创建方式可能就是构造函数,那我们先使用构造函数实现一下这个对象的创建

package com.ymy.builder;import lombok.ToString;
import org.springframework.util.StringUtils;@ToString
public class ThreadConfig {/*** 核心线程默认值*/private static final   Integer CORE_POOL_SIZE = 4;private String threadName;/*** 核心线程数*/private Integer corePoolSize = CORE_POOL_SIZE;/*** 最大线程数*/private Integer maxPoolSize;/*** 队列数*/private Integer queueCapacity;/*** 当线程空闲时间达到keepAliveTime,该线程会退出*/private Integer keepAliveTime;/*** 是否允许核心线程数空闲时退出*/private boolean allowCoreThreadTimeout;public ThreadConfig(String threadName,Integer corePoolSize,Integer maxPoolSize,Integer queueCapacity,Integer keepAliveTime) throws IllegalAccessException {if(StringUtils.isEmpty(threadName)){throw  new IllegalAccessException("线程名不能为空!");}this.threadName = threadName;if(null != corePoolSize ){if( corePoolSize <= 0){throw  new IllegalAccessException("核心线程数不能小于等于0!");}this.corePoolSize = corePoolSize;}if(null != maxPoolSize  ){if(maxPoolSize < this.corePoolSize){throw  new IllegalAccessException("最大线程数不能小于核心线程数!");}this.maxPoolSize = corePoolSize;}if(null != queueCapacity ){if( queueCapacity <= 0 ){throw  new IllegalAccessException("队列书不能小于等于0!");}this.queueCapacity = queueCapacity;}if(null != keepAliveTime  ){if( keepAliveTime <= 0 ){throw  new IllegalAccessException("空闲时间不能小于等于0!");}this.keepAliveTime = keepAliveTime;}}
}

@ToString注解是lombok依赖提供的

<dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency>

创建对象

package com.ymy.builder;public class Test {public static void main(String[] args) throws IllegalAccessException {ThreadConfig config = new ThreadConfig("thread-1",5,2,5,10);System.out.println(config);}
}

输出结果:

Exception in thread "main" java.lang.IllegalAccessException: 最大线程数不能小于核心线程数!at com.ymy.builder.ThreadConfig.<init>(ThreadConfig.java:56)at com.ymy.builder.Test.main(Test.java:6)

我这里给出的核心线程数:5,但是最大线程数给的2,所以会抛出最大线程数不能小于核心线程数!这种方式看着很完美,但有一点很不友好,当参数过多的时候容易出错,为什么这么说呢?你仔细看这行代码

ThreadConfig config = new ThreadConfig("thread-1",5,2,5,10);

除了第一个参数,其他参数都是int类型,看着好像没啥大毛病,这是因为我的参数还不够多,如果我这里有10个参数需要传递,并且都是int类型,这时候就会存在一个问题,参数很可能会被写错,比如最大线程数写到了队列数中,而且有时候还不会报错,只有在项目运行的时候才会出现某种让人摸不着头脑的bug,所以使用构造函数创建对象的时候不太适合参数过长,不但容易出错,而且让接手代码的人也头痛,代码可读性比较差,当然当参数只有一两个的时候,构造函数的创建方式还是很不错的。

set方式构建对象

既然构造函数会导致参数错误以及可读性较差,那我们能不能使用构造函数+set方法来创建对象呢?我们可以尝试一下,由于只有线程名是必传,所以构造函数只给定线程名,其他属性都通过set赋值,改造一下代码。

package com.ymy.builder;import lombok.ToString;
import org.springframework.util.StringUtils;@ToString
public class ThreadConfig {/*** 核心线程默认值*/private static final   Integer CORE_POOL_SIZE = 4;private String threadName;/*** 核心线程数*/private Integer corePoolSize = CORE_POOL_SIZE;/*** 最大线程数*/private Integer maxPoolSize;/*** 队列数*/private Integer queueCapacity;/*** 当线程空闲时间达到keepAliveTime,该线程会退出*/private Integer keepAliveTime;/*** 是否允许核心线程数空闲时退出*/private boolean allowCoreThreadTimeout;public ThreadConfig(String threadName) throws IllegalAccessException {if(StringUtils.isEmpty(threadName)){throw  new IllegalAccessException("线程名不能为空!");}this.threadName = threadName;}public void setCorePoolSize(Integer corePoolSize) throws IllegalAccessException {if(null != corePoolSize ){if( corePoolSize <= 0){throw  new IllegalAccessException("核心线程数不能小于等于0!");}this.corePoolSize = corePoolSize;}}public void setMaxPoolSize(Integer maxPoolSize) throws IllegalAccessException {if(null != maxPoolSize  ){if(maxPoolSize < this.corePoolSize){throw  new IllegalAccessException("最大线程数不能小于核心线程数!");}this.maxPoolSize = corePoolSize;}}public void setQueueCapacity(Integer queueCapacity) throws IllegalAccessException {if(null != queueCapacity ){if( queueCapacity <= 0 ){throw  new IllegalAccessException("队列书不能小于等于0!");}this.queueCapacity = queueCapacity;}}public void setKeepAliveTime(Integer keepAliveTime) throws IllegalAccessException {if(null != keepAliveTime  ){if( keepAliveTime <= 0 ){throw  new IllegalAccessException("空闲时间不能小于等于0!");}this.keepAliveTime = keepAliveTime;}}public void setAllowCoreThreadTimeout(boolean allowCoreThreadTimeout) {this.allowCoreThreadTimeout = allowCoreThreadTimeout;}
}

改造完ThreadConfig之后我们创建对象的方式也会发生细微的变化,由之前构造函数传递一堆参数变成了一个参数,加上了set方法,初始值由set给定。

package com.ymy.builder;public class Test {public static void main(String[] args) throws IllegalAccessException {ThreadConfig config = new ThreadConfig("thread-1");config.setCorePoolSize(5);config.setMaxPoolSize(10);config.setQueueCapacity(2);config.setKeepAliveTime(100);config.setAllowCoreThreadTimeout(false);System.out.println(config);}
}

这种对象的创建方式可以有效的防止赋值属性错乱的问题,因为看上去一目了然,基本上不会出错,代码可读性也很强,完美的解决了将所有参数都放在构造函数的缺陷,那为什么还会出现建造者模式呢?可以仔细想一下,set方法这么完美,建造者模式还有必要吗?我觉得建造者模式的出现并不是偶然。

我们现在稍微修改一下需求:当corePoolSize(核心线程数)被赋值的时候,最大线程数也必须要赋值,这个时候你觉得set方法还能满足吗?我觉得应该是满足不了了吧,这是一种情况,还有一种情况set也是满足不了的,那就是我希望对象初始化的时候一次性将所有的属性都赋值,之后将不能被修改,这一点也是set做不到的,set方法就是提供给调用者的,所以调用者可以通过set随时修改ThreadConfig的属性,如果处理不当,可能会造成某种安全隐患,这个时候你可能又想到,把corePoolSize、maxPoolSize也放到构造函数中不就解决了corePoolSize赋值的时候maxPoolSize也一定要赋值的要求吗,确实是能解决这个问题,如果像这样的参数很多呢?然后又有可能出现参数传错导致诡异bug,所这时候建造者模式就闪亮登场了。

java实现建造者模式

第一种实现方式

既然构造函数和set方法无法满足我们的需求,那自然会有满足我们需求的新技术出现,按照之前的需求,线程名必填、corePoolSize(核心线程数)被赋值的时候,最大线程数也必须要赋值,我们一起使用建造模式来实现一下这个对象的创建。

package com.ymy.builder;import lombok.ToString;
import org.springframework.util.StringUtils;@ToString
public class ThreadConfig {/*** 核心线程默认值*/private static final   Integer CORE_POOL_SIZE = 4;/*** 线程名*/private String threadName;/*** 核心线程数*/private Integer corePoolSize = CORE_POOL_SIZE;/*** 最大线程数*/private Integer maxPoolSize;/*** 队列数*/private Integer queueCapacity;/*** 当线程空闲时间达到keepAliveTime,该线程会退出*/private Integer keepAliveTime;/*** 是否允许核心线程数空闲时退出*/private boolean allowCoreThreadTimeout;private ThreadConfig(ThreadConfig.Builder builder) {this.threadName = builder.threadName;if(null != builder.corePoolSize){this.corePoolSize = builder.corePoolSize;}this.maxPoolSize = builder.maxPoolSize;this.queueCapacity = builder.queueCapacity;this.keepAliveTime = builder.keepAliveTime;this.allowCoreThreadTimeout = builder.allowCoreThreadTimeout;}public static class Builder {/*** 线程名*/private String threadName;/*** 核心线程数*/private Integer corePoolSize ;/*** 最大线程数*/private Integer maxPoolSize;/*** 队列数*/private Integer queueCapacity;/*** 当线程空闲时间达到keepAliveTime,该线程会退出*/private Integer keepAliveTime;/*** 是否允许核心线程数空闲时退出*/private boolean allowCoreThreadTimeout;public ThreadConfig build() throws IllegalAccessException { // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等if (StringUtils.isEmpty(threadName)) {throw new IllegalAccessException("线程名不能为空");}if(corePoolSize != null && maxPoolSize == null){throw new IllegalAccessException("最大线程数必传");}return new ThreadConfig(this);}public ThreadConfig.Builder corePoolSize(int corePoolSize) {if (corePoolSize <= 0) {throw new IllegalArgumentException("核心线程数不能小于等于0");}this.corePoolSize = corePoolSize;return this;}public ThreadConfig.Builder threadName(String threadName) {this.threadName = threadName;return this;}public ThreadConfig.Builder maxPoolSize(int maxPoolSize) {if (maxPoolSize < this.corePoolSize) {throw new IllegalArgumentException("最大线程数不能小于核心线程数");}this.maxPoolSize = maxPoolSize;return this;}public ThreadConfig.Builder queueCapacity(int queueCapacity) {if (queueCapacity <= 0) {throw new IllegalArgumentException("队列不能小于等于0");}this.queueCapacity = queueCapacity;return this;}public ThreadConfig.Builder keepAliveTime(int keepAliveTime) {if (keepAliveTime <= 0) {throw new IllegalArgumentException("保持空闲线程可用的时间不能小于等于0");}this.keepAliveTime = keepAliveTime;return this;}public ThreadConfig.Builder allowCoreThreadTimeout(boolean allowCoreThreadTimeout) {this.allowCoreThreadTimeout = allowCoreThreadTimeout;return this;}}}

我们来测试,传入核心线程数不传最先线程数

package com.ymy.builder;public class Test {public static void main(String[] args) throws IllegalAccessException {ThreadConfig config = new ThreadConfig.Builder().threadName("hello").corePoolSize(3).keepAliveTime(100).queueCapacity(2).allowCoreThreadTimeout(true).build();System.out.println(config);}
}

打印结果

Exception in thread "main" java.lang.IllegalAccessException: 最大线程数必传at com.ymy.builder.ThreadConfig$Builder.build(ThreadConfig.java:89)at com.ymy.builder.Test.main(Test.java:12)

核心线程数不传

 ThreadConfig config = new ThreadConfig.Builder().threadName("hello")//.corePoolSize(3).keepAliveTime(100).queueCapacity(2).allowCoreThreadTimeout(true).build();System.out.println(config);

结果

ThreadConfig(threadName=hello, corePoolSize=4, maxPoolSize=null, queueCapacity=2, keepAliveTime=100, allowCoreThreadTimeout=true)Process finished with exit code 0

这就说明已经达到了我们的预期效果,并且赋值清晰,不容易出错,代码的可读性也比较高,但是也有一点是不足的,那就是ThreadConfig类中会出现冗余的数据Builder。

建造者模式的参数校验放在了build()方法中,这样做法的好处在于build是集中的处理参数问题,只有校验通过之后才会给ThreadConfig对象实例化,为了对象的安全性,我们可以将ThreadConfig的构造函数设置成private,同时取消set方法,强制使用builder方式创建对象,这样就大大的保证了对象的安全性。

第二种方式

上面那种方式看着是不是很爽,但是创建对象的时候还是需要new ThreadConfig.Builder(),我现在想直接ThreadConfig.Builder()就能创建对象,我不想看到new,能不能实现呢?相信java,他能,我们一起来改造一下代码

package com.ymy.builder;import lombok.ToString;
import org.springframework.util.StringUtils;@ToString
public class ThreadConfig {/*** 核心线程默认值*/private static final   Integer CORE_POOL_SIZE = 4;/*** 线程名*/private String threadName;/*** 核心线程数*/private Integer corePoolSize = CORE_POOL_SIZE;/*** 最大线程数*/private Integer maxPoolSize;/*** 队列数*/private Integer queueCapacity;/*** 当线程空闲时间达到keepAliveTime,该线程会退出*/private Integer keepAliveTime;/*** 是否允许核心线程数空闲时退出*/private boolean allowCoreThreadTimeout;private ThreadConfig(ThreadConfig.Builder builder) {this.threadName = builder.threadName;if(null != builder.corePoolSize){this.corePoolSize = builder.corePoolSize;}this.maxPoolSize = builder.maxPoolSize;this.queueCapacity = builder.queueCapacity;this.keepAliveTime = builder.keepAliveTime;this.allowCoreThreadTimeout = builder.allowCoreThreadTimeout;}/*** 使用静态方法替代new* @return*/public static Builder builder() {return new Builder();}public static class Builder {/*** 构造函数,可以不写*/Builder(){}/*** 线程名*/private String threadName;/*** 核心线程数*/private Integer corePoolSize ;/*** 最大线程数*/private Integer maxPoolSize;/*** 队列数*/private Integer queueCapacity;/*** 当线程空闲时间达到keepAliveTime,该线程会退出*/private Integer keepAliveTime;/*** 是否允许核心线程数空闲时退出*/private boolean allowCoreThreadTimeout;public ThreadConfig build() throws IllegalAccessException { // 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等if (StringUtils.isEmpty(threadName)) {throw new IllegalAccessException("线程名不能为空");}if(corePoolSize != null && maxPoolSize == null){throw new IllegalAccessException("最大线程数必传");}return new ThreadConfig(this);}public ThreadConfig.Builder corePoolSize(int corePoolSize) {if (corePoolSize <= 0) {throw new IllegalArgumentException("核心线程数不能小于等于0");}this.corePoolSize = corePoolSize;return this;}public ThreadConfig.Builder threadName(String threadName) {this.threadName = threadName;return this;}public ThreadConfig.Builder maxPoolSize(int maxPoolSize) {if (maxPoolSize < this.corePoolSize) {throw new IllegalArgumentException("最大线程数不能小于核心线程数");}this.maxPoolSize = maxPoolSize;return this;}public ThreadConfig.Builder queueCapacity(int queueCapacity) {if (queueCapacity <= 0) {throw new IllegalArgumentException("队列不能小于等于0");}this.queueCapacity = queueCapacity;return this;}public ThreadConfig.Builder keepAliveTime(int keepAliveTime) {if (keepAliveTime <= 0) {throw new IllegalArgumentException("保持空闲线程可用的时间不能小于等于0");}this.keepAliveTime = keepAliveTime;return this;}public ThreadConfig.Builder allowCoreThreadTimeout(boolean allowCoreThreadTimeout) {this.allowCoreThreadTimeout = allowCoreThreadTimeout;return this;}}
}

其实改动很小仅仅只是加了一个static Builder builder(),使用静态方法替代new对象,这样我们创建对象的时候就不需要new了,请看创建对象代码

ThreadConfig config =ThreadConfig.builder().threadName("hello")// .corePoolSize(3).keepAliveTime(100).queueCapacity(2).allowCoreThreadTimeout(true).build();System.out.println(config);

是不是爽多了,看着真舒服,不过话说回来,你们看这种创建方式想不想lombok中给对象加了@Builder注解之后的创建方式?没错,就是一样的,因为lombok中的@Builder就是建造者模式,只不过他的build并没有我们这里的条件判断,他是直接将属性返回了。

建造者模式与构造函数的对比

**构造函数:**适用于参数较少,逻辑简单的对象创建,对于参数过多的对象创建可能会造成参数错乱的问题而导致诡异bug。

**建造者模式:**适用于参数较多,逻辑判断较复杂的对象创建,可以让代码简洁明了,但是对象的代码增加了,不但增加了很多冗余字段,所以有时候表面看起来光鲜亮丽,内心却是无比丑陋。

建造者模式与工厂模式的对比

对工厂模式还不太明白的朋友可以参考一下:工厂模式:你还在使用一堆的if/else创建对象吗?
我们知道工厂模式主要是创建一个类型多个实现的对象,比如发送短信验证码的处理方式有很多种情况,每种情况的处理方式都不相同,还有就是创建对象的时候需要经过很多的判断,这种情况下我们就可以考虑使用工厂模式来创建对象。

如果对象的职责比较单一,没有多层含义,仅仅只是创建条件复杂,参数过多等等,使用建造者模式创建对象是首选,虽然对象中含有冗余代码,但是对象的创建真的很丝滑。

总结

如果一个类中包含着大量的属性,我们可以通过构造函数+set方法来进行对象创建,但对象如果包含一下几点特性,那么我推荐使用建造者模式。
1.必填的字段很多,这样会导致构造函数参数过长的问题。
2.如果属性与属性之间关联性很强,比如设置了核心线程数就必须要设置最大线程数,这种情况下set方法是无法做到校验的。
3.如果当前对象比较重要,我们希望对象被创建之后就不能被修改,所以这时候set方法就会被屏蔽,如果利用构造函数,又会出现字段过多问题。

当然了,我们不能为了用设计模式而用设计模式,对象一共就两个属性,我们也给他弄成建造者模式,这就有点大材小用,适得其反,一定要结合的实际的项目需求,不能盲目使用。

【设计模式】建造者模式:你创建对象的方式有它丝滑吗?相关推荐

  1. activexobject对象不能创建_【设计模式】建造者模式:你创建对象的方式有它丝滑吗?...

    目录 什么是建造者模式 为什么要使用建造者模式 构造函数创建对象 set方式构建对象 java实现建造者模式 第一种实现方式 第二种方式 建造者模式与构造函数的对比 建造者模式与工厂模式的对比 总结 ...

  2. 说说设计模式~建造者模式(Builder)

    建造者模式是我的"设计模式"里创建型模式里的最后一篇,这种模式在实现中,很多架构都用到了,如MVC,MVP,MVVM,它们都是有建造者模式的精髓的,即,创建与表现分享,我们的MVC ...

  3. 设计模式 建造者模式_设计模式:建造者

    设计模式 建造者模式 有时需要在应用程序中创建一个复杂的对象. 一种解决方案是Factory模式,另一种是Builder设计模式. 在某些情况下,您甚至可以结合使用这两种模式. 但是在本文中,我想研究 ...

  4. Python设计模式-建造者模式

    Python设计模式-建造者模式 代码基于3.5.2,代码如下; #coding:utf-8 #建造者模式 class Burger():name = ""price = 0.0d ...

  5. java设计模式-建造者模式

    概念:使用多个简单的对象一步一步构建成一个复杂的对象.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 意图:将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示. ...

  6. 大话设计模式—建造者模式

    建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 一个 Builder 类会一步一步构造最 ...

  7. Java常用设计模式————建造者模式

    引言 建造者模式(Builder Pattern)使用多个简单对象一步一步构建成一个复杂的对象.这种类型的设计模式属于建造型模式,它提供了一种创建对象的最佳方式. 一个Builder会一步步构建最终的 ...

  8. 设计模式 建造者模式

    建造者模式 建造者模式(Builder Pattern)使用多个简单的对象一步一步构建成一个复杂的对象.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 一个 Builder 类会一 ...

  9. JAVA设计模式--建造者模式

    目录 前言 一.什么是建造者模式 二.建造者模式的结构 三.建造者模式应用场景 参考文章 前言 在听完厉风行老师<设计模式系列课程>中的建造者模式一节后顿时感觉有点头大,感觉它有点像工厂方 ...

最新文章

  1. centos7 firewall 防火墙 命令
  2. 使用ADO.NET直接连接Geodatabase
  3. [译]以PostgreSQL为例,谈join计算的代价
  4. Cmake常用基本命令复习
  5. html走马观花效果,走马观花台湾行 用EF-S 10-18来记录风景
  6. OpenSsl工具的介绍
  7. ActiveMQ 即时通讯服务 入門指南及淺析
  8. [picture_scrapy] 关于美女爬虫的一个集合
  9. Java将Word/Excel转换成PDF—aspose工具
  10. Oracle JDE R23更新快报
  11. Linux终端更改字体
  12. UE4渲染设置介绍(Rendering Setting)
  13. 程序员成功之路 ——The road ahead for programmer(演讲稿)
  14. Debian之CA认证
  15. 实时获取Android手机CPU占用率
  16. 第三方推广——《互联网运营的知识体系与整体逻辑》笔记(十二)
  17. javascript getDay()方法 语法
  18. 【概率论】事件的独立与事件的互斥(或互不相容)、以及它们之间的关系
  19. MySQL 数据库 分组查询
  20. [gevent源码分析] gevent两架马车-libev和greenlet

热门文章

  1. 云e办学习笔记(三十三)FastDFS学习和安装
  2. IOS开发入门(11)-导航控制器(1)
  3. 【洛谷P1361】小猫爬山
  4. 想知道如何批量旋转图片?只要学会这两招就可以
  5. 成功解决:Module build failed: Error: Vue packages version mismatch
  6. 谈谈创业这点事(10)
  7. QQ会员2018春节红包抵扣券项目实践与总结
  8. 【Metal2剖析(七):抗锯齿之基于Imageblock特性的增强MSAA】
  9. Unity Shader Early-Z技术
  10. 12.Isaac教程--未来工厂中的搬运车