设计模式:真正的建造者模式

news2024/9/22 4:07:19

又臭又长的set方法

经常进行Java项目开发使用各类starter的你一定见过这种代码:

public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any())
                .build();
    }
}

相比较你的实体类的set方法,就感觉自己的set直观上就非常的low,十分不优雅,当然这里并不是指实体类,而是类似于一些接口调用时的参数dto对象的构造,在业务代码中构建参数实体十分的麻烦,例如:

@Data
public class DevParam {
    private Long id;
    private String filter;
    private String type;
    private int pageNo;
    private int pageSize;
}

public class HttpUtil {
    private final static URL = "http://127.0.0.1:8080/deviceserver/search"
    
    public static JSONObject searchDeviceData(DevParam param) {
        HttpClient client = HttpClient.newHttpClient();
        HttpRequest request = new HttpRequest();
        request.uri(URL);
        request.body(JSONUtil.parseObj(param))
        //……其他http调用,组装返回数据逻辑、省略
        return XXX  
    }
}


public class Test{

   public static void main(String[] args) {
     //需要调用接口获取数据
    DevParam param = new DevParam();
    param.setFilter("关键字");
    param.setPageNo(1);
    param.setPageSize(10);
    JSONObject retData = HttpUtil.searchDeviceData(param);
    JSONArray array = retData.getJSONArray("data");
    for(int i=0; i < array.size();i++){
      //……其他逻辑XXX
    }
   }

}

而如果写成这样,逼格就会高很多

public class Test{

   public static void main(String[] args) {
     //需要调用接口获取数据
    JSONObject retData = HttpUtil.searchDeviceData(new DevParam().filter("XXXX")  
                                                          .pageNo(1)
                                                          .pageSize(10));
    JSONArray array = retData.getJSONArray("data");
    for(int i=0; i < array.size();i++){
      //……其他逻辑XXX
    }
   }

}

实体类的改进:简易版的Builder模式

而上述的DevParam的实现大家应该一眼就能看出来,即将原本void的set方法修改一下就可以满足装X的条件:

public class DevParam {
    private Long id;
    private String filter;
    private String type;
    private int pageNo;
    private int pageSize;

    public DevParam() {
    }
    
    public DevParam setId(Long id) {
        this.id = id;
        return this;
    }

    public DevParam setFilter(String filter) {
        this.filter = filter;
        return this;
    }

    public DevParam setType(String type) {
        this.type = type;
        return this;
    }

    public DevParam setPageNo(int pageNo) {
        this.pageNo = pageNo;
        return this;
    }

    public DevParam setPageSize(int pageSize) {
        this.pageSize = pageSize;
        return this;
    }

   
}

实体类的改进:基于内部类的Builder实现

如果想看起来跟一开始的swgger的配置类一样高级,则可以使用内部静态类的方式实现该方法,即类内创建一个内部类,通过内部类创建外部类的实体对象。

public class DevParam {
    private Long id;
    private String filter;
    private String type;
    private int pageNo;
    private int pageSize;

    private DevParam() {
    }
    
    //省略此处的get和set方法

    public static class Builder {
        private Long id;
        private String filter;
        private String type;
        private int pageNo;
        private int pageSize;

        public Builder setId(Long id) {
            this.id = id;
            return this;
        }

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

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

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

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

        public DevParam build() {
            DevParam devParam = new DevParam();
            devParam.id = this.id;
            devParam.filter = this.filter;
            devParam.type = this.type;
            devParam.pageNo = this.pageNo;
            devParam.pageSize = this.pageSize;
            return devParam;
        }
    }
}

public class Test {
    public static void main(String[] args) {
        DevParam devParam = new DevParam.Builder()
                .setId(1L)
                .setFilter("filterValue")
                .setType("typeValue")
                .setPageNo(1)
                .setPageSize(10)
                .build();

        System.out.println(devParam);
    }
}

该种实现即是我们常用一些配置类、链接类创建时带build小尾巴的方式。

真正意义的建造者模式

其实虽然上述的DevParam类在构建时携带build,但是其实不是真正意义上的建造者模式(Builder),只能说是裁剪版的。建造者模式属于创建型模式,也就生成器模式,其核心思想是:

将复杂对象的构建和表示分离,使同样的构建过程可以创建不同的表示 

