【Java设计模式】建造者模式 注解@Builder

news2024/12/25 23:52:25

概念

  • 将一个复杂对象的构造与它的表示分离,使同样的构建过程可以创建不同的表示。它使将一个复杂的对象分解成多个简单的对象,然后一步步构建而成。

  • 每一个具体建造者都相对独立,而与其它的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象。由于指挥者类针对抽象建造者编程,增加新的具体建造者无须修改原有类库的代码,系统扩展方便,符合“开闭原则”。

未用建造者模式

  • 以下举个最简单的例子:电脑配件(包括品牌、价格、描述)、组装电脑。

电脑接口

/**
 * 电脑接口
 */
public interface Computer {

    /**
     * 组件(主机Host、显示器Monitor、鼠标Mouse、键盘Keyboard)
     */
    String parts();

    /**
     * 品牌
     */
    String brand();

    /**
     * 价格
     */
    Double price();

    /**
     * 描述
     */
    String desc();
}

主机Host

/**
 * 惠普主机
 */
public class HPHost implements Computer {

    @Override
    public String parts() {
        return "惠普主机";
    }

    @Override
    public String brand() {
        return "惠普品牌";
    }

    @Override
    public Double price() {
        return 6999.00;
    }

    @Override
    public String desc() {
        return "HP Computer Welcome";
    }
}

/**
 * 联想主机
 */
public class LenovoHost implements Computer {

    @Override
    public String parts() {
        return "联想主机";
    }

    @Override
    public String brand() {
        return "联想品牌";
    }

    @Override
    public Double price() {
        return 6899.00;
    }

    @Override
    public String desc() {
        return "Lenovo Computer Welcome";
    }
}

显示器Monitor

/**
 * 小米显示器
 */
public class RedmiMonitor implements Computer {

    @Override
    public String parts() {
        return "小米显示器";
    }

    @Override
    public String brand() {
        return "小米品牌";
    }

    @Override
    public Double price() {
        return 1399.00;
    }

    @Override
    public String desc() {
        return "Redmi Monitor Welcome";
    }
}

/**
 * 华硕显示器
 */
public class ROGMonitor implements Computer {

    @Override
    public String parts() {
        return "华硕显示器";
    }

    @Override
    public String brand() {
        return "华硕品牌";
    }

    @Override
    public Double price() {
        return 1899.00;
    }

    @Override
    public String desc() {
        return "ROG Monitor Welcome";
    }
}

鼠标Monse

/**
 * 罗技鼠标
 */
public class GMouse implements Computer {

    @Override
    public String parts() {
        return "罗技鼠标";
    }

    @Override
    public String brand() {
        return "罗技品牌";
    }

    @Override
    public Double price() {
        return 139.00;
    }

    @Override
    public String desc() {
        return "G Mouse Welcome";
    }
}

/**
 * 联想鼠标
 */
public class LenovoMouse implements Computer {

    @Override
    public String parts() {
        return "联想鼠标";
    }

    @Override
    public String brand() {
        return "联想品牌";
    }

    @Override
    public Double price() {
        return 89.00;
    }

    @Override
    public String desc() {
        return "Lenovo Mouse Welcome";
    }
}

键盘Keyboard

/**
 * 罗技键盘
 */
public class GKeyboard implements Computer {

    @Override
    public String parts() {
        return "罗技键盘";
    }

    @Override
    public String brand() {
        return "罗技品牌";
    }

    @Override
    public Double price() {
        return 239.00;
    }

    @Override
    public String desc() {
        return "G Keyboard Welcome";
    }
}

/**
 * 惠普键盘
 */
public class HPKeyboard implements Computer {

    @Override
    public String parts() {
        return "惠普键盘";
    }

    @Override
    public String brand() {
        return "惠普品牌";
    }

    @Override
    public Double price() {
        return 89.00;
    }

    @Override
    public String desc() {
        return "HP Keyboard Welcome";
    }
}

组装电脑

**
 * 组装电脑
 * 不同的套装配不同的设备
 */
