设计模式之Builder模式(链式调用)

2023-10-27

问题背景:

Builder模式在很多地方都有用到,代码风格也比较简洁,但是对其深入的了解并不多,因此在参考其他博客的基础上写此文章。
首先先看我的实际应用到Builder模式的场景:
场景一:
这是在编写RPC简易框架时,需要输入服务端的配置信息时

RPC.Server server = new RPC.Builder(new Configuration())
                .setBindAddress("localhost")
                .setPort(8888)
                .setProtocol(RPCProtocol.class)
                .setInstance(new NNServer())
                .build();

场景二:
此处是编写ES的索引时

//创建插入类 Index   Builder中的参数表示要插入到索引中的文档,底层会转换Json格式的字符串,所以也可以将文档封装为样例类对象
    val index: Index = new Index.Builder(source)
      .index("movie_index_5")
      .`type`("movie")
      .id("1")
      .build()

问题分析

在上述两个场景中,我们可以看到Builder模式的应用,都是在初始化有多个参数的情境下,那么为什么要用Builder解决问题呢,他有什么优势,我们以一个例子讲解:

public class User {

    private final String firstName;     // 必传参数
    private final String lastName;      // 必传参数
    private final int age;              // 可选参数
    private final String phone;         // 可选参数
    private final String address;       // 可选参数
}

在这个类中,有些参数是必要的,而有些参数是非必要的。那么问题就来了,如何创建这个类的对象呢?
方法一(使用构造方法)
第一个构造方法只包含两个必需的参数,第二个构造方法中,增加一个可选参数,第三个构造方法中再增加一个可选参数,依次类推,直到构造方法中包含了所有的参数。

    public User(String firstName, String lastName) {
        this(firstName, lastName, 0);
    }

    public User(String firstName, String lastName, int age) {
        this(firstName, lastName, age, "");
    }

    public User(String firstName, String lastName, int age, String phone) {
        this(firstName, lastName, age, phone, "");
    }

    public User(String firstName, String lastName, int age, String phone, String address) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
        this.phone = phone;
        this.address = address;
    }

这样虽然可以运行,但是有以下缺点:
①参数多时将很麻烦
②对于只想传一个参数,且参数位置在后边,如传入address,那么只能采用最后一个构造方法,且需对前几个参数赋值,这是耗时且不必要的操作。
方法二(采用get,set方式)
我们同样可以根据JavaBean的习惯,设置一个空参数的构造方法,然后为每一个属性设置setters和getters方法

public class User {
    private String firstName;     // 必传参数
    private String lastName;      // 必传参数
    private int age;              // 可选参数
    private String phone;         // 可选参数
    private String address;       // 可选参数

    public User() {
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }

    public String getPhone() {
        return phone;
    }