建造者模式的结构

Builder:生成器接口,定义创建一个产品(Product)对象所需的各个部件的操作。


Concrete Builder:具体的生成器实现,实现各个部件的创建,并负责组装产品对象的各个部件,同时还提供一个让用户获取组装完成后的产品对象的方法。
 

Director:指导者,也被称为导向者,主要用来使用Builder(Builder)接口,以一个统一的过程来构建所需要的Product(Product)对象。
 

Product:产品,表示被生成器构建的复杂对象,包含多个部件。

样例代码:

class Product {
    private Long id;
    private String filter;
    private String type;
    private int pageNo;
    private int pageSize;

    public Product(Long id, String filter, String type, int pageNo, int pageSize) {
        this.id = id;
        this.filter = filter;
        this.type = type;
        this.pageNo = pageNo;
        this.pageSize = pageSize;
    }
    public Long getId() {
        return id;
    }
    public String getFilter() {
        return filter;
    }
    public String getType() {
        return type;
    }
    public int getPageNo() {
        return pageNo;
    }
    public int getPageSize() {
        return pageSize;
    }
}

// 构建者接口
interface Builder {
    void setId(Long id);
    void setFilter(String filter);
    void setType(String type);
    void setPageNo(int pageNo);
    void setPageSize(int pageSize);
    Product getResultProduct();
}

// 具体构建者
class ConcreteBuilder implements Builder {
    private Long id;
    private String filter;
    private String type;
    private int pageNo;
    private int pageSize;

    @Override
    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public void setFilter(String filter) {
        this.filter = filter;
    }

    @Override
    public void setType(String type) {
        this.type = type;
    }

    @Override
    public void setPageNo(int pageNo) {
        this.pageNo = pageNo;
    }

    @Override
    public void setPageSize(int pageSize) {
        this.pageSize = pageSize;
    }

    @Override
    public Product getResultProduct() {
        return new Product(id, filter, type, pageNo, pageSize);
    }
}

// 指挥者
class Director {
    private Builder builder;
    public Director(Builder builder) {
        this.builder = builder;
    }
    public void construct() {
        builder.setId(1L);
        builder.setFilter("filterValue");
        builder.setType("typeValue");
        builder.setPageNo(1);
        builder.setPageSize(10);
    }
    public Product getResultProduct() {
        return builder.getResultProduct();
    }
}

public class Main {
    public static void main(String[] args) {
        Director director = new Director(new ConcreteBuilder());
        director.construct();

        Product product = director.getResultProduct();
        System.out.println(product);
    }
}

一个建造者经典案例

可能仅看上述建造者的demo代码,发现还不如简易版或者内部类版本的创建者,甚至都不如单纯的实体类set省事,但是先别急,我们带入一个实际的场景来看,以一个导出数据的案例来看,在做导出功能时,通常需要支持导出不同格式的数据文件,以此来做一个文件导出框架,通常对于导出框架,需要对导出内容和格式进行一个整体的约束:

  1. 导出的文件,不管什么格式,都分成3个部分,分别是文件头、文件体和文件尾。
  2. 在文件头部分,需要描述如下信息:分公司或门市点编号、导出数据的日期,对于文本格式,中间用逗号分隔。
  3. 在文件体部分,需要描述如下信息:表名称,然后分条描述数据。对于文本格式,表名称单独占一行,数据描述一行算一条数据,字段间用逗号分隔。
  4. 在文件尾部分,需要描述如下信息:输出人。

下面对以上需求基于建造者进行设计类图:

具体代码实现:

// 抽象构建者接口
interface Builder {
    void buildHeader();
    void buildBody(Map<String, Collection<ExportDataModel>> mapData);
    void buildFooter();
    StringBuffer getResult();
}

// 具体构建者:XML格式
class XmlBuilder implements Builder {
    private StringBuffer buffer;

    public XmlBuilder() {
        this.buffer = new StringBuffer();
    }

    @Override
    public void buildHeader() {
        buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
        buffer.append("<document>\n");
    }