public class PackageComputer {

    /**
     * 根据套餐数字对应返回整套电脑配置详情
     *
     * @param choose 套餐数字
     * @return 电脑配置
     */
    public String getComputer(Integer choose) {
        // 价格初始值
        double price;
        // 组装电脑配件
        List<Computer> parts = new ArrayList<>();

        StringBuilder stringBuilder = new StringBuilder();

        if(choose == 1) {
            HPHost hpHost = new HPHost();
            RedmiMonitor redmiMonitor = new RedmiMonitor();
            LenovoMouse lenovoMouse = new LenovoMouse();
            HPKeyboard hpKeyboard = new HPKeyboard();

            // 组装电脑
            parts.add(hpHost);
            parts.add(redmiMonitor);
            parts.add(lenovoMouse);
            parts.add(hpKeyboard);

            // 计算价格
            price = hpHost.price() + redmiMonitor.price() + lenovoMouse.price() + hpKeyboard.price();

            stringBuilder.append("套餐为:" + choose + "号套餐\r\n");
            stringBuilder.append("配件如下:\r\n");
            for(Computer c : parts) {
                stringBuilder.append(c.parts() + "、");
                stringBuilder.append(c.brand() + "、");
                stringBuilder.append(c.price() + "、");
                stringBuilder.append(c.desc() + "\r\n");
            }
            stringBuilder.append("总价格为:" + price + "RMB\r\n");
        } else if(choose == 2) {
            LenovoHost lenovoHost = new LenovoHost();
            ROGMonitor rogMonitor = new ROGMonitor();
            GMouse gMouse = new GMouse();
            GKeyboard gKeyboard = new GKeyboard();

            // 组装电脑
            parts.add(lenovoHost);
            parts.add(rogMonitor);
            parts.add(gMouse);
            parts.add(gKeyboard);

            // 计算价格
            price = lenovoHost.price() + rogMonitor.price() + gMouse.price() + gKeyboard.price();

            stringBuilder.append("套餐为:" + choose + "号套餐\r\n");
            stringBuilder.append("配件如下:\r\n");
            for(Computer c : parts) {
                stringBuilder.append(c.parts() + "、");
                stringBuilder.append(c.brand() + "、");
                stringBuilder.append(c.price() + "、");
                stringBuilder.append(c.desc() + "\r\n");
            }
            stringBuilder.append("总价格为:" + price + "RMB\r\n");
        } else if(choose == 3) {
            LenovoHost lenovoHost = new LenovoHost();
            RedmiMonitor redmiMonitor = new RedmiMonitor();
            GMouse gMouse = new GMouse();
            LenovoMouse lenovoMouse = new LenovoMouse();

            // 组装电脑
            parts.add(lenovoHost);
            parts.add(redmiMonitor);
            parts.add(gMouse);
            parts.add(lenovoMouse);

            // 计算价格
            price = lenovoHost.price() + redmiMonitor.price() + gMouse.price() + lenovoMouse.price();

            stringBuilder.append("套餐为:" + choose + "号套餐\r\n");
            stringBuilder.append("配件如下:\r\n");
            for(Computer c : parts) {
                stringBuilder.append(c.parts() + "、");
                stringBuilder.append(c.brand() + "、");
                stringBuilder.append(c.price() + "、");
                stringBuilder.append(c.desc() + "\r\n");
            }
            stringBuilder.append("总价格为:" + price + "RMB\r\n");
        }
        return stringBuilder.toString();
    }
}

测试

public class BuilderDesign {
    public static void main(String[] args) {
        PackageComputer computer = new PackageComputer();
        System.out.println(computer.getComputer(1));
        System.out.println("=======================================================");
        System.out.println(computer.getComputer(2));
        System.out.println("=======================================================");
        System.out.println(computer.getComputer(3));
    }
}

