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

概念

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

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

未用建造者模式

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

电脑接口

/**
 * 电脑接口
 */
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.mfbz.cn/a/63466.html

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

相关文章

深度学习论文: RepViT: Revisiting Mobile CNN From ViT Perspective及其PyTorch实现

深度学习论文: RepViT: Revisiting Mobile CNN From ViT Perspective及其PyTorch实现 RepViT: Revisiting Mobile CNN From ViT Perspective PDF: https://arxiv.org/pdf/2307.09283.pdf PyTorch代码: https://github.com/shanglianlm0525/CvPytorch PyTorch代码: https://gith…

【并发专题】单例模式的线程安全(进阶理解篇)

目录 背景前置知识类加载运行全过程 单例模式的实现方式一、饿汉式基本介绍源码分析 二、懒汉式基本介绍源码分析改进 三、懒汉式单例终极解决方案&#xff08;静态内部类&#xff09;&#xff08;推荐使用方案&#xff09;基本介绍源码分析 感谢 背景 最近学习了JVM之后&…

Permute 3 for mac音视频格式转换

Permute是一款Mac平台上的媒体格式转换软件&#xff0c;由Chaotic Software开发。它可以帮助用户快速地将各种音频、视频和图像文件转换成所需格式&#xff0c;并提供了一些常用工具以便于用户进行编辑和处理。 Permute的主要特点包括&#xff1a; - 支持大量格式&#xff1a;支…

2020年09月 Python(一级)真题解析#中国电子学会#全国青少年软件编程等级考试

一、单选题 第1题 Python自带的编程环境是&#xff1f; A&#xff1a;PyScripter B&#xff1a;Spyder C&#xff1a;Notepad D&#xff1a;IDLE 正确的答案是&#xff1a;D Python自带的编程环境是IDLE&#xff08;Integrated Development and Learning Environment&a…

【c语言初级】c++基础

文章目录 1. C关键字2. 命名空间2.1 命名空间定义2.2 命名空间使用 3. C输入&输出4. 缺省参数4.1 缺省参数概念4.2 缺省参数分类 5. 函数重载5.2 C函数重载的原理--名字修饰采用C语言编译器编译后结果 1. C关键字 C是在C的基础之上&#xff0c;容纳进去了面向对象编程思想…