    @Override
    public void buildBody(Map<String, Collection<ExportDataModel>> mapData) {
    buffer.append("<Body>\n");
    for (String tblName : mapData.keySet()) {
        // 先拼接表名称
        buffer.append(
            "\n<Datas TableName=\"" + tblName + "\">\n");
        // 然后循环拼接具体数据
        for (ExportDataModel edm : mapData.get(tblName)) {
            buffer.append("\n<Data>\n");
            buffer.append("\n<ProductId>" + edm.getProductId() + "</ProductId>\n");
            buffer.append("\n<Price>" + edm.getPrice() + "</Price>\n");
            buffer.append("\n<Amount>" + edm.getAmount() + "</Amount>\n");
            buffer.append("\n</Data>\n");
        }
        buffer.append("\n</Datas>\n");
    }
    buffer.append("</Body>\n");
}

    @Override
    public void buildFooter() {
        buffer.append("</document>\n");
    }

    @Override
    public StringBuffer getResult() {
        return buffer;
    }
}

// 具体构建者:TXT格式
class TxtBuilder implements Builder {
    private StringBuffer buffer;

    public TxtBuilder() {
        this.buffer = new StringBuffer();
    }

    @Override
    public void buildHeader() {
        buffer.append("Document Content:\n");
    }

    @Override
    public void buildBody(
    Map<String, Collection<ExportDataModel>> mapData) {
        for (String tblName : mapData.keySet()) {
            // 先拼接表名称
            buffer.append(tblName + "\n");
            // 然后循环拼接具体数据
            for (ExportDataModel edm : mapData.get(tblName)) {
                buffer.append(edm.getProductid() + ","
                    + edm.getPrice() + ","
                    + edm.getAmount() + "\n");
            }
        }
     }

    @Override
    public void buildFooter() {
        buffer.append("End of Document\n");
    }

    @Override
    public StringBuffer getResult() {
        return buffer;
    }
}

// 指挥者
class Director {
    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    public void construct() {
        builder.buildHeader();
        builder.buildBody();
        builder.buildFooter();
    }

    public StringBuffer getResult() {
        return builder.getResult();
    }
}

// 客户端使用示例
public class BuilderPatternDemo {
    public static void main(String[] args) {
        Director director;

        // 创建XML格式的构建者
        director = new Director(new XmlBuilder());
        director.construct();
        System.out.println("XML Format:\n" + director.getResult());

        // 创建TXT格式的构建者
        director = new Director(new TxtBuilder());
        director.construct();
        System.out.println("TXT Format:\n" + director.getResult());
    }
}

首先通过builder定义了构建文档所需的方法buildHeaderbuildBodybuildFooter 和 getResult,以两个类分别根据构建的文件特征实现了Builder接口;最后通过指导者使用Builder来构建文档,并在构建完成后获取最终的文档内容。

建造者模式的理解

建造者模式的主要功能是构建复杂的产品,而且是细化的、分步骤的构建产品,也就是建造者模式重在一步一步解决构造复杂对象的问题。如果仅仅这么认识建造者模式的功能是不够的。更为重要的是,这个构建的过程是统一的、固定不变的,变化的部分放到生成器部分了,只要配置不同的生成器,那么同样的构建过程,就能构建出不同的产品来。

再直白点说,建造者模式的重心在于分离构建算法和具体的构造实现,从而使得构建算法可以重用。具体的构造实现可以很方便地扩展和切换,从而可以灵活地组合来构造出不同的产品对象。

建造者模式的构成

要特别注意,建造者模式分成两个很重要的部分。

  1. 一个部分是 Builder 接口,这里是定义了如何构建各个部件,也就是知道每个部件功能如何实现,以及如何装配这些部件到产品中去;
  2. 另外一个部分是 Director,Director 是知道如何组合来构建产品,也就是说Director负责整体的构建算法,而且通常是分步骤地来执行。

不管如何变化,Builder 模式都存在这么两个部分,一个部分是部件构造和产品装配,另一个部分是整体构建的算法。认识这点是很重要的,因为在生成器模式中,强调的是固定整体构建的算法,而灵活扩展和切换部件的具体构造和产品装配的方式,所以要严格区分这两个部分。

在指导者(Director)实现整体构建算法时,遇到需要创建和组合的具体部件的时候,就会把这些功能通过委托,交给Builder去完成。

建造者模式的本质

