设计模式之美
王争
前Google工程师,《数据结构与算法之美》专栏作者
立即订阅
20304 人已学习
课程目录
已更新 50 讲 / 共 100 讲
0/6登录后,你可以任选6讲全文学习。
开篇词 (1讲)
开篇词 | 一对一的设计与编码集训,让你告别没有成长的烂代码!
免费
设计模式学习导读 (3讲)
01 | 为什么说每个程序员都要尽早地学习并掌握设计模式相关知识?
02 | 从哪些维度评判代码质量的好坏?如何具备写出高质量代码的能力?
03 | 面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
设计原则与思想:面向对象 (11讲)
04 | 理论一:当谈论面向对象的时候,我们到底在谈论什么?
05 | 理论二:封装、抽象、继承、多态分别可以解决哪些编程问题?
06 | 理论三:面向对象相比面向过程有哪些优势?面向过程真的过时了吗?
07 | 理论四:哪些代码设计看似是面向对象,实际是面向过程的?
08 | 理论五:接口vs抽象类的区别?如何用普通的类模拟抽象类和接口?
09 | 理论六:为什么基于接口而非实现编程?有必要为每个类都定义接口吗?
10 | 理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?
11 | 实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?
12 | 实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?
13 | 实战二(上):如何对接口鉴权这样一个功能开发做面向对象分析?
14 | 实战二(下):如何利用面向对象设计和编程开发接口鉴权功能?
设计原则与思想:设计原则 (12讲)
15 | 理论一:对于单一职责原则,如何判定某个类的职责是否够“单一”?
16 | 理论二:如何做到“对扩展开放、修改关闭”?扩展和修改各指什么?
17 | 理论三:里式替换(LSP)跟多态有何区别?哪些代码违背了LSP?
18 | 理论四:接口隔离原则有哪三种应用?原则中的“接口”该如何理解?
19 | 理论五:控制反转、依赖反转、依赖注入,这三者有何区别和联系?
20 | 理论六:我为何说KISS、YAGNI原则看似简单,却经常被用错?
21 | 理论七:重复的代码就一定违背DRY吗?如何提高代码的复用性?
22 | 理论八:如何用迪米特法则(LOD)实现“高内聚、松耦合”?
23 | 实战一(上):针对业务系统的开发,如何做需求分析和设计?
24 | 实战一(下):如何实现一个遵从设计原则的积分兑换系统?
25 | 实战二(上):针对非业务的通用框架开发,如何做需求分析和设计?
26 | 实战二(下):如何实现一个支持各种统计规则的性能计数器?
设计原则与思想:规范与重构 (11讲)
27 | 理论一:什么情况下要重构?到底重构什么?又该如何重构?
28 | 理论二:为了保证重构不出错,有哪些非常能落地的技术手段?
29 | 理论三:什么是代码的可测试性?如何写出可测试性好的代码?
30 | 理论四:如何通过封装、抽象、模块化、中间层等解耦代码?
31 | 理论五:让你最快速地改善代码质量的20条编程规范(上)
32 | 理论五:让你最快速地改善代码质量的20条编程规范(中)
33 | 理论五:让你最快速地改善代码质量的20条编程规范(下)
34 | 实战一(上):通过一段ID生成器代码,学习如何发现代码质量问题
35 | 实战一(下):手把手带你将ID生成器代码从“能用”重构为“好用”
36 | 实战二(上):程序出错该返回啥?NULL、异常、错误码、空对象?
37 | 实战二(下):重构ID生成器项目中各函数的异常处理代码
设计原则与思想:总结课 (3讲)
38 | 总结回顾面向对象、设计原则、编程规范、重构技巧等知识点
39 | 运用学过的设计原则和思想完善之前讲的性能计数器项目(上)
40 | 运用学过的设计原则和思想完善之前讲的性能计数器项目(下)
设计模式与范式:创建型 (6讲)
41 | 单例模式(上):为什么说支持懒加载的双重检测不比饿汉式更优?
42 | 单例模式(中):我为什么不推荐使用单例模式?又有何替代方案?
43 | 单例模式(下):如何设计实现一个集群环境下的分布式单例模式?
44 | 工厂模式(上):我为什么说没事不要随便用工厂模式创建对象?
45 | 工厂模式(下):如何设计实现一个Dependency Injection框架?
46 | 建造者模式:详解构造函数、set方法、建造者模式三种对象创建方式
不定期加餐 (3讲)
加餐一 | 用一篇文章带你了解专栏中用到的所有Java语法
加餐二 | 设计模式、重构、编程规范等相关书籍推荐
春节特别加餐 | 王争:如何学习《设计模式之美》专栏?
免费
设计模式之美
登录|注册

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

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

