【设计模式-6】建造者模式的实现与框架中的应用

news2024/11/18 23:32:41

 建造者模式又被成为生成器模式,是一种使用频率比较低,相对复杂的创建型模式,在很多源码框架中可以看到建造者的使用场景,稍后我们会在本文末尾展示几个框架的使用案例。
 建造者模式所构造的对象通常是比较复杂而且庞大的,也是按照既定的构建顺序将对象中的各个属性组装起来。与工厂模式不同的是,建造者模式主要目的是把繁琐的构建过程从产品类和工厂类中抽离出来,进一步解耦,最终实现用一套标准的制造工序制造出不同的产品。

1. 定义

建造者模式 的官方定义是:将一个复杂对象的构建和表示分离,使得同样的构建过程可以创建不同的表示。建造者模式一步一步地创建一个复杂的对象,它允许用户通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的构造细节。

 官方的表达还是挺难理解的,总体意思就是,构建过程固定,构建使用的每个组件都可以通过继承或者实现 呈现出多态的特性,这依赖于抽象建造者接口和具体的建造者实现类两个组件来表示。

 在建造者模式中,通常包含4个角色:

  • 产品:复杂的产品类,建造者的最终产物,构建过程相对复杂,需要很多组件组装而成。
  • 抽象建造者:建造者接口,包含两个方便,一个是构成产品的各个属性,一个是各个属性的构建步骤方法。
  • 建造者实现:建造者接口的实现类,可以有多个,对应不同的组件实现细节,但是不会包含构建的顺序逻辑。
  • 指导者:客户端依赖的创建产品的指导者类,指导者类会通过建造者接口引入具体的建造者实现,并且包含了具体的构建顺序。

2. 代码实现

 建造者的实现逻辑想对复杂,下面展示一个酒水制造的代码案例,某个酒厂生产啤酒和红酒两类产品,对应上述的四个角色,代码实现如下:

  • 1. 产品
// 产品对象
public class Wine {

    // 用list封装制作工艺,表示制作的顺序是固定的
    private List<String> list = new ArrayList();
    // 1.准备原材料
    public void setPrepareMaterial(String prepareMaterial) {
        list.add(prepareMaterial);
    }
    // 2.制作工艺
    public void setCraftsmanShip(String craftsmanShip) {
        list.add(craftsmanShip);
    }
    // 3.出厂包装
    public void setFactoryPackage(String factoryPackage) {
        list.add(factoryPackage);
    }

    @Override
    public String toString() {
        return "Wine{" + "list=" + list + '}';
    }
}
  • 2. 抽象建造者
// 抽象的建造者
public abstract class AbstractBuilder {
    // 自定义产品
    Wine wine = new Wine();

    // 设置原材料
    public abstract void setPrepareMaterial();
    // 设置制作工艺
    public abstract void setCraftsmanShip();
    // 设置出厂包装
    public abstract void setFactoryPackage();

    // 获取产品 - 注意这里是个钩子方法,子类可以不实现
    public Wine getProduct() {
        return wine;
    }
}
  • 3. 建造者实现
// 啤酒建造者-具体的子类实现
public class BeerBuilder extends AbstractBuilder {

    @Override
    public void setPrepareMaterial() {
        wine.setPrepareMaterial("1.准备原材料:小麦 + 豆子");
    }

    @Override
    public void setCraftsmanShip() {
        wine.setCraftsmanShip("2.制作工艺:发酵 + 蒸馏");
    }

    @Override
    public void setFactoryPackage() {
        wine.setFactoryPackage("3.出厂包装:易拉罐 + 纸箱");
    }
}

// 葡萄酒制造者 - 具体的子类实现
public class GrapeBuilder extends AbstractBuilder{

    @Override
    public void setPrepareMaterial() {
        wine.setPrepareMaterial("1.准备原材料:葡萄 + 酵母");
    }

    @Override
    public void setCraftsmanShip() {
        wine.setCraftsmanShip("2.制作工艺:发酵 + 地下贮存");
    }

    @Override
    public void setFactoryPackage() {
        wine.setFactoryPackage("3.出厂包装:玻璃瓶 + 木箱");
    }
}
  • 4. 指导者