建造者模式的本质是分离整体构建算法和部件构造。构建一个复杂的对象,本来就有构建的过程,以及构建过程中具体的实现。建造者模式就是用来分离这两个部分,从而使得程序结构更松散、扩展更容易、复用性更好,同时也会使得代码更清晰,意图更明确。
虽然在建造者模式的整体构建算法中,会一步一步引导Builder来构建对象,但这并不是说生成器主要就是用来实现分步骤构建对象的。建造者模式的重心还是在于分离整体构建算法和部件构造,而分步骤构建对象不过是整体构建算法的一个简单表现,或者说是一个附带产物。

何时使用建造者模式

  • 如果创建对象的算法,应该独立于该对象的组成部分以及它们的装配方式时。
  • 如果同一个构建过程有着不同的表示时(例如导出案例)。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

文章解读与仿真程序复现思路——电力自动化设备EI\CSCD\北大核心《考虑光热集热单元的氢储能热电联供综合能源系统容量优化配置》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

【Access、Trunk和Hybrid】

概述 Access类型的端口只能属于1个VLAN&#xff0c;一般用于连接计算机的端口&#xff1b;Trunk类型的端口可以允许多个VLAN通过&#xff0c;可以接收和发送多个VLAN的报文&#xff0c;一般用于交换机之间连接的端口&#xff1b;Hybrid类型的端口可以允许多个VLAN通过&#xf…

Java中的迭代器(Iterator)

Java中的迭代器&#xff08;Iterator&#xff09; 1、 迭代器的基本方法2、 迭代器的使用示例3、注意事项4、克隆与序列化5、结论 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 在Java中&#xff0c;迭代器&#xff08;Iterator&#xff0…

1w字图文带你了解sqlmap,从0到1,WAF绕过,高级用法一文通透

前言 在信息安全领域&#xff0c;SQL注入攻击是一种极为常见且危害严重的安全漏洞。攻击者利用Web应用程序对SQL查询的不当处理&#xff0c;通过注入恶意SQL代码&#xff0c;从而绕过安全措施&#xff0c;非法访问或篡改数据库中的数据。随着网络安全威胁的日益严峻&#xff0…

在学习使用LabVIEW的过程中,需要注意哪些问题?

在学习使用LabVIEW的过程中&#xff0c;需要注意以下问题&#xff1a; 1. 基础知识 图形化编程思维&#xff1a; LabVIEW采用图形化编程方式&#xff0c;与传统的文本编程语言有很大不同&#xff0c;需要适应这种新的编程思维方式。数据流概念&#xff1a; 理解LabVIEW的核心数…

linux进程——概念理解与PCB

前言&#xff1a;本篇讲解Linux进程概念相关内容。 操作系统被称为计算机世界的哲学&#xff0c; 可以见得操作系统的知识并不好理解。 对于这篇进程概念的讲解&#xff0c; 博主认为&#xff0c; 如果没有一些前置知识的话&#xff0c;里面的有些概念并不好理解。 但是如果学习…

【机器学习实战】Datawhale夏令营2:深度学习回顾

#DataWhale夏令营 #ai夏令营 文章目录 1. 深度学习的定义1.1 深度学习&#xff06;图神经网络1.2 机器学习和深度学习的关系 2. 深度学习的训练流程2.1 数学基础2.1.1 梯度下降法基本原理数学表达步骤学习率 α梯度下降的变体 2.1.2 神经网络与矩阵网络结构表示前向传播激活函数…

人工智能未来发展前景将会怎样?

当我们探讨人工智能未来的发展前景时&#xff0c;可以从多个角度来详细说明其可能的影响和趋势&#xff1a; 技术进步与应用扩展 1.深度学习与机器学习&#xff1a; 进一步优化和算法进展&#xff1a;深度学习已经取得了巨大成就&#xff0c;但仍面临挑战&#xff0c;如对小数…

AI伦理议题:从隐私保护到算法公平

文章目录 &#x1f34a;1 人工智能兴起背后的伦理及道德风险1.1 算法偏见与歧视1.2 数据隐私侵权1.3 透明度受限1.4 决策失衡1.5 AI生成内容的危险性 &#x1f34a;2 建构AIGC伦理观&#xff1a;实现人机共创的永续提升2.1 技术手段与伦理预防2.2 即时警告与紧急关停措施2.3 法…

Xcode进行真机测试时总是断连,如何解决?

