设计模式之建造者设计模式

news2024/11/23 12:13:00

写在前面

不知道,你在工作中有没有使用过lombok,如果你使用过,不知道你有没有使用过其中的@Builder注解,其就会帮我们生成建造者设计模式相关的代码,本文就一起来看下吧!

1:介绍

1.1:什么时候用工厂方法

当一个对象的属性很多,并且在不同的场景下对象创建时需要初始化的属性不同时,可以考虑使用该设计模式,否则就需要创建大量的构造函数,造成代码的臃肿和难以维护,并且对于使用者来说,到底选择哪个构造函数来初始化也会比较麻烦。

1.2:UML类图

工厂方法设计模式,包含如下元素:

1:产品
    待创建的对象
2:抽象构造者
    定义创建产品需要设置的属性
3:具体构造者
    具体产品的构造者,继承抽象构造者,负责设置具体的信息
4:导演
    使用构造者来创建产品

如下图:

在这里插入图片描述

2:实例

源码 。

2.1:场景

按照不同的需求创建手机对象。

2.2:程序

  • 创建产品类
@Data // 可自动生成get、set、toString方法
public class Phone {

    /**
     * 品牌
     */
    private String brand;

    /**
     * 操作系统
     */
    private String os;

    /**
     * 内存大小, Unit: GB
     */
    private Integer ramSize;

    /**
     * 售价
     */
    private Double price;
}
  • 创建抽象构造器
/**
 * 手机抽象建造者的接口
 */
public interface PhoneBuilder {
    /**
     * 设置品牌
     */
    void setBrand();

    /**
     * 设置操作系统
     */
    void setOs();

    /**
     * 设置内存大小
     */
    void setRamSize();

    /**
     * 设置售价
     */
    void setPrice();

    /**
     * 获取 Phone 实例
     * @return
     */
    Phone getPhone();
}
  • 创建两个具体的构造器
    分别创建预定好的苹果手机,和小米手机:
/**
 * 苹果手机建造者
 */
public class IPhoneBuilder implements PhoneBuilder{

    private Phone phone;

    public IPhoneBuilder() {
        phone = new Phone();
    }

    @Override
    public void setBrand() {
        phone.setBrand("Apple");
    }

    @Override
    public void setOs() {
        phone.setOs("IOS");
    }

    @Override
    public void setRamSize() {
        phone.setRamSize(2);
    }

    @Override
    public void setPrice() {
        phone.setPrice(6666.66);
    }

    @Override
    public Phone getPhone() {
        return phone;
    }
}

/**
 * 小米手机建造者
 */
public class MiPhoneBuilder implements PhoneBuilder {

    private Phone phone;

    public MiPhoneBuilder() {
        phone = new Phone();
    }

    @Override
    public void setBrand() {
        phone.setBrand("Xiao Mi");
    }

    @Override
    public void setOs() {
        phone.setOs("Android");
    }

    @Override
    public void setRamSize() {
        phone.setRamSize(8);
    }

    @Override
    public void setPrice() {
        phone.setPrice(1999.99);
    }

    @Override
    public Phone getPhone() {
        return phone;
    }
}
  • 创建导演类
/**
 * 导演: 负责指定手机建造流程
 */
public class PhoneDirector {

    /**
     * 导演指挥建造者完成手机的建造工作
     * @param phoneBuilder
     */
    public void construct(PhoneBuilder phoneBuilder) {
        phoneBuilder.setBrand();
        phoneBuilder.setOs();
        phoneBuilder.setRamSize();
        phoneBuilder.setPrice();
    }
}
  • 测试
