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

news2024/12/25 9:35:17

文章目录

  • 建造者模式介绍
  • 建造者模式适用场景
  • 案例场景
    • 一坨坨代码实现
    • 重构代码
  • 与工厂模式区别
  • 建造者模式优缺点
  • 总结


该说不说几乎是程序员都知道或者了解设计模式,但大部分小伙伴写代码总是习惯于一把梭。好的代码不只为了完成现有功能,也会考虑后续扩展。通过学习和使用设计模式,开发人员可以提高自己的设计能力和代码水平,更好地完成项目开发任务。

本文聊聊设计模式中的建造者模式,其实我们生活中关于建造者模式的场景不少。就拿买车举例吧,当我们去买车,不会只买一个轮胎或者方向盘,大家买的都是一辆包含轮胎、方向盘和发动机等多个部件的完整汽车。如何将这些部件组装成一辆完整的汽车并返回给用户,这是建造者模式需要解决的问题。

建造者模式介绍

建造者模式是较为复杂的创建型模式,它将客户端与包含多个组成部分(或部件)的复杂对象的创建过程分离,客户端无须知道复杂对象的内部组成部分与装配方式,只需要知道所需建造者的类型即可。它关注如何一步一步创建一个的复杂对象,不同的具体建造者定义了不同的创建过程,且具体建造者相互独立,增加新的建造者非常方便,无须修改已有代码,系统具有较好的扩展性。

建造者模式结构图:

image.png

  • Builder(抽象建造者):它为创建一个产品Product对象的各个部件指定抽象接口,在该接口中一般声明两类方法,一类方法是buildPart(),它们用于创建复杂对象的各个部件;另一类方法是getResult(),它们用于返回复杂对象。Builder既可以是抽象类,也可以是接口。

  • ConcreteBuilder(具体建造者):它实现了Builder接口,实现各个部件的具体构造和装配方法,定义并明确它所创建的复杂对象,也可以提供一个方法返回创建好的复杂产品对象。

  • Product(产品角色):它是被构建的复杂对象,包含多个组成部件,具体建造者创建该产品的内部表示并定义它的装配过程。

  • Director(指挥者):指挥者又称为导演类,它负责安排复杂对象的建造次序,指挥者与抽象建造者之间存在关联关系,可以在其construct()建造方法中调用建造者对象的部件构造与装配方法,完成复杂对象的建造。客户端一般只需要与指挥者进行交互,在客户端确定具体建造者的类型,并实例化具体建造者对象(也可以通过配置文件和反射机制),然后通过指挥者类的构造函数或者Setter方法将该对象传入指挥者类中。

建造者模式适用场景

  1. 需要生成的产品对象有复杂的内部结构,这些产品对象通常包含多个成员属性。

  2. 需要生成的产品对象的属性相互依赖,需要指定其生成顺序。

  3. 对象的创建过程独立于创建该对象的类。在建造者模式中通过引入了指挥者类,将创建过程封装在指挥者类中,而不在建造者类和客户类中。

  4. 隔离复杂对象的创建和使用,并使得相同的创建过程可以创建不同的产品。

案例场景

现实生活中我们去买车,一般同一辆车会有多个版本,根据配置不一样,分为经典版、舒适版、豪华版等,比如经典版是手动挡手动座椅,舒适版是自动挡全景天窗等。

定义产品角色

@Data
public class Car {

    // 车名称
    private String name;
    // 自动挡
    private String automaticCatch;
    // 手动挡
    private String manualTransmission;
    // 全景天窗
    private String panoramicSunroof;
    // 自动座椅
    private String automaticSeat;
    // 手动座椅
    private String manualSeat;
    // 倒车影像
    private String reversingImage;

}

一坨坨代码实现

public class CarController {

