[设计模式] 建造者模式

一、引言

起因是学习okhttp过程中遇到的这段代码

        Request request = original.newBuilder()
                .url(original.url())
                .header("Authorization", "Bearer " + BearerTokenUtils.getToken(configuration.getApiKey(), configuration.getApiSecret()))
                .header("Content-Type", Configuration.JSON_CONTENT_TYPE)
                .header("User-Agent", Configuration.DEFAULT_USER_AGENT)
                .header("Accept", Configuration.SSE_CONTENT_TYPE)
                .method(original.method(), original.body())
                .build();

newBuilder().build()创建对象的方式以前没注意过,搜了一下,这种方法属于广义上的建造者模式,于是进行学习。

二、介绍

我自己概括建造者模式的思想:把一个整体A,分割成多个部分A1,A2,A3,A4。每个部分分开建造,最后组装成一个整体。

设想这样的场景,装修一个房子,需要考虑吊顶、涂料、地板、瓷砖等部分。每个部分都有不同的品牌选择。

装修公司提供一些成套的、整体装修方案,可以看作是这些部分的排列组合。

三、代码

我们用代码进行描述。以下是代码结构

模块builder-pattern-01中,Matter是材料的父接口,定义了材料的各种属性。

public interface Matter {
    /**
     * 场景:ceiling、coat、floor、tile
     * @return 吊顶、涂料、地板、瓷砖
     */
    String scene();

    /**
     * 品牌
     * @return 自定字符串
     */
    String brand();

    /**
     * 型号
     * @return 自定义字符串
     */
    String model();

    /**
     * 单价(平米报价)
     * @return BigDecimal
     */
    BigDecimal price();

    /**
     * 描述
     * @return 自定义字符串
     */
    String desc();
}

 ceiling、coat、floor、tile分别表示四个部分:吊顶、涂料、地板、瓷砖。

这四个部分有不同的材料。这些材料组合装修,成为一个整体方案。代码略。

builder-pattern-02中,IMenu是装修包接口,用于添加材料;DecoratiuonPackageMenu是其实现。
 

/**
 * 装修包接口
 * 用于添加材料
 */
public interface IMenu {
    /**
     * 添加吊顶
     * @param matter 材料
     * @return 装修包
     */
    IMenu appendCeiling(Matter matter);

    /**
     * 添加涂料
     * @param matter 材料
     * @return 装修包
     */
    IMenu appendCoat(Matter matter);

    /**
     * 添加地板
     * @param matter 材料
     * @return 装修包
     */
    IMenu appendFloor(Matter matter);

    /**
     * 添加瓷砖
     * @param matter 材料
     * @return 装修包
     */
    IMenu appendTile(Matter matter);

    /**
     * 获取装修包信息
     * @return 装修包detail
     */
    String getDetail();
}
public class DecorationPackageMenu implements IMenu{

    /** 材料清单 */
    private List<Matter> list = new ArrayList<>(16);
    /** 总价格 */
    private BigDecimal price = BigDecimal.ZERO;
    /** 面积 */
    private BigDecimal area;
    /** 装修等级 */
    private String grade;

    public DecorationPackageMenu(){}
    public DecorationPackageMenu(double area, String grade) {
        this.area = new BigDecimal(area);
        this.grade = grade;
    }

    @Override
    public IMenu appendCeiling(Matter matter) {
        list.add(matter);
        // 注意这个price=赋值操作,单纯的add等运算不会改变原来的值
        // price = price + ( area * (unitPrice * matter.price()) )
        price = price.add(area.multiply(matter.price()));
        return this;
    }

    @Override
    public IMenu appendCoat(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(matter.price()));
        return this;
    }

    @Override
    public IMenu appendFloor(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(matter.price()));
        return this;
    }

    @Override
    public IMenu appendTile(Matter matter) {
        list.add(matter);
        price = price.add(area.multiply(matter.price()));
        return this;
    }

    @Override
    public String getDetail() {
        StringBuilder detail = new StringBuilder("\r\n-------------------------------------------------------\r\n" +
                "装修清单: " + "\r\n" +
                "套餐等级: " + grade + "\r\n" +
                "套餐价格: " + price.setScale(2, BigDecimal.ROUND_HALF_UP) + "元\r\n" +
                "房屋面积: " + area.doubleValue() + "平方米\r\n" +

                "材料清单: " + "\r\n" );
        for (Matter matter : list) {
            detail.append("场景:").append(matter.scene()).append("、")
                    .append("品牌:").append(matter.brand()).append("、")
                    .append("类别:").append(matter.model()).append("、")
                    .append("平米价格:").append(matter.price()).append("元\r\n");
        }
        return detail.toString();
    }
}