嗨。大家好&#xff0c;我是兰若姐姐。最近我在用真机进行app自动化测试的时候&#xff0c;经常会遇到xcode和手机断连&#xff0c;每次断连之后需要重新连接&#xff0c;每次断开都会出现以下截图的报错 当这种情况出现时&#xff0c;之前执行的用例就相当于白执行了&#xff…

vue视频、图片自动轮播并伴随进度条

废话不多说直接上代 多余没用的部分自己看着删除 <template><div class"showImg"><el-carousel ref"carousel" trigger"hover" :autoplay"false" class"dimControl" :height"${(currenInnerWith*0.37…

JavaScript:移除元素

这是原题&#xff1a;给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 假设 nums 中不等于 val 的元素数量为 k&#xff0c;要通过此题&#xff0c;您需要执行以下操…

sourcrinlight 4.0 的使用技巧:如何在文件名后省略路径名

如图&#xff1a; 如果路径名很长&#xff0c;将显示不了几个文件名的&#xff0c;会造成一些不便。如何隐藏文件的路径名呢&#xff1f; 选中或取消这个按钮&#xff1a; 就可以了。要想再查看文件路径&#xff0c;鼠标放上去&#xff0c;就会显示了&#xff1a; 谢谢

数据融合工具(11)悬挂点自动检测和修复

一、需求背景 GIS数据通常需要满足拓扑规则&#xff0c;即点、线和面之间的拓扑关系应该正确。 悬挂点是指在地图数据中孤立存在的点&#xff0c;它们不与任何线或面的节点相连接。悬挂点通常是数据中的异常情况&#xff0c;可能是由于数字化或数据编辑错误而导致的。 下图以橙…

【关闭个人数据跨境传输】

现象 &#xff1a;WIN10更新补丁重启就有了这&#xff1b; 解决办法&#xff1a; 第一步&#xff1a;按shiftF10 第二步&#xff1a;按winR 第三步&#xff1a;输入taskmgr&#xff0c;回车 第四步&#xff1a;结束Microsoft账户进程&#xff0c;就可以进入桌面&#xff1…

Android中OkHttp3中超时时间概述

目录 前言connectTimeoutreadTimeoutwriteTimeoutcallTimeoutpingInterval拓展 前言 可以看到&#xff0c;使用还是很简单的。主要相关的有这五个参数&#xff0c;其中我们常用到是就是connectTimeout、readTimeout和writeTimeout。 再看上图&#xff0c;可以看到默认下connec…

matlab小白入门的基本使用

一.基本运算 加&#xff1a;a55 减&#xff1a;a5-2 立方&#xff1a;a2^3 乘&#xff1a;a2*3 ans默认变量名&#xff0c;应答最近依次操作运算结果eps浮点数的相对误差i,j虚数单位&#xff0c;定义-1inf代表无穷大NaN代表不定值&#xff08;不是数字&#xff09;pi圆周率…

【内网安全】横向移动-Wmi-Smb-CME密码喷射

目录 环境介绍域信息收集-横向移动前置判断是不是在域内获取域控主机的内网ip端口扫描内网获取主机密码 域横向移动-WMI-自带&命令&套件&插件1.wmic系统自带&#xff1a;(单执行&#xff1a;即无回显) 2.cscript系统自带&#xff1a;(交互式) 3.wmiexec-impacket&a…

[RuoYi-Vue] - 6. 若依二次开发

文章目录 &#x1f333;1. 模块定制(修改包名)1.1 若依框架修改器1.2 使用示例 &#x1f33f;2. 新建业务模块2.1 新建子模块2.2 版本锁定2.3 添加模块依赖 &#x1f33e;3. 菜品管理开发3.1 准备SQL并导入数据库3.2 配置代码生成信息3.3 下载代码并导入项目 &#x1f343;4. 通…

【Linux】深入了解`rm`命令:删除文件与目录的终极指南

文章目录 一、rm命令概述二、rm命令的基本用法三、rm命令的常用选项1. -i&#xff1a;交互式删除2. -f&#xff1a;强制删除3. -r或-R&#xff1a;递归删除目录4. -v&#xff1a;详细模式 四、rm命令的高级用法1. 结合其他命令使用2. 删除空目录 五、rm命令的注意事项 在Linux操…