设计模式之美
王争
前 Google 工程师,《数据结构与算法之美》专栏作者
120453 人已学习
新⼈⾸单¥98
登录后,你可以任选6讲全文学习
课程目录
已完结/共 113 讲
设计模式与范式:行为型 (18讲)
设计模式之美
15
15
1.0x
00:00/00:00
登录|注册

46 | 建造者模式:详解构造函数、set方法、建造者模式三种对象创建方式

上两节课中,我们学习了工厂模式,讲了工厂模式的应用场景,并带你实现了一个简单的 DI 容器。今天,我们再来学习另外一个比较常用的创建型设计模式,Builder 模式,中文翻译为建造者模式或者构建者模式,也有人叫它生成器模式
实际上,建造者模式的原理和代码实现非常简单,掌握起来并不难,难点在于应用场景。比如,你有没有考虑过这样几个问题:直接使用构造函数或者配合 set 方法就能创建对象,为什么还需要建造者模式来创建呢?建造者模式和工厂模式都可以创建对象,那它们两个的区别在哪里呢?
话不多说,带着上面两个问题,让我们开始今天的学习吧!

为什么需要建造者模式?

在平时的开发中,创建一个对象最常用的方式是,使用 new 关键字调用类的构造函数来完成。我的问题是,什么情况下这种方式就不适用了,就需要采用建造者模式来创建对象呢?你可以先思考一下,下面我通过一个例子来带你看一下。
假设有这样一道设计面试题:我们需要定义一个资源池配置类 ResourcePoolConfig。这里的资源池,你可以简单理解为线程池、连接池、对象池等。在这个资源池配置类中,有以下几个成员变量,也就是可配置项。现在,请你编写代码实现这个 ResourcePoolConfig 类。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结
仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《设计模式之美》
新⼈⾸单¥98
立即购买
登录 后留言