【分布式能源选址与定容】光伏、储能双层优化配置接入配电网研究(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码、数据、讲解 &#x1f4a5;1 概述 由于能源的日益匮乏&#xff0c;电力需求的不断增长等&#xff0c;配电网中分布式能源渗透率不断提高&#xff0c;且逐渐向主动配电网方…

Android平台一对一音视频通话方案对比:WebRTC VS RTMP VS RTSP

一对一音视频通话使用场景 一对一音视频通话都需要稳定、清晰和流畅&#xff0c;以确保良好的用户体验&#xff0c;常用的使用场景如下&#xff1a; 社交应用&#xff1a;社交应用是一种常见的使用场景&#xff0c;用户可以通过音视频通话进行面对面的交流&#xff1b;在线教…

OLAP ModelKit Crack,ADO.NET和IList

OLAP ModelKit Crack,ADO.NET和IList OLAP ModelKit是一个多功能的.NET OLAP组件&#xff0c;用C#编写&#xff0c;只包含100%托管代码。它具有XP主题的外观&#xff0c;并能够使用任何.NET数据源(ADO.NET和IList)。借助任何第三方组件(尤其是图表组件)呈现数据的能力扩展了产品…

Pytorch深度学习-----损失函数(L1Loss、MSELoss、CrossEntropyLoss)

系列文章目录 PyTorch深度学习——Anaconda和PyTorch安装 Pytorch深度学习-----数据模块Dataset类 Pytorch深度学习------TensorBoard的使用 Pytorch深度学习------Torchvision中Transforms的使用&#xff08;ToTensor&#xff0c;Normalize&#xff0c;Resize &#xff0c;Co…

【Hystrix技术指南】(5)Command创建和执行实现

创建流程 构建HystrixCommand或者HystrixObservableCommand对象 *使用Hystrix的第一步是创建一个HystrixCommand或者HystrixObservableCommand对象来表示你需要发给依赖服务的请求。 若只期望依赖服务每次返回单一的回应&#xff0c;按如下方式构造一个HystrixCommand即可&a…

24届近5年江南大学自动化考研院校分析

今天给大家带来的是江南大学控制考研分析 满满干货&#xff5e;还不快快点赞收藏 一、江南大学 学校简介 江南大学&#xff08;Jiangnan University&#xff09;是国家“双一流”建设高校&#xff0c;“211工程”、“985工程优势学科创新平台”重点建设高校&#xff0c;入选…

【linux源码学习】【实验篇】使用bochs运行linux0.11系统(搭建一个自己的工作站)

目录 背景资源获取bochs环境搭建windowsbochs环境搭建linux声明 背景 最近看赵炯老师的《linux内核完全注释》&#xff0c;然后在最后一个习题里面看到使用bochs跑一下0.11的内核代码&#xff0c;本来觉得很难&#xff0c;但是如果做过一遍就会发现其实很简单&#xff0c;这个…

在centos7上使用非编译方式安装ffmpeg

很多在centos7上安装ffmpeg的教程都需要使用编译方式的安装&#xff1b;编译时间较长而且需要配置; 后来搜索到可以通过加载rpm 源的方式实现快速便捷操作 第一种方式&#xff1a; 首先需要安装yum源&#xff1a; yum install epel-release yum install -y https://mirrors.…

内网安全-隧道技术SSH实现通信DNS上线与通信CS上线Linux主机

内网安全-隧道技术&SSH实现通信&DNS上线与通信&CS上线Linux主机 一、DNS隧道技术 DNS简介&#xff1a;DNS协议为应用层协议&#xff0c;区域传输时用tcp协议&#xff0c;域名解析时用udp协议 ###通过DNS隧道绕过防火墙&#xff0c;实现CS上线 实验背景&#xff…

在收到满意的大厂offer之前,面试也是至关重要的,那么该如何做好IT类的面试呢?

方向一&#xff1a;分享你面试IT公司的小技巧 沉着冷静应对刁难&#xff1a;应试场上&#xff0c;考官往往会针对求职者的薄弱点提出一些带有挑战性的问题。面对这样的考题&#xff0c;你一定要心平气和&#xff0c;较为委婉地加以反驳和申诉&#xff0c;绝不可情绪激动&#x…

ELK、ELFK日志分析系统

菜单一、ELK简介1.1 ELK组件说明1.1.1 ElasticSearch1.1.2 Kiabana1.1.3 Logstash 1.2 可以添加的其它组件1.2.1 Filebeat1.2.2 缓存/消息队列&#xff08;redis、kafka、RabbitMQ等&#xff09;1.2.3 Fluentd 1.3 为什么要用ELK1.4 完整日志系统的基本特征1.5 ELK 的工作原理 …

python+requests+json 接口测试思路示例

实际项目中用python脚本实现接口测试的步骤&#xff1a; 1 发送请求&#xff0c;获取响应 》》2 提取响应里的数据&#xff0c;对数据进行必要的处理 》》3 断言响应数据是否与预期一致 以豆瓣接口为例&#xff0c;做一个简单的接口测试吧。使用到的知识涉及requests库&…

Glass指纹识别工具,多线程Web指纹识别工具-Chunsou

Glass指纹识别工具&#xff0c;多线程Web指纹识别工具-Chunsou。 Glass指纹识别工具 Glass一款针对资产列表的快速指纹识别工具&#xff0c;通过调用Fofa/ZoomEye/Shodan/360等api接口快速查询资产信息并识别重点资产的指纹&#xff0c;也可针对IP/IP段或资产列表进行快速的指…

C语言有关文件的操作

打开文件与关闭文件 在编写代码时&#xff0c;我有一个习惯是“保证一一对应”。 写下代码fopen()之后&#xff0c;还没有写对文件进行增删查改等操作的代码&#xff0c;先立刻写上fclose()&#xff0c;避免忘记关闭FILE* fd的情况。 不关闭fd&#xff0c;在fopen()次数较少的…

怎么在树莓派环境上搭建web网站,并发布到外网可访问,今天教给大家

怎么在树莓派上搭建web网站&#xff0c;并发布到外网可访问&#xff1f; 文章目录 怎么在树莓派上搭建web网站&#xff0c;并发布到外网可访问&#xff1f;概述使用 Raspberry Pi Imager 安装 Raspberry Pi OS测试 web 站点安装静态样例站点 将web站点发布到公网安装 Cpolarcpo…