// 指挥者 - 构造产品对象
public class Director {

    private AbstractBuilder abstractBuilder;

    public Director(AbstractBuilder abstractBuilder) {
        this.abstractBuilder = abstractBuilder;
    }

    // 这里固定制造的工序,按照固定步骤执行
    public Wine createProduct() {
        // 第一步:设置原材料
        abstractBuilder.setPrepareMaterial();
        // 第二步:设置制作工艺
        abstractBuilder.setCraftsmanShip();
        // 第三步:设置出厂包装
        abstractBuilder.setFactoryPackage();
        // 第四步,返回产品
        return abstractBuilder.getProduct();
    }
}
  • 客户端
// 客户端
public class Client {
    public static void main(String[] args) {
        // 啤酒制造
        Director beerDirector = new Director(new BeerBuilder());
        Wine beer = beerDirector.createProduct();
        System.out.println(beer);
        
        // 红酒制造
        Director grapeDirector = new Director(new GrapeBuilder());
        Wine grape = grapeDirector.createProduct();
        System.out.println(grape);
    }
}

3. UML类图

 对照上述的代码Demo,我们来把这个案例的类图画出来,对应如下:
在这里插入图片描述

4. 简化

 因为建造者是属于相对复杂的一种模式,在实际的应用当中有很多种简化的写法,比如可以忽略指导者类、建造者接口等等。下面是一种实现方式,这种实现方式在很多源码中可以看到具体实现。

 通常在产品类的构造函数参数超过4个,而且这些参数中有一些是必填项,考虑使用这种建造者模式。现在我们来构建一个电脑的产品对象。

// 电脑产品对象
public class Computer {
    private String cpu; // 必选
    private String ram; // 必选
    private String keyboard; // 可选
    private String mouse; // 可选
    private String display; // 可选
}

 这种bean类的属性设置有两种方式,一个是构造函数入参,一个是通过set()方法入参,这两种方式都有些问题。构造函数入参,当参数较多的时候,类型相同的情况下,属性的顺序容易混乱;第二个中set()方式,一个对象会支持在很多模块中设置,因为类中的属性是可以分布设置的,所以容易出现属性状态变化造成错误。

 下面是一种简易的建造者实现方式(在实际场景中可能属性的构建过程很复杂):

  • 建造者模式
// 电脑产品对象
public class Computer {
    private final String cpu; // 必选
    private final String ram; // 必选
    private final String keyboard; // 可选
    private final String mouse; // 可选
    private final String display; // 可选

    private Computer(Computer.Builder builder) {
        this.cpu = builder.cpu;
        this.ram = builder.ram;
        this.keyboard = builder.keyboard;
        this.mouse = builder.mouse;
        this.display = builder.display;
    }

    public static Computer.Builder builder(String cpu, String ram) {
        return new Computer.Builder(cpu, ram);
    }

    public static class Builder {
        private String cpu; // 必选
        private String ram; // 必选
        private String keyboard; // 可选
        private String mouse; // 可选
        private String display; // 可选

        Builder(String cpu, String ram) {
            this.cpu = cpu;
            this.ram = ram;
        }

        public Builder cpu(String cpu) {
            this.cpu = cpu;
            return this;
        }

        public Builder ram(String ram) {
            this.ram = ram;
            return this;
        }

        public Builder keyboard(String keyboard) {
            this.keyboard = keyboard;
            return this;
        }

        public Builder mouse(String mouse) {
            this.mouse = mouse;
            return this;
        }

        public Builder display(String display) {
            this.display = display;
            return this;
        }

        public Computer build() {
            return new Computer(this);
        }
    }

    @Override
    public String toString() {
        return "Computer{" + "cpu='" + cpu + '\'' + ", ram='" + ram + '\'' + ", keyboard='" + keyboard + '\''
            + ", mouse='" + mouse + '\'' + ", display='" + display + '\'' + '}';
    }
}
  • 客户端实现
// 客户端
public class Client {
    public static void main(String[] args) {
        Computer computer = Computer.builder("cpu", "ram")
            .cpu("cpu-1").mouse("mouse").display("display").build();
        System.out.println(computer);
    }
}

