Java设计模式:建造者模式之经典与流式的三种实现(四)

本文将深入探讨Java中建造者模式的两种实现方式:经典建造者与流式建造者。建造者模式是一种创建型设计模式,它允许你构建复杂对象的步骤分解,使得对象的创建过程更加清晰和灵活。我们将通过示例代码详细解释这两种实现方式,并分析它们各自的特点和使用场景。

[参见]:

Java设计模式:核心概述(一)

Java设计模式:单例模式之六种实现方式详解(二)

Java设计模式:工厂模式之简单工厂、工厂方法、抽象工厂(三)

目录

    • 一、前言
    • 二、建造者模式的使用场景
    • 三、建造者模式的三种实现方式
      • 3.1 经典建造者模式
      • 3.2 流式建造者模式
      • 3.3 使用Lombok库实现流式建造者模式
    • 四、建造者模式的注意事项
    • 五、结语

一、前言

开发中,我们经常需要创建具有多个属性或配置选项的复杂对象。直接通过构造函数或大量的setter方法来设置这些属性可能会导致代码的可读性和维护性下降。建造者模式通过提供一个建造者类来封装复杂对象的创建过程,使得客户端代码可以更加简洁和清晰地构建对象。

二、建造者模式的使用场景

  1. 当对象有多个构造函数参数时:如果一个类的构造函数需要多个参数,而这些参数中有些是可选的,那么使用建造者模式可以更加清晰地表达对象的创建过程,避免构造函数的参数列表过长和混乱。

  2. 当对象的构建过程需要多个步骤时:如果对象的创建过程涉及多个步骤,并且这些步骤的顺序很重要,那么建造者模式可以确保这些步骤按照正确的顺序执行,并提供一种灵活的方式来调整这些步骤。

  3. 当需要创建不同配置的对象变体时:如果需要根据不同的配置选项创建对象的多个变体,那么建造者模式可以提供一种清晰的方式来表示这些配置选项,并根据需要构建不同的对象变体。

  4. 当对象的构建过程需要复杂的逻辑时:如果对象的构建过程涉及复杂的逻辑,如验证参数的有效性、计算属性的默认值等,那么建造者模式可以将这些逻辑封装在建造者类中,使客户端代码更加简洁和清晰。

在这里插入图片描述


三、建造者模式的三种实现方式

Java中,实现建造者设计模式通常有两种主要方式:经典的建造者模式(也称为分步构建器)和流式建造者模式(也称为流畅接口构建器或链式调用构建器)。第三种借用lomback自动生成流式建造者模式,所以实际是两种方式。

3.1 经典建造者模式

经典建造者模式通常包括一个产品类、一个抽象建造者接口、一个具体建造者类和一个指挥者类(可选)。产品类包含需要设置的属性和访问器方法。抽象建造者接口定义了构建产品所需的各个步骤。具体建造者类实现了抽象建造者接口,并提供了设置产品属性和获取构建好的产品对象的方法。

然而,在实际应用中,我们往往可以省略指挥者类,直接在客户端代码中操作具体建造者来构建产品对象。这种方式简化了经典建造者模式的结构,但仍然保留了其逐步构建对象的优点。

代码:

// 产品类  
public class Product {  
    private String partA;  
    private String partB;  
    private String partC;  
  
    // 构造方法可以是私有的,因为建造者模式可以控制对象的构建过程  
    private Product() {}  
  
    public String getPartA() {  
        return partA;  
    }  
  
    public void setPartA(String partA) {  
        this.partA = partA;  
    }  
  
    // 省略其他getter和setter方法...  
  
    // 静态内部类作为具体建造者  
    public static class Builder {  
        private Product product;  
  
        public Builder() {  
            product = new Product();  
        }  
  
        public Builder setPartA(String partA) {  
            product.partA = partA;  
            return this; // 返回当前Builder实例,以便链式调用  
        }  
  
        public Builder setPartB(String partB) {  
            product.partB = partB;  
            return this;  
        }  
  
        public Builder setPartC(String partC) {  
            product.partC = partC;  
            return this;  
        }  
  
        // 创建并返回产品对象  
        public Product build() {  
            return product;  
        }  
    }  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        Product product = new Product.Builder()  
            .setPartA("This is part A")  
            .setPartB("This is part B")  
            .setPartC("This is part C")  
            .build();  
  
        System.out.println(product.getPartA());  
        // 省略访问其他部分...  
    }  
}

Product 类有一个静态内部类 Builder,它负责构建 Product 对象。客户端代码通过调用 Builder 的方法来设置产品的各个部分,并最终调用 build() 方法来获取构建好的产品对象。

3.2 流式建造者模式