使用建造者模式

  • 从上面可以看出来,电脑的每个配件都要去建对应的类。例子中我给了主机、显示器、鼠标、键盘四种部件,每个部件假设两种品牌,就写了 2 * 4 = 8个类。虽说不会是指数型增长,但是无论哪个增加都会是很明显的增长趋势。而且在组装电脑时,要根据每个不同要求的去返回对应的信息,每一个if语句都有二十行代码左右,看起来十分臃肿。

  • 接下来将会用到建造者模式去优化上面的代码量。

组装电脑接口

public interface IComputer {

    /**
     * 主机
     */
    IComputer appendHost(Computer computer);

    /**
     * 显示器
     */
    IComputer appendMonitor(Computer computer);

    /**
     * 鼠标
     */
    IComputer appendMouse(Computer computer);

    /**
     * 键盘
     */
    IComputer appendKeyboard(Computer computer);

    /**
     * @return 电脑清单
     */
    String computerDetail();
}

建造者组装电脑

/**
 * 建造者组装电脑
 */
public class BuilderComputer implements IComputer{

    List<Computer> parts = new ArrayList<>();
    private double price = 0.00;
    private Integer choose;

    public BuilderComputer(){}

    public BuilderComputer(Integer choose) {
        this.choose = choose;
    }

    @Override
    public IComputer appendHost(Computer computer) {
        parts.add(computer);
        price = price + computer.price();
        return this;
    }

    @Override
    public IComputer appendMonitor(Computer computer) {
        parts.add(computer);
        price = price + computer.price();
        return this;
    }

    @Override
    public IComputer appendMouse(Computer computer) {
        parts.add(computer);
        price = price + computer.price();
        return this;
    }

    @Override
    public IComputer appendKeyboard(Computer computer) {
        parts.add(computer);
        price = price + computer.price();
        return this;
    }

    @Override
    public String computerDetail() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("套餐为:" + choose + "号套餐\r\n");
        stringBuilder.append("配件如下:\r\n");
        for(Computer c : parts) {
            stringBuilder.append(c.parts() + "、");
            stringBuilder.append(c.brand() + "、");
            stringBuilder.append(c.price() + "、");
            stringBuilder.append(c.desc() + "\r\n");
        }
        stringBuilder.append("总价格为:" + price + "RMB\r\n");
        return stringBuilder.toString();
    }
}

建造者

        去掉了繁琐的if else,符合单一职责原则、开闭原则,代码可读性、复用性、拓展性强。这里面就完美的展示了什么叫做将一个复杂对象的构造与它的表示分离。并且链式编程的语法比不断的set()要美观得多,这会在后续Lambok中的@Builder中进行说明。

/**
 * 建造者
 */
public class Builder {

    /**
     * @return 一号套餐
     */
    public IComputer chooseOne() {
        return new BuilderComputer(1)
                .appendHost(new HPHost())
                .appendMonitor(new RedmiMonitor())
                .appendMouse(new LenovoMouse())
                .appendKeyboard(new HPKeyboard());
    }

    /**
     * @return 二号套餐
     */
    public IComputer chooseTwo() {
        return new BuilderComputer(2)
                .appendHost(new LenovoHost())
                .appendMonitor(new ROGMonitor())
                .appendMouse(new GMouse())
                .appendKeyboard(new GKeyboard());
    }

    /**
     * @return 三号套餐
     */
    public IComputer chooseThree() {
        return new BuilderComputer(3)
                .appendHost(new LenovoHost())
                .appendMonitor(new RedmiMonitor())
                .appendMouse(new GMouse())
                .appendKeyboard(new LenovoMouse());
    }
}

测试

public class BuilderDesign {
    public static void main(String[] args) {
        Builder builder = new Builder();
        System.out.println(builder.chooseOne().computerDetail());
        System.out.println("=======================================================");
        System.out.println(builder.chooseTwo().computerDetail());
        System.out.println("=======================================================");
        System.out.println(builder.chooseThree().computerDetail());
    }
}