@Test
public void origin() {
    // 1. 创建一个导演
    PhoneDirector phoneDirector = new PhoneDirector();

    /***** 2. 建造 iPhone 手机 *****/
    
    // 2a. 创建一个 iPhone建造者
    IPhoneBuilder iPhoneBuilder = new IPhoneBuilder();
    // 2b. 导演指导 iPhone建造者 来建造一个iPhone的实例
    phoneDirector.construct(iPhoneBuilder);
    // 2c. 从 iPhone建造者 中获取实例
    Phone iPhone = iPhoneBuilder.getPhone();
    System.out.println(iPhone);

    /***** 3. 建造 Xiao Mi Phone 手机 *****/
    
    // 3a. 创建一个 MiPhone建造者
    MiPhoneBuilder miPhoneBuilder = new MiPhoneBuilder();
    // 3b. 导演指导 MiPhone建造者 来建造一个MiPhone的实例
    phoneDirector.construct(miPhoneBuilder);
    // 3c. 从 MiPhone建造者 中获取实例
    Phone miPhone = miPhoneBuilder.getPhone();
    System.out.println(miPhone);

}

运行:

Phone(brand=Apple, os=IOS, ramSize=2, price=6666.66)
Phone(brand=Xiao Mi, os=Android, ramSize=8, price=1999.99)

Process finished with exit code 0

需要注意到,这里客户端虽然不需要去设置对象的各种属性信息了,但是仅仅适用于要设置的属性都是确定的,并且要设置的属性值也是确定的场景,如果是要设置哪些属性是不确定,要设置的属性值也是不确定的话,这种方式明显就不使用了,怎么做呢?可以创建对应的构造函数,直接使用构造函数创建,但是可能需要创建非常多的构造函数,会让代码变的臃肿且难以维护。也可以调用无参构造函数,然后分别调用对应的setXxx方法,但是这样程序会变的复杂,且效率低下,因此就有了建造者设计模式的简化版本,这种方式创建一个内部的静态Builder类,之后通过链式调用的方式来设置属性,最终调用build,build内会调用对象的全部参数的构造函数,从而完成对象创建,使用简化版本的建造者设计模式修改Phone类如下:

/**
 * 手机
 */
@ToString
public class Phone {

    /**
     * 品牌
     */
    private String brand;

    /**
     * 操作系统
     */
    private String os;

    /**
     * 内存大小, Unit: GB
     */
    private Integer ramSize;

    /**
     * 售价
     */
    private Double price;

    /**
     * 提供一个静态方法以方便创建一个Phone建造者实例
     * @return
     */
    public static Phone.PhoneBuilder builder() {
        return new Phone.PhoneBuilder();
    }

    /**
     * 提供一个Phone的全参构造器以供建造者Builder来建造Phone实例
     * @param brand
     * @param os
     * @param ramSize
     * @param price
     */
    public Phone(String brand, String os, Integer ramSize, Double price) {
        this.brand = brand;
        this.os = os;
        this.ramSize = ramSize;
        this.price = price;
    }


    /**
     * 静态内部类: Phone Builder 建造者
     */
    public static class PhoneBuilder {

        private String brand;

        private String os;

        private Integer ramSize;

        private Double price;

        /**
         * Builder 建造者构造器
         */
        public PhoneBuilder() {
        }

        public Phone.PhoneBuilder brand(String brand) {
            this.brand = brand;
            return this;
        }

        public Phone.PhoneBuilder os(String os) {
            this.os = os;
            return this;
        }

        public Phone.PhoneBuilder ramSize(Integer ramSize) {
            this.ramSize = ramSize;
            return this;
        }

        public Phone.PhoneBuilder price(Double price) {
            this.price = price;
            return this;
        }

        /**
         * 建造者通过 Phone的全参构造器 来构造 Phone 实例
         * @return
         */
        public Phone build() {
            return new Phone(brand, os, ramSize, price);
        }
    }
}

测试:

@Test
public void simplify() {
    dongshi.daddy.builder.simplify.Phone P40Pro
            = dongshi.daddy.builder.simplify.Phone.builder()  // 通过产品的静态方法获取建造者
            .brand("华为")         // "客户"(调用方)充当了Director这个角色
            .os("鸿蒙")
            .ramSize(12)
            .price(9999.99)
            .build();             // 获得建造完成的产品

    System.out.println(P40Pro);
}