5. 总结

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

  • 首先建造者模式使得客户端和产品创建的过程解耦,客户端可以不知道产品创建的内部细节;
  • 构建过程固定的情况下,构建的细节可以多变性,这就可以很方便的增加不同的构造实现,符合开闭原则;
  • 将每个属性的构建过程分解在不同的方法中,精细化管理,高内聚才能低耦合,符合单一职责。

6. 框架中的应用

  • StringBuilder和StringBuffer
  • SqlSessionBuilder
  • Lombok的@Builder注解

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

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

相关文章

C++ n皇后问题 || 深度优先搜索模版题

n− 皇后问题是指将 n 个皇后放在 nn 的国际象棋棋盘上&#xff0c;使得皇后不能相互攻击到&#xff0c;即任意两个皇后都不能处于同一行、同一列或同一斜线上。 现在给定整数 n &#xff0c;请你输出所有的满足条件的棋子摆法。 输入格式 共一行&#xff0c;包含整数 n 。 …

SpringCloud 之HttpClient、HttpURLConnection、OkHttpClient切换源码

SpringCloud 之HttpClient、HttpURLConnection、OkHttpClient切换源码 HttpClient、HttpURLConnection、OkHttpClient区别切换HttpClient 源码分析总结切换HttpClient源码验证切换是否成功okHttpClient 切换源码分析总结 okHttpClient 切换源码同时开启 okHttp 与httpClient 会…

【数字人】8、EAT | 为数字人引入情感表情(ICCV2023)

论文&#xff1a;Efficient Emotional Adaptation for Audio-Driven Talking-Head Generation 代码&#xff1a;https://yuangan.github.io/eat/ 出处&#xff1a;ICCV2023 特点&#xff1a;能引入表情&#xff0c;但无法眨眼&#xff0c;需要 音频 pose 图片 同时作为输入…

Java文件自动生成文档

说明 此文章根据Gemini Pro 生成资料整理。 生成文档 javadoc -d mydoc -author -version HelloWorld.java javadoc -d mydoc -author -version HelloWorld.java 命令用于生成 Java 源文件的javadoc文档&#xff0c;并将javadoc文档输出到 mydoc 目录中。 javadoc&#xf…

Linux学习之网络编程2(socket,简单C/S模型)

写在前面 Linux网络编程我是看视频学的&#xff0c;Linux网络编程&#xff0c;看完这个视频大概网络编程的基础差不多就掌握了。这个系列是我看这个Linux网络编程视频写的笔记总结。 网络字节序 小端法&#xff1a;pc本地存储&#xff0c;高位存高地址&#xff0c;低位存低地…

AI技术已经发现了一种新材料,可以在电池制造中减少对锂的需求

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Docker 安装:在linux系统CentOS7 版本 安装Docker

目录 一&#xff0c;Docker介绍&#xff1a; 1.1Docker是什么&#xff1f; 1.2Docker组成 二&#xff0c;Docker安装&#xff1a; 三&#xff0c;Docker基本使用 3.1服务 3.2镜像 3.3容器 &#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&am…

UniApp调试支付宝沙箱(安卓)

先看下这里完整的交互的图&#xff1a;小程序文档 - 支付宝文档中心 一、打包 不管怎样&#xff0c;先打个包先。可以直接使用云端证书、云端打包&#xff0c;只需要指定包名即可。 二、在支付宝开放平台创建应用 这个参考官方的过程就可以了&#xff0c;只要有刚才打的包&…

【REST2SQL】08 日志重构增加输出到文件log.txt

【REST2SQL】01RDB关系型数据库REST初设计 【REST2SQL】02 GO连接Oracle数据库 【REST2SQL】03 GO读取JSON文件 【REST2SQL】04 REST2SQL第一版Oracle版实现 【REST2SQL】05 GO 操作 达梦 数据库 【REST2SQL】06 GO 跨包接口重构代码 【REST2SQL】07 GO 操作 Mysql 数据库 原来…

解决:ModuleNotFoundError: No module named ‘pymysql’

解决&#xff1a;ModuleNotFoundError: No module named ‘pymysql’ 文章目录 解决&#xff1a;ModuleNotFoundError: No module named pymysql背景报错问题报错翻译报错位置代码报错原因解决方法方法一&#xff0c;直接安装方法二&#xff0c;手动下载安装方法三&#xff0c;…