全部留言(144)

  • 最新
  • 精选
  • zhengyu.nie
    依赖关系(Dependencies)、合法校验(Preconditions)、不可变(Immutable)。 争哥这几个描述很精准! 借着思路延伸一下的话,很多库和设计都是以上这些理念的。 比如com.google.guava的校验、不可变集合,多线程设计模式中的Immutable模式、保护性拷贝(其中又可以分深浅api),java.lang.String的不可变设计。还有关于类状态的控制,还有Effective Java中类创建这一章中对于构造方法、set方法、Builder构建、枚举、静态工厂方法构建等对比,像Guava Lists、Sets、Maps、ImmutableList这种创建方式现在也很主流了

    作者回复: 👍

    2
    43
  • 苏暮沉觞
    小争哥,我在学习建造者模式的时候,发现传统的构建者模式写法跟文中的不太一样,传统的构建者模式存在监工,抽象Builder,具体Builder,产品类。可以通过不同的具体Builder,创建对应的产品类。而你在文章中的设计模式的写法,在某些文章中,被称为变种的构造者模式。 我比较了一下两种方式,感觉传统的构建者模式需要提前定义好你要生产的产品对应的属性,而文中这种方法则是在构建产品时,自己动态的设置产品属性,虽然说这种方法更加灵活,但是侵入性更高。 那么考虑到以后需求变更,是不是应该是用传统的构建者模式呢?不然要是资源池对应的参数变了,就要修改业务代码中的参数了。

    作者回复: 在真实的项目里,大部分都是用我说的这种实现方式吧。你说的监工、抽象builder、具体builder的实现方式,有具体的例子吗?

    4
  • 悠南
    你这建造者模式代码,看不懂,构造方法私有了,怎么来的Builder 方法

    作者回复: 内部类可以直接调用外部类的成员和方法,包括private的吧

    3
    1
  • 是非~成败~
    /** * 需求: * 当 isRef 为 true 的时候,arg 表示 String 类型的 refBeanId,type 不需要设置; * 当 isRef 为 false 的时候,arg、type 都需要设置。 * 请根据这个需求,完善 ConstructorArg 类。 */ public class ConstructorArg_02 { private boolean isRef; private Class type; private Object arg; private ConstructorArg_02(Builder builder) { this.isRef = builder.isRef; this.type = builder.type; this.arg = builder.arg; } public static class Builder { private boolean isRef; private Class type; private Object arg; // 后面两个参数依赖isRef,所以isRef设置成必选参数 public Builder(boolean isRef) { this.isRef = isRef; } public ConstructorArg_02 build() { return new ConstructorArg_02(this); } public Builder setType(Class type) { this.type = type; return this; } public Builder setArg(Object arg) { // isRef为true, arg不是String类型就报错 if (isRef && arg.getClass() != String.class) { throw new RuntimeException("参数错误"); } this.arg = arg; return this; } } } 咱也不知道写的好不好,希望争哥看到了检视指导一二

    作者回复: 不错!

  • Morse
    为什么定义了一个长方形类,如果不使用建造者模式,采用先创建后 set 的方式,那就会导致在第一个 set 之后,对象处于无效状态?求大佬解答

    作者回复: 那就要用builder模式了

    4
  • Darren
    简单理解就是:工厂模式是根据不同的条件生成不同Class的对象,构建者模式是根据不同参数生成一个class的不同对象。
    4
    126
  • webmin
    public class ConstructorArg { private boolean isRef; private Class type; private Object arg; public boolean isRef() { return isRef; } public Class getType() { return type; } public Object getArg() { return arg; } private ConstructorArg(Builder builder) { this.isRef = builder.isRef; this.type = builder.type; this.arg = builder.arg; } public static class Builder { private boolean isRef; private Class type; private Object arg; public ConstructorArg build() { if(isRef && type != null) { throw new IllegalArgumentException("..."); } if (!isRef && type == null) { throw new IllegalArgumentException("..."); } if (this.isRef && (arg != null && arg.getClass() != String.class)) { throw new IllegalArgumentException("..."); } if (!this.isRef && arg == null) { throw new IllegalArgumentException("..."); } return new ConstructorArg(this); } public Builder setRef(boolean ref) { if(ref && this.type != null) { throw new IllegalArgumentException("..."); } this.isRef = ref; return this; } public Builder setType(Class type) { if (this.isRef || type == null) { throw new IllegalArgumentException("..."); } this.type = type; return this; } public Builder setArg(Object arg) { if (this.isRef && (arg != null && arg.getClass() != String.class)) { throw new IllegalArgumentException("..."); } if (!this.isRef && arg == null) { throw new IllegalArgumentException("..."); } this.arg = arg; return this; } } }
    13
    114
  • 相逢是缘
    打卡 一、使用场景: 1)类的构造函数必填属性很多,通过set设置,没有办法校验必填属性 2)如果类的属性之间有一定的依赖关系,构造函数配合set方式,无法进行依赖关系和约束条件校验 3)需要创建不可变对象,不能暴露set方法。 (前提是需要传递很多的属性,如果属性很少,可以不需要建造者模式) 二、实现方式: 把构造函数定义为private,定义public static class Builder 内部类,通过Builder 类的set方法设置属性,调用build方法创建对象。 三、和工厂模式的区别: 1)工厂模式:创建不同的同一类型对象(集成同一个父类或是接口的一组子类),由给定的参数来创建哪种类型的对象; 2)建造者模式:创建一种类型的复杂对象,通过很多可设置参数,“定制化”的创建对象
    4
    55
  • javaadu
    课堂讨论题: /** * 在下面的 ConstructorArg 类中, * 当 isRef 为 true 的时候,arg 表示 String 类型的 refBeanId,type 不需要设置; * 当 isRef 为 false 的时候,arg、type 都需要设置 * * @author javaadu */ public class ConstructorArg { private boolean isRef; private Class type; private Object arg; private ConstructorArg(Builder builder) { this.isRef = builder.isRef; this.type = builder.type; this.arg = builder.arg; } public static class Builder { private boolean isRef; private Class type; private Object arg; public ConstructorArg build() { if (arg == null) { throw new IllegalArgumentException("arg必须设置"); } if (isRef) { if (!(arg instanceof String)) { throw new IllegalArgumentException("arg必须为String类型的对象"); } } else { if (type == null) { throw new IllegalArgumentException("arg必须设置"); } } return new ConstructorArg(this) } public Builder setRef(boolean ref) { isRef = ref; return this; } public Builder setArg(Object arg) { this.arg = arg; return this; } public Builder setType(Class type) { this.type = type; return this; } } }
    48
  • 小文同学
    说说自己读了 Builder 模式的最大感悟: 1、Builder 模式可以保证对象的状态。 2、Builder 模式可以把对象的构造鉴定逻辑写在Builder类中,保证了类的简洁。 平时普普通通地使用 lombok 生成 Builder,应该更加深入地了解一下。
    17
收起评论
显示
设置
留言
99+
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部