输出:

Phone(brand=华为, os=鸿蒙, ramSize=12, price=9999.99)

Process finished with exit code 0

这种简化版本的构造者设计模式其实就是我们在工作中经常用的lombok 的@Builder注解,如下:

@Builder
@ToString
public class PhoneWithLombok {

    /**
     * 品牌
     */
    private String brand;

    /**
     * 操作系统
     */
    private String os;

    /**
     * 内存大小, Unit: GB
     */
    private Integer ramSize;

    /**
     * 售价
     */
    private Double price;
}

程序是不是简洁多了,工作中用起来吧!

测试:

@Test
public void simplifyWithLombok() {
    PhoneWithLombok P40Pro
            = PhoneWithLombok.builder()  // 通过产品的静态方法获取建造者
            .brand("华为lombok")         // "客户"(调用方)充当了Director这个角色
            .os("lombok 操作系统")
            .ramSize(12)
            .price(9999.99)
            .build();             // 获得建造完成的产品

    System.out.println(P40Pro);
}

输出:

PhoneWithLombok(brand=华为lombok, os=lombok 操作系统, ramSize=12, price=9999.99)

Process finished with exit code 0

写在后面

参考文章列表

GoF设计模式(五):Builder Pattern 建造者模式 。

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

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

相关文章

css3的新特性

动画效果 过渡 transition 鼠标放上去瞬间变大 过渡是变大的过程慢慢变化 第一个参数:对哪些值进行过渡。all为hover中所有,也可以指定属性 第二个参数:让动画过渡多长时间。要添加单位(s秒) 第三个参数&#xff1…

路径规划算法:基于厨师优化的路径规划算法- 附代码

路径规划算法:基于厨师优化的路径规划算法- 附代码 文章目录 路径规划算法:基于厨师优化的路径规划算法- 附代码1.算法原理1.1 环境设定1.2 约束条件1.3 适应度函数 2.算法结果3.MATLAB代码4.参考文献 摘要:本文主要介绍利用智能优化算法厨师…

【pytest自动化测试框架】从0到1由浅入深详细讲解

一:框架简介 pytest,rf(学关键字语法,报告漂亮),unitest   pytest是python的第三方单元测试框架,可以做系统测试,比unitest更简洁和高效,执行315种以上的插件&#xff…

AtcoderABC256场

A - 2^NA - 2^N 题目大意 给n,计算2n 思路分析 可以直接使用幂运算进行计算,也可以利用位运算来快速计算。 使用幂运算:将2连乘N次。利用位运算:2的N次方等于1左移N位(即将1的二进制表示中的1向左移N位)。 时间复…

Redis数据结构 — SDS

目录 C 语言字符串的缺陷 简单动态字符串SDS 扩容机制 SDS优点 字符串在 Redis 中是很常用的,Key-Value中的Key是字符串类型,Value有时也是字符串类型 Redis 是用 C 语言实现的,但是它没有直接使用 C 语言的 char* 字符数组来实现字符串…

如何激励员工跟踪时间?

正确跟踪工作时间对有效的企业管理和盈利能力起到重要作用。企业通过跟踪时间,监控出勤情况,确保员工收到应得的工资,保护企业的利益。 时间跟踪对企业来说和其他指标一样重要,但他们往往难以说服员工将其视为一种有益的活动。那…

windows 下载安装Redis,并配置开机自启动

windows 下载安装Redis,并配置开机自启动 1. 下载 地址:https://github.com/tporadowski/redis/releases Redis 支持 32 位和 64 位。这个需要根据你系统平台的实际情况选择,这里我们下载 Redis-x64-xxx.zip压缩包,之后解压 打…

园区水电能源管理系统平台

园区水电能源管理系统平台是一种集成了水电能源监测、管理和优化功能的平台,旨在帮助园区管理者实现对水电资源的实时监测、合理节能和优化使用。随着能源需求的不断增长和能源价格的不断攀升,园区管理者面临着越来越大的节能减排压力。因此,…