为什么需要建造者模式?

在平时的开发中,创建一个对象最常用的方式是,使用 new 关键字调用类的构造函数来完成。我的问题是,什么情况下这种方式就不适用了,就需要采用建造者模式来创建对象呢?你可以先思考一下,下面我通过一个例子来带你看一下。
假设有这样一道设计面试题:我们需要定义一个资源池配置类 ResourcePoolConfig。这里的资源池,你可以简单理解为线程池、连接池、对象池等。在这个资源池配置类中,有以下几个成员变量,也就是可配置项。现在,请你编写代码实现这个 ResourcePoolConfig 类。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《设计模式之美》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(14)

  • 相逢是缘
    打卡
    一、使用场景:
    1)类的构造函数必填属性很多,通过set设置,没有办法校验必填属性
    2)如果类的属性之间有一定的依赖关系,构造函数配合set方式,无法进行依赖关系和约束条件校验
    3)需要创建不可变对象,不能暴露set方法。
    (前提是需要传递很多的属性,如果属性很少,可以不需要建造者模式)
    二、实现方式:
    把构造函数定义为private,定义public static class Builder 内部类,通过Builder 类的set方法设置属性,调用build方法创建对象。

    三、和工厂模式的区别:
    1)工厂模式:创建不同的同一类型对象(集成同一个父类或是接口的一组子类),由给定的参数来创建哪种类型的对象;
    2)建造者模式:创建一种类型的复杂对象,通过很多可设置参数,“定制化”的创建对象
    2020-02-17
    2
  • 黄林晴
    打卡~
    最近半年用的最多的就是Builder模式了
    2020-02-17
    1
  • 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;
            }
        }
    }
    2020-02-17
    1
  • 大牛凯
    感觉课后思考题的arguments逻辑判断相对复杂,再加上想尝试一下建造者模式,所以抛砖引玉,给一个Python的尝试,有一个困惑是对于Python这种语言,如何避免对用户暴露在调用builder之前build方法之前初始化的ConstructorArg对象?

    class ConstructorArg(object):
        def __init__(self, isRef): #如何避免暴露这个实例对象?
            self.isRef = isRef
            self.type = None
            self.arg = None
        
        def __repr__(self):
            string = f"isRef: {self.isRef}\n" + \
                     f"type: {self.type}\n" + \
                     f"arg: {self.arg}\n"
            return string

        class ConstructorArgBuilder(object):
            def __init__(self, isRef):
                self.constructorArg = ConstructorArg(isRef)

            def addType(self, typeObj):
                self.constructorArg.type = typeObj
                return self

            def addArg(self, argObj):
                self.constructorArg.arg = argObj
                return self

            def build(self):
                if self.constructorArg.isRef:
                    if self.constructorArg.type is None:
                        raise Exception("type cannot be None when isRef")
                    elif not isinstance(self.constructorArg.type, str):
                        raise Exception("type must be string when isRef")
                elif not self.constructorArg.isRef:
                    if self.constructorArg.type is None:
                        raise Exception("type cannot be None when not isRef")
                    if self.constructorArg.arg is None:
                        raise Exception("arg cannot be None when not isRef")
                return self.constructorArg
    2020-02-17
    1
  • 啦啦啦
    用php实现了一个
    <?php
    class ResourcePoolConfig{
        private $maxTotal;
        private $maxIdle;
        private $minIdle;
        public function __construct(build $build)
        {
            $this->maxTotal = $build->maxTotal;
            $this->maxIdle = $build->maxIdle;
            $this->minIdle = $build->minIdle;
            echo 'maxTotal'.$this->maxTotal;
            echo '<br />';
            echo 'maxIdle'.$this->maxIdle;
            echo '<br />';
            echo 'minIdle'.$this->minIdle;
            echo '<br />';
        }
    }

    class Builder{
        public $maxTotal;
        public $maxIdle;
        public $minIdle;
        public function noodleValidate(){
            if($this->maxIdle>$this->maxTotal){
                throw new Exception("maxIdle抛出异常");
            }
            if($this->minIdle>$this->maxTotal){
                throw new Exception("minIdle抛出异常");
            }
        }

        public function setMaxTotal($value=20){
            $this->maxTotal = $value;
            return $this;
        }

        public function setMaxIdle($value=10){
            $this->maxIdle = $value;
            return $this;
        }

        public function setMinIdle($value=10){
            $this->minIdle = $value;
            return $this;
        }
    }

    $b = new Builder();
    $b->setMaxTotal(5)->setMaxIdle()->setMinIdle()->noodleValidate();
    new ResourcePoolConfig($b);
    2020-02-17
  • PHP是世界上最好的需要
    总感觉只是把校验逻辑挪到了bulder 里。没直观的感觉到builder 的有点呢。老师~
    2020-02-17
  • 王涛
    最近,在学习本专栏的过程中。
    逐渐体会到“知其然,更知其所以然”的感觉
    2020-02-17
  • Ken张云忠
    public class ConstructorArg {
        private boolean isRef;
        private Class type;
        private Object arg;
        public ConstructorArg(ConstructorArgBuilder builder) {
            this.isRef = builder.isRef;
            this.type = builder.type;
            this.arg = builder.arg;
        }
        public static class ConstructorArgBuilder {
            private boolean isRef;
            private Class type;
            private Object arg;
            public ConstructorArg builder() {
                if (arg == null) {
                    throw new IllegalArgumentException("arg should not be null");
                }
                if (isRef == true) {
                    if (arg instanceof String) {
                        type = String.class;
                    } else {
                        throw new IllegalArgumentException("when isRef is true,arg should be String type");
                    }
                } else {
                    type = arg.getClass();
                }
                return new ConstructorArg(this);
            }
            public ConstructorArgBuilder(boolean isRef) {
                this.isRef = isRef;
            }
            public ConstructorArgBuilder setArg(Object arg) {
                this.arg = arg;
                return this;
            }
        }
    }
     // use case
    ConstructorArg constructorArg = new ConstructorArgBuilder(true).setArg(0).builder();
    ConstructorArg constructorArg1 = new ConstructorArgBuilder(false).setArg(1).builder();
    2020-02-17
  • Algo
    package com.acacia.leetcode.huahua.questions;

    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 final static Class TYPE_STRING = String.class;
            private final static Object ARG_STRING = "String";

            private boolean isRef;
            private Class type;
            private Object arg;

            public ConstructorArg build(){
                if (isRef == true){
                    this.type = TYPE_STRING;
                    this.arg = ARG_STRING;
                    return new ConstructorArg(this);
                }else {
                    return new ConstructorArg(this);
                }
            }

            public Builder setRef(boolean ref) {
                this.isRef = ref;
                return this;
            }

            public Builder setType(Class type) {
    // if (!type.isMemberClass()){
    // throw new IllegalArgumentException("failed class name");
    // }
                this.type = type;
                return this;
            }

            public Builder setArg(Object arg) {
                if (arg == null){
                    throw new IllegalArgumentException("can not be null");
                }
                this.arg = arg;
                return this;
            }
        }

        public static void main(String[] args) {
            ConstructorArg arg = new ConstructorArg.Builder().setRef(true).build();
            System.out.println(arg.type);
            System.out.println(arg.arg);
            ConstructorArg arg1 = new ConstructorArg.Builder().setArg(false).setType(Integer.class).setArg(2).build();
            System.out.println(arg1.type);
            System.out.println(arg1.arg);
        }
    }



    2020-02-17
  • cricket1981
    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 boolean getIsRef() {
            return isRef;
        }

        public Class getType() {
            return type;
        }

        public Object getArg() {
            return arg;
        }

        public static class Builder {

            private boolean isRef;
            private Class type;
            private Object arg;

            public ConstructorArg build() {
                if (arg == null) {
                    throw new IllegalArgumentException("The value of parameter `arg` should be provided.");
                }
                if (!this.isRef && type == null) {
                    throw new IllegalArgumentException("The value of parameter `type` should be provided if `isRef` == false.");
                }
                return new ConstructorArg(this);
            }

            public Builder setRef(boolean isRef) {
                this.isRef = isRef;
                return this;
            }

            public Builder setType(Class type) {
                this.type = type;
                return this;
            }

            public Builder setArg(Object arg) {
                this.arg = arg;
                return this;
            }
        }

    }

    用法:
    ConstructorArg constructorArg1 = new ConstructorArg.Builder()
            .setArg("rateCounter")
            .setRef(true)
            .build();

    ConstructorArg constructorArg2 = new ConstructorArg.Builder()
            .setArg("127.0.0.1")
            .setType(String.class)
            .setRef(false)
            .build();
    2020-02-17
  • 小晏子
    课后讨论题:

     public class ConstructorArg {
        private boolean isRef;
        private Class type;
        private Object arg;
        // TODO: 待完善...

        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) {
                    if (type == null || arg == null){
                        throw new IllegalArgumentException("type and arg must be set when isRef is false")
                    }
                } else {
                    if (!(arg instanceof String)) {
                        throw new IllegalArgumentException("arg must be a String instance when isRef is true")
                    }
                }
                return new ConstructorArg(this);
            }
        }

        public Builder setIsRef(boolean isRef){
            this.isRef = isRef;
            return this;
        }

        public Builder setType(Class type) {
            this.type = type;
            return this;
        }

        public Builder setObject(Object arg) {
            this.arg = arg;
            return this;
        }
      }

      //use case
      ConstructorArg ca = ConstructorArg.Builder()
            .setIfRef(false)
            .setType(Integer)
            .setObject(2)
            .build();
    2020-02-17
  • Frank
    当在开发中需要创建一个具体的对象,如果必填属性很多,属性存在复杂的校验和属性之间存在依赖关系,可以使用建造者模式来避免类的构造函数参数列表过长,导致可读性差,不易使用的问题。建造者模式可以将类的构造方法私有,不提供类的setter方法,因此可以创建一个不变的对象。同时在某些场景下将属性构造好可以解决类的无效状态。
    使用思路:把校验逻辑放置到 Builder 类中,先创建建造者,并且通过 set() 方法设置建造者的变量值,然后在使用 build() 方法真正创建对象之前,做集中的校验,校验通过之后才会创建对象。除此之外,我们把主类的构造函数为了private 私有权限,只能通过建造者来创建 类对象,并且主类没有提供任何 set() 方法,这样创建出来的对象就是不可变对象了。
    2020-02-17
  • 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;
            }
        }
    }
    2020-02-17
  • Jeff.Smile
    建造者模式核心代码:
    ResourcePoolConfig config = new ResourcePoolConfig.Builder()
            .setName("dbconnectionpool")
            .setMaxTotal(16)
            .setMaxIdle(10)
            .setMinIdle(12)
            .build();
    2020-02-17
收起评论
14
返回
顶部