    public Car getCarInstance(String carName) {
        Car car = new Car();
        if ("经典版".equals(carName)) {
            car.setName("经典版");
            car.setManualTransmission("手动挡");
        } else if ("舒适版".equals(carName)) {
            car.setName("舒适版");
            car.setAutomaticCatch("自动挡");
            car.setManualSeat("手动座椅");
        } else if ("豪华版".equals(carName)) {
            car.setName("豪华版");
            car.setAutomaticCatch("自动挡");
            car.setAutomaticSeat("自动座椅");
            car.setReversingImage("倒车影像");
            car.setPanoramicSunroof("全景天窗");
        } else {
            throw new IllegalArgumentException("carName is error: carName=" + carName);
        }
        return car;
    }
}

测试

@Test
public void test(){
    CarController carController = new CarController();
    System.out.println(carController.getCarInstance("豪华版").toString());
}
Car(name=豪华版, automaticCatch=自动挡, manualTransmission=null, panoramicSunroof=全景天窗, automaticSeat=自动座椅, manualSeat=null, reversingImage=倒车影像)

以上这段使用if else方式实现的代码,目前已经满足的我们的一些功能,但其实汽车的配置是有很多的,种类一旦多了,上面的代码就会显得很臃肿,也不好维护,下面我们用建造者模式来重构。

重构代码

创建抽象建造者类

public abstract class CarBuilder {

    public abstract Car buildClassic();

    public abstract Car buildComfortable();

    public abstract Car buildLuxury();
}

创建具体建造者类

public class CarConcreteBuilder extends CarBuilder {

    @Override
    public Car buildClassic() {
        Car car = new Car();
        car.setName("经典版");
        car.setManualTransmission("手动挡");
        return car;
    }

    @Override
    public Car buildComfortable() {
        Car car = new Car();
        car.setName("舒适版");
        car.setAutomaticCatch("自动挡");
        car.setManualSeat("手动座椅");
        return car;
    }

    @Override
    public Car buildLuxury() {
        Car car = new Car();
        car.setName("豪华版");
        car.setAutomaticCatch("自动挡");
        car.setAutomaticSeat("自动座椅");
        car.setReversingImage("倒车影像");
        car.setPanoramicSunroof("全景天窗");
        return car;
    }
}

创建我们的导演类

public class CarDirector {

    private CarBuilder carBuilder;

    public CarDirector(CarBuilder carBuilder) {
        this.carBuilder = carBuilder;
    }

    public Car classicConstruct() {
        return carBuilder.buildClassic();
    }

    public Car comfortableConstruct() {
        return carBuilder.buildComfortable();
    }

    public Car luxuryConstruct() {
        return carBuilder.buildLuxury();
    }
}

测试

@Test
public void test(){
    CarBuilder carBuilder = new CarConcreteBuilder();
    CarDirector carDirector = new CarDirector(carBuilder);
    Car comfortableCar = carDirector.luxuryConstruct();
    System.out.println(comfortableCar);
}
Car(name=豪华版, automaticCatch=自动挡, manualTransmission=null, panoramicSunroof=全景天窗, automaticSeat=自动座椅, manualSeat=null, reversingImage=倒车影像)

测试结果是一样的,调用方式也基本类似。但是目前的代码结构却可以让你很方便的进行扩展业务开发。而不是像以往一样把所有代码都写到if else里面。

与工厂模式区别

工厂模式是由工厂类来负责创建对象,而建造者模式也是让具体建造者类负责创建对象,那这两者有什么区别呢?

实际上工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。而建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。

可以形象的解释:顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨

建造者模式优缺点

主要优点:

  1. 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。

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

  3. 由于具体每个建造者过程是独立的,因此可以对过程更加细化,而不会对其它模块产生影响。

主要缺点:

  1. 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,例如很多组成部分都不相同,不适合使用建造者模式,因此其使用范围受到一定的限制。

  2. 如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,增加系统的理解难度和运行成本。

总结