builder是建造者,使用IMenu装修包对各种材料进行组装

public class Builder {
    public IMenu levelOne(Double area) {
        return new DecorationPackageMenu(area, "豪华欧式")
                .appendCeiling(new LevelOneCeiling())
                .appendCoat(new DuluxCoat())
                .appendFloor(new DerFloor())
                .appendTile(new DongPengTile());
    }

    public IMenu levelTwo(Double area) {
        return new DecorationPackageMenu(area, "轻奢田园")
                .appendCeiling(new LevelTwoCeiling())
                .appendCoat(new NipponCoat())
                .appendFloor(new ShengXiangFloor())
                .appendTile(new MarcoPoloTile());
    }

    public IMenu levelThree(Double area) {
        return new DecorationPackageMenu(area, "现代简约")
                .appendCeiling(new LevelOneCeiling())
                .appendCoat(new NipponCoat())
                .appendFloor(new ShengXiangFloor())
                .appendTile(new DongPengTile());
    }
}

我们使用单元测试进行测试一下

    @Test
    public void test_builder() {
        Builder builder = new Builder();
        System.out.println(builder.levelOne(132.5D).getDetail());
        System.out.println(builder.levelTwo(150.0D).getDetail());
        System.out.println(builder.levelThree(170.0D).getDetail());
    }

结果部分为
 

其余略

四、回归okhttp

建造者模式思想基本领略,那么okhttp的newBuilder().build()创建对象的思想跟上面差不多。

那么对于这段代码,

        Request request = original.newBuilder()
                .url(original.url())
                .header("Authorization", "Bearer " + BearerTokenUtils.getToken(configuration.getApiKey(), configuration.getApiSecret()))
                .header("Content-Type", Configuration.JSON_CONTENT_TYPE)
                .header("User-Agent", Configuration.DEFAULT_USER_AGENT)
                .header("Accept", Configuration.SSE_CONTENT_TYPE)
                .method(original.method(), original.body())
                .build();

我们可以进行学习newBuilder().build()这种创建对象的方式,先来看看其源码

public Builder newBuilder() {
        return new Builder(this);
    }

public Request build() {
            if (this.url == null) {
                throw new IllegalStateException("url == null");
            } else {
                return new Request(this);
            }
        }

public Builder url(HttpUrl url) {
            if (url == null) {
                throw new NullPointerException("url == null");
            } else {
                this.url = url;
                return this;
            }
        }

public Builder header(String name, String value) {
            this.headers.set(name, value);
            return this;
        }

public Builder method(String method, @Nullable RequestBody body) {
            if (method == null) {
                throw new NullPointerException("method == null");
            } else if (method.length() == 0) {
                throw new IllegalArgumentException("method.length() == 0");
            } else if (body != null && !HttpMethod.permitsRequestBody(method)) {
                throw new IllegalArgumentException("method " + method + " must not have a request body.");
            } else if (body == null && HttpMethod.requiresRequestBody(method)) {
                throw new IllegalArgumentException("method " + method + " must have a request body.");
            } else {
                this.method = method;
                this.body = body;
                return this;
            }
        }

通过newBuilder()创建了一个Builder类的对象,然后每次url() header()等方法修改属性,都是在对Builder类的对象进行修改,最后build()方法 通过Builder类的对象创建了Request对象。

这种基于不可变对象(或者为了不改变原对象)而创建新对象的方式也值得我们学习。

基于此思想,创建一个类Message

@Data
public class Message implements Serializable {
    private String role;
    private String content;
    private String name;

    public Message(){}
    public Message(Builder builder) {
        this.role = builder.role;
        this.content = builder.content;
        this.name = builder.name;
    }