    public String getAddress() {
        return address;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

这种方法看起来可读性不错,而且易于维护。作为调用者,创建一个空的对象,然后只需传入所需要的参数,但是仍有以下缺点:
①对象会产生不一致的状态。当你想要传入5个参数的时候,你必需将所有的setXX方法调用完成之后才行。然而一部分的调用者看到了这个对象后,以为这个对象已经创建完毕,就直接使用了,其实User对象并没有创建完成。
②类是可变的了,不可变类所有好处都不复存在。
此时,Builder模式便可以解决以上问题。

public class User {

    private final String firstName;     // 必传参数
    private final String lastName;      // 必传参数
    private final int age;              // 可选参数
    private final String phone;         // 可选参数
    private final String address;       // 可选参数

    private User(UserBuilder builder) {
        this.firstName = builder.firstName;
        this.lastName = builder.lastName;
        this.age = builder.age;
        this.phone = builder.phone;
        this.address = builder.address;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public int getAge() {
        return age;
    }

    public String getPhone() {
        return phone;
    }

    public String getAddress() {
        return address;
    }

    public static class UserBuilder {
        private final String firstName;
        private final String lastName;
        private int age;
        private String phone;
        private String address;

        public UserBuilder(String firstName, String lastName) {
            this.firstName = firstName;
            this.lastName = lastName;
        }

        public UserBuilder age(int age) {
            this.age = age;
            return this;
        }

        public UserBuilder phone(String phone) {
            this.phone = phone;
            return this;
        }

        public UserBuilder address(String address) {
            this.address = address;
            return this;
        }

        public User build() {
            return new User(this);
        }
    }
}

这种方式有以下优点:
①类的构造方法是私有的。也就是说调用者不能直接创建User对象。
②类的属性都是不可变的。所有的属性都添加了final修饰符,并且在构造方法中设置了值。并且,对外只提供getters方法。
③Builder的内部类构造方法中只接收必传的参数,并且该必传的参数适用了final修饰符。

因此,创建一个User类对象的代码为:

new User.UserBuilder("王", "小二")
                .age(20)
                .phone("123456789")
                .address("亚特兰蒂斯大陆")
                .build();

代码分析:

UserBuilder类是内部类,且被static修饰,则为静态内部类。它有以下特点:

1.静态内部类跟静态方法一样,只能访问静态的成员变量和方法,不能访问非静态的方法和属性,但是普通内部类可以访问任意外部类的成员变量和方法

2.静态内部类可以声明普通成员变量和方法,而普通内部类不能声明static成员变量和方法。

3.静态内部类可以单独初始化

4.外部类访问成员内部类的成员,需要“内部类.成员”或“内部类对象.成员”的方式

静态内部类使用场景一般是当外部类需要使用内部类,而内部类无需外部类资源,并且内部类可以单独创建的时候会考虑采用静态内部类的设计。

总结:

1.如果类的构造器或静态工厂中有多个参数,设计这样类时,最好使用Builder模式,特别是当大多数参数都是可选的时候。

2.如果现在不能确定参数的个数,最好一开始就使用构建器即Builder模式。

参考博客:

https://www.jianshu.com/p/e2a2fe3555b9
https://blog.csdn.net/czh500/article/details/83876425
https://blog.csdn.net/junehappylove/article/details/85236946

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

设计模式之Builder模式(链式调用) 的相关文章

随机推荐

  • AD PCB导出Gerber文件(非常详细的步骤)

    当我们的PCB绘制好 并仔细检查后 就可以把文件交给工厂生产了 一般有两种方式 第一种最简单 就是直接将PCB文件压缩打包 发给工厂 发给工厂的途径一般有两种 一种是在其官网上提交 一种是在其开发的应用程序上提交 嘉立创工厂就可以在其开发的
  • Hibernate笔记_如何处理OO中的一些特点

    1 对象属性是复合数据类型 composite user type 这其实是OO中的aggregation 和 composition Embeddable Embedded span style font size 14px packag
  • linux ctrl+z之后如何恢复

    在linux中使用matlab的时候 常常用ctrl z将matlab挂起 一开始并不知道怎么处理 也关不掉 后来发现用fg再回车就可以将后台挂起程序切换的前台来
  • 如何利用github搭建个人网站(无需购买云服务器)

    请看原创 转载来源 1 建立GithubPage 这里的作用就是说在github上建立一个仓库 并且将它设置成github的网页模式 其实我们后面的域名只是跳转到这个仓库的页面 首先新建一个仓库 然后注意设置仓库名字时要和你的githubI
  • Graph Correspondence Transfer for Person Re-Identification论文笔记

    摘要 提出了GCT 图关系迁移 模型解决行人重识别问题 与现存的方法不一样 GCT将行人重识别视为一个离线的图匹配问题和一个在线的关系迁移问题 在训练过程中 通过patch级别的图匹配 在具有不同姿势对配置的正样本对中离线的学习得到一个关系
  • 详解numpy.random.randn函数

    文章目录 正态分布 函数原型 参数解析 该函数的注意事项 示例代码 示例结果 参考 正态分布曲线绘制代码 numpy的random模块中的randn函数用于从 标准正态 方差为1 均值为0的正态分布 分布返回一个 或多个 float类型数据
  • Ubuntu20.04下交叉编译树莓派能运行的c++程序(不含第三方库)

    参见博主之前的博客 里面生成了test目标文件 现在将这个目标文件传到树莓派上 运行出现如下报错信息 这里因为我的编译平台 x64 Ubuntu操作系统 和目标平台 ARM raspbian操作系统 所以前者编译出来的东西并不能在目标平台上
  • Ant 组件动态表单多行输入框设置禁止编辑

    deep textarea ant input background color f5f5f5 cursor not allowed
  • ModelAndView: materialized View is [null];和Action的onSubmit()方法不被执行

    ModelAndView materialized View is null
  • JavaScript继承详解(五)

    http www cnblogs com sanshi archive 2009 07 14 1523523 html 在本章中 我们将分析John Resig关于JavaScript继承的一个实现 Simple JavaScript In
  • 统计学习方法——感知机

    基本模型 感知机1957年由Rosenblatt提出 是神经网络与SVM的基础 它是一个二分类的线性分类模型 其输入为实例的特征向量 输出为实例的类别 取 1和 1二值 线性方程w x b 0对应于特征空间Rn中的一个超平面S 其中w是超平
  • 解决用QtCreator编译examples时出现的错误

    下载最新 2010 04 版本的Qt之后 运行QtCreator 打开其examples工程 编译 出现 main moc No such file or directory 错误 在网上找了一下 发现下面这个讨论 http bugrepo
  • C/C++

    文章目录 C语言的预处理及编译过程分析 gcc v o build 1 c 主要步骤 预处理 相当于替换 示例 编译 汇编 让cpu认识代码 链接器 链接系统标准库 示例 摘自 麦子学院 C语言程序设计及快速入门 C语言的预处理及编译过程分
  • 面向对象继承

    面向对象 继承 人 class CPerson public CPerson CPerson int GetGender return m nGender void SetGender int nGender m nGender nGend
  • 关于消除不受信任的HTML(来防止XSS攻击)

    1 什么是消除不受信任的HTML 来防止XSS攻击 引用 2 什么是jsoup jsoup 是一个用于处理实际HTML的Java库 它使用HTML5最佳DOM方法和CSS选择器 为提取URL以及提取和处理数据提供了非常方便的API jsou
  • 多处理器架构

    多处理器架构是一种计算机体系结构 它具有多个处理器或中央处理单元 CPU 这些处理器可以同时处理多个任务和数据 多处理器架构可以大大提高计算机的性能和吞吐量 特别是在需要处理大量数据和高度并发的应用程序中 在现代计算机系统中 多处理器架构已
  • UE4 C++ 对结构体数组内元素进行排序

    对结构体数组排序需要两个必要条件 一是结构体定义内重写 lt 操作符 二是元素内必须要有可以用来排序的属性例如int float类型的变量 案例 h UENUM BlueprintType enum class EOrient uint8
  • React Native 技术选型分析

    本文从技术决策讲起 聊聊我们为什么要使用 React Native 如何使用 以及其他维度的思考 文中将用 RN 代表 React Native 1 技术选型决策 技术选型是一个复杂的过程 必须谨慎并保持敬畏 需要根据项目的实际情况而定 这
  • LeetCode 多线程1116. 打印零与奇偶数

    题目https leetcode cn com problems print zero even odd 首先判断奇数偶数 num 1 0 为奇数 可以通过信号量Semaphore来求 class ZeroEvenOdd private i
  • 设计模式之Builder模式(链式调用)

    问题背景 Builder模式在很多地方都有用到 代码风格也比较简洁 但是对其深入的了解并不多 因此在参考其他博客的基础上写此文章 首先先看我的实际应用到Builder模式的场景 场景一 这是在编写RPC简易框架时 需要输入服务端的配置信息时