流式建造者模式是经典建造者模式的一种变体,它更加强调链式调用的流畅性。在流式建造者模式中,产品类通常包含一个静态内部类作为流式建造者。这个内部类提供了设置产品属性的方法,并返回自身的实例以支持链式调用。最后,通过一个build()方法返回构建好的产品对象。

流式建造者模式省略了经典建造者模式中的抽象建造者接口和指挥者类,使得代码更加简洁和直观。同时,通过链式调用的方式设置产品属性,可以提高代码的可读性和编写的灵活性。

代码:

// 产品类  
public class Product {  
    private String partA;  
    private String partB;  
    private String partC;  
  
    // 私有构造方法,防止直接实例化  
    private Product() {}  
  
    // 省略getter方法...  
  
    // 静态内部类作为流式建造者  
    public static class Builder {  
        private String partA;  
        private String partB;  
        private String partC;  
  
        public Builder withPartA(String partA) {  
            this.partA = partA;  
            return this;  
        }  
  
        public Builder withPartB(String partB) {  
            this.partB = partB;  
            return this;  
        }  
  
        public Builder withPartC(String partC) {  
            this.partC = partC;  
            return this;  
        }  
  
        // 创建产品对象  
        public Product build() {  
            Product product = new Product();  
            product.partA = this.partA;  
            product.partB = this.partB;  
            product.partC = this.partC;  
            return product;  
        }  
    }  
}  
  
// 客户端代码  
public class Client {  
    public static void main(String[] args) {  
        Product product = new Product.Builder()  
            .withPartA("This is part A")  
            .withPartB("This is part B")  
            .withPartC("This is part C")  
            .build();  
  
        // 省略访问产品属性的代码...  
    }  
}

Builder 类的每个设置方法都返回 Builder 类型的实例(即 this),从而允许链式调用。这种方式使得客户端代码更加简洁和易读。注意,在这个实现中,Product 类的构造方法是私有的,以防止外部直接实例化。所有的属性设置都是通过 Builder 进行的。

3.3 使用Lombok库实现流式建造者模式

Lombok 是一个 Java 库,它可以通过注解来简化 Java 代码,例如自动生成 getter、setter、equals、hashCode 和 toString 方法等。其中,@Builder 注解就是用来实现建造者模式的一个简化工具。

使用 Lombok 的 @Builder 注解,你不需要手动编写建造者模式的代码。只需要在类定义上添加 @Builder 注解,Lombok 就会在编译时自动生成相应的建造者类。

下面是一个使用 Lombok @Builder 注解来实现建造者模式的代码:

import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@Builder
public class Person {
    private String name;
    private int age;
    private String address;

    // 使用 Lombok @Builder 注解后,无需再手动编写建造者模式的代码
    // Lombok 会在编译时自动生成一个名为 Person.PersonBuilder 的内部类

    // 下面是如何使用生成的建造者类来构建 Person 对象的示例
    public static void main(String[] args) {
        Person person = Person.builder()
                .name("John Doe")
                .age(30)
                .address("123 Main St")
                .build();
        
        System.out.println(person);
    }
}

在这个例子中,我们定义了一个 Person 类,并使用了 @Getter@Setter@Builder 注解。@Getter@Setter 注解分别用于生成 getter 和 setter 方法,而 @Builder 注解则用于生成建造者模式的代码。

main 方法中,我们使用 Person.builder() 方法来获取一个 PersonBuilder 实例,然后通过链式调用设置 nameageaddress 属性,最后调用 build() 方法来构建 Person 对象。

以上使用 Lombok 的 @Builder 注解所生成的代码在编译时会创建一个内部类,这个内部类通常命名为 类名Builder,在这个例子中是 PersonBuilder。这个内部类会包含对应类中所有字段的设置方法以及一个 build() 方法来创建目标对象。

生成的代码大致相当于手动实现的流式建造者模式,因为它允许通过链式调用的方式来设置对象的属性。不过,由于 Lombok 自动处理了这些细节,用户无需手动编写这些代码。

假设没有 Lombok,并且我们要手动实现上述 Person 类的建造者模式,代码可能看起来像这样:

public class Person {
    private String name;
    private int age;
    private String address;

    private Person(Builder builder) {
        this.name = builder.name;
        this.age = builder.age;
        this.address = builder.address;
    }

    public static class Builder {
        private String name;
        private int age;
        private String address;

        public Builder() {}

        public Builder name(String name) {
            this.name = name;
            return this;
        }

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

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

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

    // getters, setters, toString, etc.
}

使用 Lombok,上面的所有建造者逻辑都会在编译时自动生成,你不需要手动编写这些代码。Lombok 会创建一个与上述手动实现的 Builder 类类似的内部类,并提供同样的链式调用功能。

在这里插入图片描述


四、建造者模式的注意事项