@Builder

        此注解是Lombok依赖下的,而Lombok基本是各个公司都会使用到的工具包。可以用来简化开发。上面的建造者组装电脑的示例代码就是链式编程的关键之处:每个方法除了会传参还会返回this自身。我创建了一个用户User类,其带有六个属性。

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private String username;

    private String sex;

    private Integer age;

    private String address;

    private String qq;

    private String email;

}

底层

        为了验证此注解背后的样子,最简单的实践方法就是加上此注解然后查看编译后的class文件中的代码。等编译后我发现多了以下内容。会发现多了一个静态内部类UserBuilder以及返回User.UserBuilder的build()方法

        其实User中的builder()方法以及User类的静态内部类UserBuilder的build()方法。这两个方法名在@Builder注解中已经是默认的值了。并且或者注解可以用于类、普通方法和构造方法上。关于其底层是如何在User类中生成静态内部类并且具体的方法代码块就不深究Lombok中的源码了。这里我需要强调的是使用建造者赋值的时候就是赋值给其内部类属性的

优势

可读性好

        其实当使用过@Builder这个注解的时候就已经可以感受到它的好处之一了:美观且可读性高。这里我使用了三种创建对象的方式来作比较出优劣处。

        第一个User对象使用有参构造的真是长的让人反胃,甚至如果在真实的复杂业务场景中,还不知道其中一个参数是什么含义,还需要点进去看注释。并且自己使用这种有参构造的话,如果没有背下来每个位置要放什么参数那就更麻烦了。所以说有参构造的劣势就是:可读性差、参数过多可能导致传递错误。

        第二个User对象就是一直Setter。相比于第三种而言没有那么好的可读性。所以说使用建造者模式的链式编程可读性好。但是要记住建造者模式的赋值是给其内部类属性的

public class BuilderDesign {
    public static void main(String[] args) {
        User u1 = new User("张三x", "男", 18, "福建省厦门市xxx镇xxxx小区x楼xxx号", "465795464", "465795464@qq.com");

        User u2 = new User();
        u2.setUsername("李四");
        u2.setSex("女");
        u2.setAge(20);
        u2.setAddress("福建省泉州市xxx镇xxxx小区x楼xxx号");
        u2.setQq("504899214");
        u2.setEmail("504899214@qq.com");

        User u3 = User.builder()
                .username("王五")
                .sex("男")
                .age(22)
                .address("福建省福州市xxx镇xxxx小区x楼xxx号")
                .qq("684354768")
                .email("684354768@qq.com")
                .build();
    }
}

JavaBean创建

        我曾在某个地方看到一个大佬说过使用set()方法注入属性和静态内部类Builder注入属性值的区别,但具体怎么说的已经忘记了,

        这里由衷希望看到这里的读者可以在评论里说一下关于JavaBean赋值可能涉及到的线程安全问题或者其它问题。谢谢。

避坑

        在上面有说过一个问题就是:使用builder()方法赋值是赋值给其静态内部类建造者类的。那么这句话是什么意思呢?这句话的意思就是当我们在实体类上已经附带初始值了,但是使用建造者模式去构建实体类打印toString()方法出来的时候是看到为类加载的初始值的(比如0/false/null等)。具体看以下代码以及控制台输出。

public class BuilderDesign {
    public static void main(String[] args) {
        User u = User.builder()
                .username("王五")
                .sex("男")
                .address("福建省福州市xxx镇xxxx小区x楼xxx号")
                .qq("684354768")
                .email("684354768@qq.com")
                .build();
        System.out.println(u);
    }
}

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class User {

    private String username;
    private String sex;
    private Integer age = 30;
    private String address;
    private String qq;
    private String email;

}

        可以看到age = null。因为age是包装类型Integer,所以类加载时的初始值为null,而不是0。这里的原因就是User的age属性初始值为30,但是其内部的UserBuilder类的age属性并没有,所以导致获取到的User对象的age属性为初始值null。为了避免这个情况发生,@Builder注解中有一个内部注解来解决这个问题,就是@Builder.Default。只需要在设置初始值的属性上使用此注解即可。编译生成的User对象会多生成个静态的$default$age()方法。

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {

    private String username;
    private String sex;

    @Builder.Default
    private Integer age = 30;

    private String address;
    private String qq;
    private String email;

}

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

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