    /**
     * 注意这里 static方法
     */
    public static Builder builder() {
        return new Builder();
    }

    /**
     * 建造者模式
     */
    public static final class Builder {
        private String role;
        private String content;
        private String name;
        public Builder(){}
        public Builder role(Constants.Role role) {
            this.role = role.getCode();
            return this;
        }
        public Builder content(String content) {
            this.content = content;
            return this;
        }
        public Builder name(String name) {
            this.name = name;
            return this;
        }
        public Message build() {
            return new Message(this);
        }
    }
}

得注意build()方法是在最后使用的,所以应该是Builder的方法,并且返回值类型应该是Message类。

补充,后来才发现lombok有@Builder注解,就是这个方法......

@Builder

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

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

相关文章

《第三期(先导课)》之《Python 开发环境搭建》

文章目录 《第 1 节 初始Python》《第 6 节 pip包管理工具》 《第 1 节 初始Python》 。。。 《第 6 节 pip包管理工具》 pip是Python的包管理工具,用于安装、升级和管理Python包。 pip是Python标准库之外的一个第三方工具,可以从Python Package Index(PyPI)下载和安装各种P…

在PostgreSQL中创建和管理数据库

PostgreSQL是一个强大、开源的关系型数据库管理系统&#xff0c;它提供了丰富的功能和灵活的配置选项&#xff0c;使得它成为许多开发者和组织的首选数据库之一&#xff0c;接下来我会介绍如何在PostgreSQL中创建和管理数据库。 一、安装和配置PostgreSQL 第一步&#xff0c;…

嵌软工程师要掌握的硬件知识2:一文看懂什么是开漏和推挽电路(open-drain / push-pull)

文 / 黑猫学长 本文根据笔者个人工作/学习经验整理而成,如有错误请留言。 文章为付费内容,已加入原创侵权保护,禁止私自转载及抄袭。 文章所在专栏: 嵌软工程师要掌握的硬件知识 1 推挽(push pull)电路 1.1 理解什么是推挽电路 - 详细介绍 如图所示,Q3是个NPN型三极管…

【mysql】CommunicationsException: Communications link failure

CommunicationsException: Communications link failure The last packet sent successfully to the server was 0 milliseconds ago. The driver has not received any packets from the server. 通信异常&#xff1a;通信链路故障 最后一个成功发送到服务器的数据包是0毫秒前…

【Unity实战】实现强大通用易扩展的对话系统(附项目源码)

先看看实现的最终效果 前言 之前的对话系统因为存在一些错误和原作者不允许我分享&#xff0c;所以被我下架了&#xff0c;而且之前对话系统确实少了一些功能&#xff0c;比如最基本的逐字打印功能&#xff0c;原本来是打算后面补充的。 对话系统在游戏中实现太常见了&#x…

基于 Python 的课程助教智能聊天机器人

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 Wechat / QQ 名片 :) 1. 项目简介 课程助教是高校中一种常见的教学模式,其在学生理论知识的掌握与实践能力的提高方面起到关键性的作用,已经成为高校日常教育环节中不可或缺的一环。然而,传统的人力助教有若干关键问题亟待…

Leangoo领歌免费Scrum管理工具中如何看到关于自己的所有任务?

个人工作台 个人工作台是个人最新待办工作的展示区域&#xff0c;它展示了个人所有的待办任务&#xff0c;最新访问的项目和工作动态&#xff0c;当一个人在多个项目和看板上工作时&#xff0c;它可以帮助个人快速看到个人在各个项目的工作&#xff0c;快速进入任务看板处理任…

Flink集群的搭建

1、Flink独立集群模式 1、首先Flink的独立集群模式是不依赖于Hadoop集群。 2、上传压缩包&#xff0c;配置环境&#xff1a; 1、解压&#xff1a; tar -zxvf flink-1.15.2-bin-scala_2.12.tgz2、配置环境变量&#xff1a;vim /etc/profileexport FLINK_HOME/usr/local/soft/fl…

3、FFmpeg基础

1、FFmpeg 介绍 FFmpeg是一套可以用来记录、转换数字音频、视频&#xff0c;并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库。 2、FFmpeg 组成 - libavformat&#xff1a;用于…