  1. 避免滥用建造者模式:虽然建造者模式可以提供一种清晰和灵活的方式来构建复杂对象,但过度使用它可能会导致代码变得复杂和难以维护。因此,在决定使用建造者模式之前,应该仔细评估对象的复杂性和构建过程的需求。

  2. 考虑建造者的可重用性:如果对象的构建过程涉及一些可重用的步骤或组件,那么可以设计可重用的建造者类来减少代码重复和提高可维护性。例如,可以创建一些通用的建造者类来处理常见的构建步骤,并在需要时进行扩展或定制。

  3. 注意与工厂模式的区别:建造者模式与工厂模式都是创建型设计模式,但它们的职责和用途有所不同。工厂模式主要负责对象的创建和实例化,而建造者模式则关注于对象的构建过程和属性的设置。在实际应用中,可以根据需要选择使用哪种模式或结合使用它们。

五、结语

  • 建造者模式是一种强大且灵活的设计模式,适用于构建具有多个属性和复杂构建过程的对象。
  • 通过合理使用建造者模式,可以提高代码的可读性、可维护性和可扩展性。
  • 然而,在使用建造者模式时,也需要注意避免滥用、考虑线程安全问题以及与其他设计模式的区别和协作。
  • 只有在合适的场景下使用建造者模式,才能充分发挥其优势并提升软件的质量。

经典建造者模式和流式建造者模式都是用于封装复杂对象创建过程的有效方式。经典建造者模式通过抽象建造者接口和具体建造者类的分离,实现了构建过程的灵活性和可扩展性。而流式建造者模式则通过链式调用的方式,提高了代码的可读性和编写的便捷性。

在选择使用哪种建造者模式时,需要根据具体的需求和场景来判断。如果需要较高的灵活性和可扩展性,可以考虑使用经典建造者模式;如果追求代码的简洁和易读性,流式建造者模式可能是一个更好的选择。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/435104.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

重塑Android通信新格局:探秘Android 8.0之后的Binder架构革新

重塑Android通信新格局:探秘Android 8.0 之后的Binder架构革新 1. 引言 Android作为全球主流移动操作系统,在移动设备领域扮演着举足轻重的角色。其开放性、灵活性和广泛的应用生态系统使得无数用户和开发者受益。作为一个基于Linux内核的操作系统,Android的核心架构设计至…

Spring Webflux 详解

目录 0、组件对比 1、WebFlux 1、引入 2、Reactor Core 1、HttpHandler、HttpServer 3、DispatcherHandler 1、请求处理流程 4、注解开发 1、目标方法传参 2.返回值写法 5、文件上传 6、错误处理 7、RequestContext 8、自定义Flux配置 9、Filter WebFlux&am…

Elasticsearch模拟网络丢包

背景 Elasticsearch一旦遇到网络抖动就可能节点(单个或者多个)掉出集群。从而集群出现red/yellow状态,理论情况下ES会自愈,但某些情况下可能非预期,此时就需要我们模拟各种case了,比如网络丢包。 操作 1…

postman Unable to load data as you are offline解决办法 重新登陆无效

postman Unable to load data as you are offline解决办法 重新登陆无效 也能如下一直打不开 - 重新登陆试过了 没有效果 - 刚刚代理切换到了全局,软件内的开关开启了 修改后

鸿蒙NEXT开发实战:【网络管理-数据请求】

概述 本示例仿postman输入API接口地址,获取相应数据,介绍数据请求接口的用法。 样例展示 基础信息 Http 介绍 本示例通过[ohos.net.http]等接口,实现了根据URL地址和相关配置项发起http请求的功能。 效果预览 首页结果页 使用说明 1.…

java上传本地文件到服务器共享

在Windows系统中,将本地文件夹中的某个文件上传到另一台Windows服务器电脑上,前提:两台电脑网络互通,要接收文件的Windows服务器文件夹开启了共享,可以被本机用如下方式进行写入和读取: 如何配置服务器共享请自行百度查找。 所需要的maven依赖如下: <dependency>…

备战蓝桥杯————二分查找(二)

引言 在上一篇博客中&#xff0c;我们深入探讨了二分搜索算法及其在寻找数组左侧边界的应用。二分搜索作为一种高效的查找方法&#xff0c;其核心思想在于通过不断缩小搜索范围来定位目标值。在本文中&#xff0c;我们将继续这一主题&#xff0c;不仅会回顾二分搜索的基本原理&…

leetcode 热题 100_最小覆盖子串

题解一&#xff1a; 双指针滑动窗口&#xff1a;暴力解法——用双指针来表示字符串s中的子串首尾&#xff0c;遍历所有子串并与字符串t判断是否符合条件。我们可以对遍历和判断的过程进行优化&#xff0c;首先是遍历&#xff0c;右指针先移动直到涵盖所以需要的字母&#xff0c…

弹性地基梁matlab有限元编程 | 双排桩支护结构 | Matlab源码 | 理论文本

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

记一次systemd服务启动找不到Java命令

首先systemd服务文件 /etc/systemd/system/test.service(文件简化处理了) [Unit] Descriptiontest Afternetwork.target [Service] ExecStart/opt/test/bin/test_start.sh [Install] WantedBymulti-user.target其中启动命令ExecStart指向的是一个sh启动脚本&#xff0c; 脚本内…

Zabbix(三)

监控Nginx服务 nginx配置 增加location{} [rootwenzi ~]#vim /etc/nginx/sites-enabled/defaultserver_name _; #_是通配符。服务器将响应任何域名的请求 ...location /status { stub_status;} ...访问 http://IP/status 即可 zabbix配置 Nginx by HTTP&#xff1a;无…

【重要公告】BSV区块链上线TypeScript SDK,未来将支持更多开发语言

​​发表时间&#xff1a;2024年2月21日 BSV区块链协会宣布上线JavaScript和TypeScript SDK&#xff08;即“标准开发工具包”&#xff09;。TypeScript SDK旨在为开发者提供新版统一核心代码库&#xff0c;以便利开发者在BSV区块链上开发能够任意扩容的应用程序。新上线的SDK替…

目标检测评估指标

目录 一、检测精度1、TP、FP、TN、FN概念正样本和负样本TP(True Positive---正确的正向预测)FP(False Positive---错误的正向预测&#xff09;FN(False Negative---错误的负向预测)TN(True Negative---正确的负向预测) 2、Precision(准确率)和Recall(召回率)3、P-R curve &…

C++STL【list链表】

list 1. list介绍 list文档&#xff08;非官方&#xff09; 官方文档list是双向带头循环链表&#xff0c;它可以在常数范围内的任意位置进行插入和删除操作。list的迭代器是双向迭代器(bidirectional iterator)&#xff0c;它可以前后双向迭代。 由容器的底层结构决定&#xf…

SQOOP安装与使用

SQOOP安装及使用 文章目录 SQOOP安装及使用SQOOP安装1、上传并解压2、修改配置文件3、修改环境变量4、添加MySQL连接驱动5、测试 准备MySQL数据登录MySQL数据库创建student数据库切换数据库并导入数据另外一种导入数据的方式使用Navicat运行SQL文件导出MySQL数据库 importMySQL…

HarmonyOS应用开发-环境搭建(windows环境)

官网地址&#xff1a;链接 DevEco Studio 3.1.1 Release&#xff1a;下载地址 1、安装DevEco Studio 直接安装即可 2、配置开发环境 1.运行已安装的DevEco Studio&#xff0c;首次使用&#xff0c;请选择Do not import settings&#xff0c;单击OK。 2.安装Node.js与ohpm。注…

【MySQL】索引优化与关联查询优化

数据库调优的几个维度&#xff1a; 索引失效&#xff0c;没有充分用到索引——索引建立关联查询太多JOIN——SQL优化服务器调优以及各个参数设置——调整my.cnf数据过多——分库分表 SQL查询优化的几种方式&#xff1a; 物理查询优化&#xff1a;通过索引以及表连接方式进行…

微服务分布式中为什么要分库分表呢?

什么是分库分表&#xff1f; 概念&#xff1a; 分库分表是一种数据库水平扩展的方法&#xff0c;通过将数据分散存储在多个数据库实例或多张表中&#xff0c;以提高系统的性能和扩展性。在Java应用中&#xff0c;可以使用一些数据库中间件或框架来实现分库分表。 为什么要分…

2024-3-6-数据库作业

作业&#xff1a;数据库操作的增、删、改完成 源代码&#xff1a; #include <myhead.h> void do_add(sqlite3 *ppDb) {char *errmsg NULL;char sql[128] "insert into Worker values(1001,小张,15000);";// "insert into Worker values(1002,小刘,900…

实验一 将调试集成到vscode

先唤起终端&#xff0c;按照上一篇文章的步骤分别启动调试服务器和调试客户端&#xff0c;然后挂在后台 PS&#xff1a;同时挂两个终端可以开两个窗口&#xff0c;也可以使用多窗口分屏式终端terminator 注意是要图二的光标一直闪&#xff0c;如果熄灭了说明连接超时了&#xf…