建造者模式的核心在于如何一步步构建一个包含多个组成部件的完整对象,使用相同的构建过程构建不同的产品,在软件开发中,如果我们需要创建复杂对象并希望系统具备很好的灵活性和可扩展性可以考虑使用建造者模式。

设计模式能带给你的是一些思想,但在平时的开发中怎么样清晰的提炼出符合此思路的建造模块,是比较难的。需要经过一些锻炼和不断承接更多的项目,从而获得这部分经验。有的时候你的代码写的好,往往是倒逼的,复杂的业务频繁的变化,不断的挑战!

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

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

相关文章

springboot自动配置源码解析

概述 使用springboog的时候引入starter就自动为我们加载,例如我们引入 spring-boot-starter-web 之后,就自动引入了 Spring MVC 相关的 jar 包,从而自动配置 Spring MVC 。 自动装配原理 SpringBootApplication SpringBootApplication: Spri…

Java的引用

一、概述 其实java有4种引用,4种可分为强、软、弱、虚。我们将从这四个方面入手进行介绍。 二、强引用 首先看到我们有一个类叫M,在这个类里我重写了一个方法叫finalize(),我们可以看到这个方法是已经被废弃的方法,为什么要重写…

【jupyter】Jupyter Notebook如何导入导出文件

目录 0.系统:windows 1.打开 Jupyter Notebook 2.Jupyter Notebook导入文件 3.Jupyter Notebook导出文件 0.系统:windows 1.打开 Jupyter Notebook 1)下载【Anaconda】后,直接点击【Jupyter Notebook】即可在网页打开 Jupyte…

用户研究干货——这一篇就够啦

一、基本概念: ①工作内容:用户研究的首要目的是帮助企业定义产品目标用户群,明确、细化产品概念,并通过对用户的任务操作特性、知觉特征、认知心理特征的研究,使用户的实际需求成为产品设计的导向,使产品…

建面超72万㎡,南山红花岭旧改规划公示,配套近15万㎡宿舍

近日,深圳市南山区城市更新和土地整备局发布关于桃源街道红花岭工业南区更新单元(暂定名)03-01、02-02地块《建设工程规划许可证》及总平面图的公告。 此次批复的红花岭工业南区02-02、03-01块,总建面超72万㎡,用地单…

nginx+tomcat 负载均衡、动静分离集群

文章目录 一、NginxTomcat负载均衡的组合原因1.1 Nginx实现负载均衡的原理1.2 Nginx实现负载均衡的主要配置项1.3 NginxTomcat负载均衡的组合的优点1.4 NginxTomcat负载均衡的实验设计 二、动静分离部署2.1 部署TOMCAT后端服务器2.2部署nginx服务器2.3安装nginx动态服务器 一、…

java中try-with-resources自动关闭io流