相关文章

【HDFS】每天一个RPC系列----complete(二):客户端侧

上图给出了最终会调用到complete RPC的客户端侧方法链路(除去Router那条线了)。 org.apache.hadoop.hdfs.DFSOutputStream#completeFile(org.apache.hadoop.hdfs.protocol.ExtendedBlock): 下面这个方法在complete rpc返回true之前,会进行重试,直到超过最大重试次数抛异…

解决Element Plus中Select在El Dialog里层级过低的问题(修改select选项框样式)

Element Plus是Vue.js的一套基于Element UI的组件库&#xff0c;提供了丰富的组件用于构建现代化的Web应用程序。其中&#xff0c;<el-select>是一个常用的下拉选择器组件&#xff0c;但在某些情况下&#xff0c;当<el-select>组件嵌套在<el-dialog>&#xf…

Python web实战之Django的文件上传和处理详解

概要 关键词&#xff1a;Python Web开发、Django、文件上传、文件处理 今天分享一下Django的文件上传和处理。 1. 上传文件的基本原理 在开始深入讲解Django的文件上传和处理之前&#xff0c;先了解一下文件上传的基本原理。当用户选择要上传的文件后&#xff0c;该文件会被发…

Oracle-ORA-00600:[ktspffbmb:objdchk_kcbnew_3]

问题背景: 应用执行存储过程报错ORA-00600: 内部错误代码, 参数: [ktspffbmb:objdchk_kcbnew_3], [0], [3303775], [4], [], [], [], [], [], [], [], []&#xff0c;导致过程无法正常执行 ORA-00600: 内部错误代码, 参数: [ktspffbmb:objdchk_kcbnew_3], [0], [3303775], [4]…

关联字典表查询记录

字典表 也可以case gender when xxx then xxx when xxx then xxx end 别名 注意前面case别忘了 重新编辑了下 字典表 key value更加合理点吧 嘿嘿

【计算机视觉】关于图像处理的一些基本操作

目录 图像平滑滤波处理均值滤波计算过程python实现 高斯滤波计算过程python实现 中值滤波计算过程python实现 图像的边缘检测Robert算子计算过程python实现 图像处理腐蚀算子计算过程python实现 Hog&#xff08;梯度方向直方图&#xff09;特征计算流程&#xff1a;Hog的特征维…

数据库操作系列-Mysql, Postgres常用sql语句总结

文章目录 1.如果我想要写一句sql语句&#xff0c;实现 如果存在则更新&#xff0c;否则就插入新数据&#xff0c;如何解决&#xff1f;MySQL数据库实现方案: ON DUPLICATE KEY UPDATE写法 Postgres数据库实现方案:方案1&#xff1a;方案2&#xff1a;关于更新&#xff1a;如何实…

【项目多人协作的困扰】git-cli 解决 git merge 合并时 lock 文件变化,忘记重新安装依赖的问题

项目多人协作的困扰 相信大家多多少少都遇到过&#xff0c;当主线分支的代码&#xff0c;合入到自己的分支的时候&#xff0c;如果这时候&#xff0c;主线中有一些依赖的更新或者添加或者删除&#xff0c;如果合入之后&#xff0c;没有及时的install的话&#xff0c;项目启动的…

力扣 -- 467. 环绕字符串中唯一的子字符串

一、题目 二、解题步骤 下面是用动态规划的思想解决这道题的过程&#xff0c;相信各位小伙伴都能看懂并且掌握这道经典的动规题目滴。 三、参考代码 class Solution { public:int findSubstringInWraproundString(string s) {int ns.size();vector<int> dp(n,1);int re…

diffusion model(六)Dalle2技术小结