selenium查找svg元素

目录 如何为SVG元素编写XPath 使用local-name()的语法 需要记住的一点 将“and”与SVG元素一起使用 如何定位嵌套的SVG元素? XPath是一种用于定位XML文档中的web元素的语言,包括构成网页的HTML文档。在Selenium中&#xff0…

001 Jetpack Compose入门

目录 1.前提准备 2.新建项目 1.前提准备 下载最新版本的AndroidStudio,我目前的版本如下: 注意要将kotlin环境配置好 2.新建项目 新建项目中空Activity就是新建的Compose项目 然后就会得到一个Compose界面的应用 为何要学Compose可以看郭霖的文章&…

拉丁语翻译软件分享-收藏这几个备用

在当今全球化的时代,语言交流变得更加重要。虽然英语已经成为国际通用语言,但依旧有一些古老的语言在特定领域和文化中仍然得到广泛使用。其中,拉丁语作为古代罗马帝国的官方语言,对欧洲语言起了重要的影响。在本文中,…

el-select组件如何在页面一加载时就根据数据选中el-option中的数据

页面一加载就要选中下拉中的数据,要和下拉数组中的value的类型一致,value是number,select的v-model就不能是字符串,必须是number.不然就会显示字符串

【C语言】【数据结构初阶】 快排变慢排?怎么个事儿?

一.为何“快排”变“慢排” 我们知道,快排是一种很好的排序算法。但是在极少数的一些情况下,“快速排序”好像名不副实了。比如:1,2,1,2,1,2,1。。。。。。这样的数列。 …

十七、web网页像素知识

目录: 像素视口 一、像素 像素:- 屏幕是由一个个发光的小点构成,这一个个的小点就是像素- 分辨率:1920 x 1080 说的就是屏幕中小点的数量- 在前端开发中像素要分成两种情况讨论:css像素和物理像素- 物理像素&#xff…

具有电动驱动的四足机器人模型研究(SimulinkMatlab代码)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

Mac的docker安装redis

Mac的docker安装redis 1、docker search redis NAME DESCRIPTION STARS OFFICIAL AUTOMATED redis Redis is an open source key-value store that… 12205 …

unity 2018.4.0+SteamVR1.2.2+VRTK3.2.1报错解决

开发案例参考 07-游乐设备选择场景搭建_哔哩哔哩_bilibili 导入之后代码报错Assets/SteamVR/Editor/SteamVR_Settings.cs(135,49): error CS0117: UnityEditorInternal.VR.VREditor does not contain a definition for GetVREnabledDevices 解决方法 打开SteamVR_Settings 脚…

深入了解C语言中scanf()函数的用法

目录 正文 一、基本用法 二、格式化字符串 三、多个输入值 四、错误处理 五、总结 正文 在C语言中,scanf()函数是一个非常有用的输入函数,它允许我们从用户那里获取输入,并将输入存储到指定的变量中。本文将详细介绍scanf()函数的使用方…

【ELK企业级日志分析系统】部署Filebeat+Kafka+Logstash+Elasticsearch+Kibana集群详解(EFLFK)

部署FilebeatKafkaLogstashElasticsearchKibana集群详解 1. Kafka1.1 Kafka概述1.1.1 为什么需要消息队列(MQ)1.1.2 使用消息队列的好处 1.2 消息队列的两种模式1.3 Kafka定义1.3.1 Kafka简介1.3.2 Kafka的特性1.3.3 Kafka系统架构1.3.4 Partation数据路…

微信消息延迟怎么办?

微信是我们常用的聊天工具,那在微信聊天中,相信有很多朋友都遇到过,明明对方的消息已经发过来了,可微信就是没有显示。这就很容易发生误会或者耽误很多事情。 那么微信消息延迟是什么原因导致的呢? 网络信号差 当我们的…