【理解链表指针赋值】链表中cur->next = cur->next->next->next与cur =cur->next->next的区别

最近在做链表的题目的时候&#xff0c;对于所定义的cur链表指针产生了一些疑惑&#xff0c;查阅资料后整理一下我的理解&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode(int x) : val(x), next(n…

AI创作系统ChatGPT商业运营系统源码+支持GPT4/支持ai绘画

一、AI创作系统 SparkAi创作系统是基于OpenAI很火的ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如…

工程(十二)Ubuntu20.04LSD_SLAM运行

博主创建了一个科研互助群Q&#xff1a;772356582&#xff0c;欢迎大家加入讨论。这是一个科研互助群&#xff0c;主要围绕机器人&#xff0c;无人驾驶&#xff0c;无人机方面的感知定位&#xff0c;决策规划&#xff0c;以及论文发表经验&#xff0c;以方便大家很好很快的科研…

CSS 显示、定位、布局、浮动

一、CSS 显示&#xff1a; CSS display属性设置元素应如何显示&#xff1b;CSS visibility属性指定元素应可见还是隐藏。隐藏元素可以通过display属性设置为“none”&#xff0c;也可以通过visibility属性设置为“hidden”。两者的区别&#xff1a;visibility:hidden可以隐藏某…

王道p40 1.设计一个递归算法,递归删除单链表L中所有值为x的结点(c语言)

视频讲解在这里&#xff1a;&#x1f447; p40 第1题 王道数据结构课后代码题c语言代码实现_哔哩哔哩_bilibili 本题代码如下 void delete(linklist* L,int x)//递归删除x {if((*L)->next! NULL){if ((*L)->next->data x)//找到x{lnode* p (*L)->next;(*L)-&…

【Springboot】Vue3-Springboot引入JWT实现登录校验以及常见的错误解决方案

文章目录 前言一、JWT简单介绍二、token校验设计思路三、使用步骤Springboot部署JWT引入依赖&#xff1a;创建登录实体类后端&#xff1a;LoginController.java路由守卫函数 四、问题 前言 项目版本&#xff1a; 后端&#xff1a; Springboot 2.7、 Mybatis-plus、Maven 3.8.1…

网络测试工具—— iperf2 安卓APK 下载 及简单使用

网络测试工具—— iperf2 安卓APK 下载 及简单使用 前言一、iperf2是什么&#xff1f;二、使用步骤附上help中命令截图翻译总结 前言 项目上有一款安卓车机加载局域网图片加载非常慢&#xff0c;所以需要测试一个安卓车机设备的带宽&#xff0c;经过调研后使用到了iperf2。 一…

项目管理之如何出道(中)

昨日立冬&#xff0c;各位盆友&#xff0c;有没有吃饺子&#xff1f; 冬天来了&#xff0c;寒冷未约而至&#xff0c;冬雪侵袭北国。未知的变化总能让人产生恐慌和无措&#xff0c;就像行走在荒岛小路&#xff0c;前面遇到的究竟是迷人之景&#xff1f;还是饿狼之瞟&#xff1f…

52基于MATLAB的希尔伯特Hilbert变换求包络谱

基于MATLAB的希尔伯特Hilbert变换求包络谱&#xff0c;对原始信号进行初步滤波&#xff0c;之后进行包络谱分析。可替换自己的数据进行优化。程序已调通&#xff0c;可直接运行。 52的尔伯特Hilbert变换包络谱 (xiaohongshu.com)

计算机三级四级嵌入式备战经验

2023年9月23日于东北大学考完三四级 大四的时候时间比较多&#xff0c;因为本科学了一点嵌入式的知识&#xff0c;研究生又用不到&#xff0c;所以想着考个证金盆洗手。 三级考的是一本书&#xff0c;更多涉及到S3C2410这个芯片&#xff1b;四级考的是两本书&#xff1a;《操作…

故障注入测试目的及方法

在软件开发的复杂环境中&#xff0c;保证应用程序的鲁棒性和稳定性是至关重要的。故障注入测试是一种专门设计用于模拟和评估系统对故障的响应能力的测试方法。通过主动引入故障并观察系统的行为&#xff0c;开发者可以更全面地了解系统在面临异常情况时的表现。 一、故障注入测…