php多小区智慧物业管理系统源码带文字安装教程

多小区智慧物业管理系统源码带文字安装教程 运行环境 服务器宝塔面板 PHP 7.0 Mysql 5.5及以上版本 Linux Centos7以上 统计分析以小区为单位&#xff0c;统计如下数据&#xff1a;小区总栋数、小区总户数、小区总人数、 小区租户数量、小区每月收费金额统计、小区车位统计、小…

C#,入门教程(14)——字符串与其他数据类型的转换

上一篇&#xff1a; C#&#xff0c;入门教程(13)——字符&#xff08;char&#xff09;及字符串&#xff08;string&#xff09;的基础知识https://blog.csdn.net/beijinghorn/article/details/123928151 数据只有可视化才能更好地体现其价值&#xff0c;因而 string 与 image…

【软件测试】学习笔记-微服务模式下API测试

这篇文章探讨当下最热门的技术领域的API测试&#xff0c;即微服务模式下的API测试。微服务架构下&#xff0c;API测试的最大挑战来自于庞大的测试用例数量&#xff0c;以及微服务之间的相互耦合。这篇文章探讨这两个问题的本质&#xff0c;以及如何基于消费者契约的方法来应对这…

SQL-数据类型

目录 DDL-表操作-数据类型 数值类型 字符串类型 举例&#xff1a; 案例&#xff1a; 日期时间类型 案例 表操作-案例 &#x1f389;欢迎您来到我的MySQL基础复习专栏 ☆* o(≧▽≦)o *☆哈喽~我是小小恶斯法克&#x1f379; ✨博客主页&#xff1a;小小恶斯法克的博客 &a…

【常用的简单功能及算法】拦截器 加盐算法 深克隆 时间日期格式化 加盐算法 sql分页算法 验证码

1.实现拦截器 Interceptor (以登录拦截器为例) 1.1 写一个登录拦截器普通类 实现HandlerInterceptor接口重写preHandle方法 //检验登录状态拦截器 //实现接口HandlerInterceptor 重写方法preHandle public class LoginInterceptor implements HandlerInterceptor {/** 该方…

网络安全保险发展起始阶段的挑战及应对措施

文章目录 前言一、网络安全保险的有序发展二、当前我国网络安全保险发展的初期态势&#xff08;一&#xff09;网络安全风险类型&#xff08;二&#xff09;网络安全保险的作用&#xff08;三&#xff09;与外国网络安全保费的规模对比 三、我国网络安全保险发展初期面临的挑战…

一天一个设计模式---适配器模式

概念 适配器模式是一种结构型设计模式&#xff0c;用于将一个类的接口转换成客户端所期望的另一个接口。它允许不兼容的接口之间进行协同工作&#xff0c;使得原本由于接口不匹配而无法合作的类能够一起工作。 具体内容 适配器模式主要包括以下几个要素&#xff1a; 目标接…

yolov8 瑞芯微 RKNN 的 C++部署,部署工程难度小、模型推理速度快

之前写过两次yolov8目标检测部署&#xff0c;后续继续思考&#xff0c;针对部署还有优化空间&#xff0c;本示例的部署方式优化了部署难度&#xff0c;加快了模型推理速度&#xff08;略微增加了后处理的时耗&#xff09;。 特别说明&#xff1a;如有侵权告知删除&#xff0c;…

半Happy的一天

终于差不多将SWMM模型与LisFlood模型耦合运转起来了 MDL的雏型也出来了&#xff0c;注册了模型方法和参数&#xff0c;差一个方法参数 晚上和师兄聊了聊未来规划&#xff0c;回顾了这半年研究生生涯的“拍烂”生活&#xff08;其实也没特别摆烂&#xff0c;还是学了不少东西&…

服务器数据传输安全如何保障?保障意义是什么?

数据安全&#xff0c;是指通过采取必要措施确保数据处于有效保护和合法利用的状态&#xff0c;以及具备保障持续安全状态的能力。数据安全应保证数据生产、存储、传输、访问、使用、销毁、公开等全过程的安全&#xff0c;并保证数据处理过程的保密性、完整性、可用性。无论是互…