在传统的输入输出流处理中,我们一般使用的结构如下所示,使用try - catch - finally结构捕获相关异常,最后不管是否有异常,我们都将流进行关闭处理: try {//todo } catch (IOException e) {log.error("read xxx f…

《Lua程序设计》--学习1

前言&#xff1a; --> 表示一条语句的输出或表达式求值的结果 -- 单行注释 > 标注 一些代码需要在交互模式下输入 如果需要打印表达式求值的结果&#xff0c;必须在每个表达式前加上一个等号 <--> 表示两者完全等价 语言基础 我们将Lua语言执行的每一…

html选择器

基本选择器 基本选择器 : 标签选择器 , 类选择器 , ID选择器 标签选择器 代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEed…

小企业软件项目流程六步法

小企业软件项目流程六步法&#xff0c;很有效 软件项目的沟通成本是巨大的 软件生产是非常特殊的一套流程 没有过程控制&#xff0c;最终一定失控或废弃 趣讲大白话&#xff1a;输入垃圾&#xff0c;输出也是垃圾 【趣讲信息科技188期】 **************************** 软件行业…

九、(补充文章四)Arcgis实现深度学习训练样本数据的批量制作——只靠原图+shp如何批量制作样本图片

之前写了一些个深度学习系列文 其中先是单张样本的制作方法 最后通过构造模型批量处理 大大提高了生成样本的速度 四、Arcgis实现深度学习河流训练样本数据的制作(使用软件批量获取样本图片)——对已经获取到的完整面状样本数据进行处理 但是这个方法不仅仅需要shp和原图 还需要…

在不到200行的HTML代码中,实现老板要求为他的孩子绘制一个童话乐园:七彩彩虹、微笑笑脸和魔法树

文章目录 准备工作1.绘制七彩彩虹2.绘制微笑笑脸3.绘制多变的魔法树 结语 欢迎来到童话乐园&#xff01;这里有一些有趣的绘图功能&#xff0c;让你在代码的世界中感受童话般的乐趣。本篇博文将介绍如何使用代码来绘制七彩彩虹、微笑笑脸和魔法树。让我们一起来探索吧&#xff…

vector 练习

目录 一、创建动态二维数组的方法 0x01 C语言法 0x02 C法 二、 杨辉三角 三、电话号码的数字组合 一、创建动态二维数组的方法 0x01 C语言法 int** p (int**)malloc(sizeof(int*) * M);//创建M行的数组,每一行都是一个数组 for(size_t i 0;i < M;i) {p[i] (int*)mal…

Redis主从集群与哨兵集群

一、Redis 哨兵集群原理 Redis 哨兵集群是一种高可用性的解决方案&#xff0c;用于监控 Redis 实例的状态并在实例出现故障时自动进行故障转移。 Redis 哨兵集群由多个哨兵实例组成&#xff0c;每个哨兵实例都运行在独立的服务器上。每个哨兵实例都会周期性地向 Redis 实例发…

Linux内存初始化-启动阶段的内存初始化

本文代码基于ARM64平台, Linux kernel 5.15 在加载kernel 之前&#xff0c; kernel对于系统是有一定要求的&#xff0c;明确规定了boot阶段必须要把MMU关闭&#xff1a; arch/arm64/kernel/head.S/** Kernel startup entry point.* ---------------------------** The require…

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

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

入门大数据就业前景怎么样?

时势造英雄&#xff0c;对个人而言亦是如此。跟随趋势&#xff0c;找准自己未来发力的赛道&#xff0c;在合适的时间干合适的事&#xff0c;就是抓住自己的未来。 猎聘大数据研究院发布了《2022未来人才就业趋势报告》 从排名来看&#xff0c;2022年1-4月各行业中高端人才平均…

mac m1/m2 安装 ps 2023 插件无法显示扩展界面

碎碎念&#xff1a;一直在踩坑的路上&#xff0c;甚至想休息时间玩一会儿 ps 都能踩坑 问题描述 新的 m2 芯片 mac 安装了色环插件后&#xff0c;在窗口界面中没有找到扩展&#xff0c;且在首选项->增效工具的旧版扩展也是灰色的 题外话&#xff1a;记录一下 mac 的 photo…

2023尚上优选-社区团购 优选电商Spring Cloud Alibaba

尚上优选2023最新企业级微服务架构项目 分布式微服务后端VUE、小程序 尚上优选是真实居住社区内居民团体的一种互联网线上线下购物消费行为&#xff0c;是依托真实社区的一种区域化、小众化、本地化、网络化的团购形式。简而言之&#xff0c;它是依托社区和团长社交关系实现生…

DHCP与DHCPv6讲解

目录 DHCP DHCP端口号 DHCP报文 DHCP工作过程 DHCP租期续租 DHCPv6 DHCPv6端口号 DHCPv6报文 DHCPv6工作原理 DHCP DHCP端口号 DHCP主要有两个端口号&#xff0c;分别是UDP67和UDP68 DHCP客户端向DHCP服务器发送报文时采用68端口号&#xff0c;DHCP服务器向DHCP客户端…