Dalle2 技术小结 文章目录 Dalle2 技术小结系列阅读背景Dalle2的总体架构DiffusionPrior模块训练阶段推理阶段 DiffusionDecode 模块 Dalle2的生成质量和目前的局限性Dalle2的生成效果基于MS-COCO caption生成效果Other Dalle2的局限性 参考文献 系列阅读 diffusion model&…

Ubuntu服务器ELK部署与实践

文章目录 1. Docker安装2. 拉镜象2.1 ElastciSearch2.2 Kibana2.3 logstash 3. 数据展示 1. Docker安装 看之前的文章 docker ubuntu完全卸载docker及再次安装 Ubuntu安装 Docker 此外&#xff0c;Docker偶尔会出现这种问题dial tcp: lookup registry-1.docker.io on 192.168.1…

ICMP 理解

icmp 差错报告机制&#xff0c;tcp/ip协议族中重要的子协议&#xff0c;通常被ip层或者更高的&#xff08;tcp/udp&#xff09;使用&#xff0c;&#xff0c;属于网络层协议&#xff0c;主要用于在ip主机和路由器之间传递控制消息&#xff0c;用于报告主机是否可达、路由是否可…

融邦JAVA面试题

1. Java线程池的作用及使用方式 线程池的作用&#xff1a; (1) 降低系统资源消耗&#xff1a;通过重用已存在的线程&#xff0c;降低线程创建和销毁造成的消耗。 (2) 提高系统响应速度&#xff1a;当有任务到达时&#xff0c;无需等待新线程的创建便能立即执行。 (3) 提高线程…

SpringBoot3基础用法

技术和工具「!喜新厌旧」 一、背景 最近在一个轻量级的服务中&#xff0c;尝试了最新的技术和工具选型&#xff1b; 即SpringBoot3&#xff0c;JDK17&#xff0c;IDEA2023&#xff0c;Navicat16&#xff0c;虽然新的技术和工具都更加强大和高效&#xff0c;但是适应采坑的过程…

阿里云瑶池 PolarDB 开源官网焕新升级上线

导读近日&#xff0c;阿里云开源云原生数据库 PolarDB 官方网站全新升级上线。作为 PolarDB 开源项目与开发者、生态伙伴、用户沟通的平台&#xff0c;将以开放、共享、促进交流为宗旨&#xff0c;打造开放多元的环境&#xff0c;以实现共享共赢的目标。 立即体验全新官网&…

【测试】软件测试工具JMeter简单用法

简明扼要&#xff0c;点到为止。 1. JMeter介绍 JMeter的全称是Apache JMeter&#xff0c;是一款用于软件测试的工具软件&#xff0c;其是开源免费的&#xff0c;由Apache基金会运营。 官网&#xff1a;Apache JMeter - Apache JMeter™ 2. 下载安装及运行 2.1 安装 Java8…

RabbitMQ的6种工作模式

RabbitMQ的6种工作模式 官方文档&#xff1a; http://www.rabbitmq.com/ https://www.rabbitmq.com/getstarted.html RabbitMQ 常见的 6 种工作模式&#xff1a; 1、simple简单模式 1)、消息产生后将消息放入队列。 2)、消息的消费者监听消息队列&#xff0c;如果队列中…

深入探究Spring核心模块

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

Flowise AI:用于构建LLM流的拖放UI

推荐&#xff1a;使用NSDT场景编辑器助你快速搭建可二次编辑的3D应用场景 什么是Flowise AI&#xff1f; Flowise AI是一个开源的UI可视化工具&#xff0c;用于帮助开发LangChain应用程序。在我们详细介绍 Flowise AI 之前&#xff0c;让我们快速定义 LangChain。LangChain是…

谈谈如何使用ShellExecute的返回值

之前的一篇文章中&#xff0c;我们讲到了在 16 位 Windows 中&#xff0c;实例句柄(HINSTANCE)唯一标识了一个进程。到了 32 位 Windows&#xff0c;内核得到了完全的重新设计&#xff0c;其中之一是&#xff1a;它引入了 “内核对象” 和 “安全描述符”。 在 16 位 Windows …