JAVA设计模式之建造者模式详解

建造者模式

1 建造者模式介绍

建造者模式 (builder pattern), 也被称为生成器模式 , 是一种创建型设计模式.

  • 定义: 将一个复杂对象的构建与表示分离,使得同样的构建过程可以创建不同的表示。

**建造者模式要解决的问题 **

建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

比如: 一辆汽车是由多个部件组成的,包括了车轮、方向盘、发动机等等.对于大多数用户而言,并不需要知道这些部件的装配细节,并且几乎不会使用单独某个部件,而是使用一辆完整的汽车.而建造者模式就是负责将这些部件进行组装让后将完整的汽车返回给用户.

在这里插入图片描述

2 建造者模式原理

建造者(Builder)模式包含以下4个角色 :

  • 抽象建造者类(Builder):这个接口规定要实现复杂对象的哪些部分的创建,并不涉及具体的部件对象的创建。

  • 具体建造者类(ConcreteBuilder):实现 Builder 接口,完成复杂产品的各个部件的具体创建方法。在构造过程完成后,提供一个方法,返回创建好的负责产品对象。

  • 产品类(Product):要创建的复杂对象 (包含多个组成部件).

  • 指挥者类(Director):调用具体建造者来创建复杂对象的各个部分,在指导者中不涉及具体产品的信息,只负责保证对象各部分完整创建或按某种顺序创建(客户端一般只需要与指挥者进行交互)。

在这里插入图片描述

3 建造者模式实现方式1

创建共享单车

生产自行车是一个复杂的过程,它包含了车架,车座等组件的生产。而车架又有碳纤维,铝合金等材质的,车座有橡胶,真皮等材质。对于自行车的生产就可以使用建造者模式。

这里Bike是产品,包含车架,车座等组件;Builder是抽象建造者,MobikeBuilder和HelloBuilder是具体的建造者;Director是指挥者。类图如下:

在这里插入图片描述

具体产品

public class Bike {//车架private String frame;//座椅private String seat;public String getFrame() {return frame;}public void setFrame(String frame) {this.frame = frame;}public String getSeat() {return seat;}public void setSeat(String seat) {this.seat = seat;}
}

构建者类

public abstract class Builder {protected Bike mBike = new Bike();public abstract void buildFrame();public abstract void buildSeat();public abstract Bike createBike();
}public class HelloBuilder extends Builder {@Overridepublic void buildFrame() {mBike.setFrame("碳纤维车架");}@Overridepublic void buildSeat() {mBike.setSeat("橡胶车座");}@Overridepublic Bike createBike() {return mBike;}
}public class MobikeBuilder extends Builder {@Overridepublic void buildFrame() {mBike.setFrame("铝合金车架");}@Overridepublic void buildSeat() {mBike.setSeat("真皮车座");}@Overridepublic Bike createBike() {return mBike;}
}

指挥者类

public class Director {private Builder mBuilder;public Director(Builder builder) {this.mBuilder = builder;}public Bike construct() {mBuilder.buildFrame();mBuilder.buildSeat();return mBuilder.createBike();}
}

客户端

public class Client {public static void main(String[] args) {showBike(new HelloBuilder());showBike(new MobikeBuilder());}private static void showBike(Builder builder) {Director director = new Director(builder);Bike bike = director.construct();System.out.println(bike.getFrame());System.out.println(bike.getSeat());}
}

4 建造者模式实现方式2

建造者模式除了上面的用途外,在开发中还有一个常用的使用方式,就是当一个类构造器需要传入很多参数时,如果创建这个类的实例,代码可读性会非常差,而且很容易引入错误,此时就可以利用建造者模式进行重构。

  1. 构造方法创建复杂对象的问题
  • 构造方法如果参数过多,代码的可读性和易用性都会变差. 在使用构造函数时,很容易搞错参数的顺序,传递进去错误的参数值,导致很有隐蔽的BUG出现.
package com.demo.example02;/*** MQ连接客户端**/
public class RabbitMQClient1 {private String host = "127.0.0.1";private int port = 5672;private int mode;private String exchange;private String queue;private boolean isDurable = true;int connectionTimeout = 1000;//构造方法参数过多,代码的可读性和易用性太差,在使用构造函数时,很容易搞错顺序,传递错误的参数值,导致很有隐蔽的BUGpublic RabbitMQClient1(String host, int port, int mode, String exchange, String queue, boolean isDurable, int connectionTimeout) {this.host = host;this.port = port;this.mode = mode;this.exchange = exchange;this.queue = queue;this.isDurable = isDurable;this.connectionTimeout = connectionTimeout;if(mode == 1){ //工作队列模式不需要设计交换机,但是队列名称一定要有if(exchange != null){throw new RuntimeException("工作队列模式无需设计交换机");}if(queue == null || queue.trim().equals("")){throw new RuntimeException("工作队列模式名称不能为空");}if(isDurable == false){throw new RuntimeException("工作队列模式必须开启持久化");}}else if(mode == 2){ //路由模式必须设计交换机,但是不能设计队列if(exchange == null){throw new RuntimeException("路由模式下必须设置交换机");}if(queue != null){throw new RuntimeException("路由模式无须设计队列名称");}}//其他验证方式,}public void sendMessage(String msg){System.out.println("发送消息......");}public static void main(String[] args) {//每一种模式,都需要根据不同的情况进行实例化,构造方法会变得过于复杂.RabbitMQClient1 client1 = new RabbitMQClient1("192.168.52.123",5672,2,"sample-exchange",null,true,5000);client1.sendMessage("Test-MSG");}
}
  1. set方法创建复杂对象的问题
  • set方式设置对象属性时,存在中间状态,并且属性校验时有前后顺序约束,逻辑校验的代码找不到合适的地方放置.

    比如下面的代码, 创建对象后使用set 的方式,那就会导致在第一个 set 之后,对象处于无效状态

    Rectangle r = new Rectangle (); //无效状态

    r.setWidth(2); //无效状态

    r.setHeight(3); //有效状态

  • set方法还破坏了"不可变对象"的密闭性 .

    不可变对象: 对象创建好了,就不能再修改内部的属性值,下面的client类就是典型的不可变对象,创建好的连接对象不能再改动

package com.demo.example02;/*** MQ连接客户端**/
public class RabbitMQClient2 {private String host = "127.0.0.1";private int port = 5672;private int mode;private String exchange;private String queue;private boolean isDurable = true;int connectionTimeout = 1000;//私有化构造方法private RabbitMQClient2() {}public String getExchange() {return exchange;}public void setExchange(String exchange) {if(mode == 1){ //工作队列模式不需要设计交换机,但是队列名称一定要有if(exchange != null){throw new RuntimeException("工作队列模式无需设计交换机");}if(queue == null || queue.trim().equals("")){throw new RuntimeException("工作队列模式名称不能为空");}if(isDurable == false){throw new RuntimeException("工作队列模式必须开启持久化");}}else if(mode == 2){ //路由模式必须设计交换机,但是不能设计队列if(exchange == null){throw new RuntimeException("路由模式下必须设置交换机");}if(queue != null){throw new RuntimeException("路由模式无须设计队列名称");}}//其他验证方式,this.exchange = exchange;}public String getHost() {return host;}public void setHost(String host) {this.host = host;}public int getPort() {return port;}public void setPort(int port) {this.port = port;}public int getMode() {return mode;}public void setMode(int mode) {if(mode == 1){ //工作队列模式不需要设计交换机,但是队列名称一定要有if(exchange != null){throw new RuntimeException("工作队列模式无需设计交换机");}if(queue == null || queue.trim().equals("")){throw new RuntimeException("工作队列模式名称不能为空");}if(isDurable == false){throw new RuntimeException("工作队列模式必须开启持久化");}}else if(mode == 2){ //路由模式必须设计交换机,但是不能设计队列if(exchange == null){throw new RuntimeException("路由模式下必须设置交换机");}if(queue != null){throw new RuntimeException("路由模式无须设计队列名称");}}this.mode = mode;}public String getQueue() {return queue;}public void setQueue(String queue) {this.queue = queue;}public boolean isDurable() {return isDurable;}public void setDurable(boolean durable) {isDurable = durable;}public int getConnectionTimeout() {return connectionTimeout;}public void setConnectionTimeout(int connectionTimeout) {this.connectionTimeout = connectionTimeout;}public void sendMessage(String msg){System.out.println("发送消息......");}/*** set方法的好处是参数的设计更加的灵活,但是通过set方式设置对象属性时,对象有可能存在中间状态(无效状态),* 并且进行属性校验时有前后顺序约束.* 怎么保证灵活设置参数又不会存在中间状态呢? 答案就是: 使用建造者模式*/public static void main(String[] args) {RabbitMQClient2 client2 = new RabbitMQClient2();client2.setHost("192.168.52.123");client2.setQueue("queue");client2.setMode(1);client2.setDurable(true);client2.sendMessage("Test-MSG2");}
}
  1. 建造者方式实现

建造者使用步骤如下:

  1. 目标类的构造方法要传入Builder对象
  2. Builder建造者类位于目标类内部,并且使用static修饰
  3. Builder建造者对象提供内置的各种set方法,注意set方法返回的是builder对象本身
  4. Builder建造者类提供build()方法实现目标对象的创建
public class 目标类{//目标类的构造方法需要传入Builder对象public 目标类(Builder builder){}public 返回值 业务方法(参数列表){}//Builder建造者类位于目标类内部,并且使用static修饰public static class Builder(){//Builder建造者对象提供内置的各种set方法,注意set方法返回的是builder对象本身private String xxx;public Builder setXxx(String xxx){this.xxx = xxx;return this;}//Builder建造者类提供build()方法实现目标对象的创建public 目标类 build(){//校验return new 目标类(this);}}
}

重写案例代码

/*** 建造者模式**/
public class RabbitMQClient {//私有构造方法private RabbitMQClient(Builder builder) {}public static class Builder{//属性密闭性,保证对象不可变private String host = "127.0.0.1";private int port = 5672;private int mode;private String exchange;private String queue;private boolean isDurable = true;int connectionTimeout = 1000;public Builder setHost(String host) {this.host = host;return this;}public Builder setPort(int port) {this.port = port;return this;}public Builder setMode(int mode) {this.mode = mode;return this;}public Builder setExchange(String exchange) {this.exchange = exchange;return this;}public Builder setQueue(String queue) {this.queue = queue;return this;}public Builder setDurable(boolean durable) {isDurable = durable;return this;}public Builder setConnectionTimeout(int connectionTimeout) {this.connectionTimeout = connectionTimeout;return this;}//返回构建好的复杂对象public RabbitMQClient build(){//首先进行校验if(mode == 1){ //工作队列模式不需要设计交换机,但是队列名称一定要有if(exchange != null){throw new RuntimeException("工作队列模式无需设计交换机");}if(queue == null || queue.trim().equals("")){throw new RuntimeException("工作队列模式名称不能为空");}if(isDurable == false){throw new RuntimeException("工作队列模式必须开启持久化");}}else if(mode == 2){ //路由模式必须设计交换机,但是不能设计队列if(exchange == null){throw new RuntimeException("路由模式下必须设置交换机");}if(queue != null){throw new RuntimeException("路由模式无须设计队列名称");}}return new RabbitMQClient(this);}}public void sendMessage(String msg){System.out.println("发送消息......");}
}

测试

public class MainAPP {public static void main(String[] args) {//使用链式编程设置参数RabbitMQClient client = new RabbitMQClient.Builder().setHost("192.168.52.123").setMode(2).setExchange("text-exchange").setPort(5672).setDurable(true).build();client.sendMessage("Test");}
}

5 建造者模式总结

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

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

  1. 建造者模式的优缺点
  • 优点

    • 建造者模式的封装性很好。使用建造者模式可以有效的封装变化,在使用建造者模式的场景中,一般产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指挥者类中对整体而言可以取得比较好的稳定性。
    • 在建造者模式中,客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。
    • 可以更加精细地控制产品的创建过程 。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程。
    • 建造者模式很容易进行扩展。如果有新的需求,通过实现一个新的建造者类就可以完成,基本上不用修改之前已经测试通过的代码,因此也就不会对原有功能引入风险。符合开闭原则。
  • 缺点

    • 建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
  1. 应用场景
  • 建造者(Builder)模式创建的是复杂对象,其产品的各个部分经常面临着剧烈的变化,但将它们组合在一起的算法却相对稳定,所以它通常在以下场合使用。
    • 创建的对象较复杂,由多个部件构成,各部件面临着复杂的变化,但构件间的建造顺序是稳定的。
    • 创建复杂对象的算法独立于该对象的组成部分以及它们的装配方式,即产品的构建过程和最终的表示是独立的。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

大数据应用对企业的价值

目录 一、大数据应用价值 1.1 大数据技术分析 1.2 原有技术场景的优化 1.2.1 数据分析优化 1.2.2 高并发数据处理 1.3 通过大数据构建新需求 1.3.1 智能推荐 1.3.2 广告系统 1.3.3 产品/流程优化 1.3.4 异常检测 1.3.5 智能管理 1.3.6 人工智能和机器学习 二、大数…

降准是什么意思?降准对股市有哪些影响?

降准是什么意思 降准,全称为“中央银行调低法定存款准备率”,是指中央银行降低法定存款准备率,以增加银行的可用资金,从而增加市场的流动性。 具体来说,存款准备金是商业银行为了应对储户取款和清算时准备的资金&…

【MySQL进阶之路】MySQL 中表空间和数据区的概念以及预读机制

欢迎关注公众号(通过文章导读关注:【11来了】),及时收到 AI 前沿项目工具及新技术的推送! 在我后台回复 「资料」 可领取编程高频电子书! 在我后台回复「面试」可领取硬核面试笔记! 文章导读地址…

火车可视化调车系统

列车在调车作业时,当机车头在尾部推动车厢时,司机室一人操控机车,车厢前端配备两名挂梯随车运行调车员,调车员人为分析行车方向是否有障碍、轨道行人等紧急情况,通过对讲机通知司机控制停车。由于司机无法直观观察列车…

django admin 自定义界面时丢失左侧导航 nav_sidebar

只显示了自定义模板的内容,左侧导航没有显示出来。 原因:context 漏掉了,要补上。 # 错误写法(左侧导航不显示)def changelist_view(self, request, extra_contextNone):form CsvImportForm()payload {"form&qu…

C语言指针运算

指针运算 指针加法意味着地址向上移动若干个目标指针减法意味着地址向下移动若干个目标示例: int a 100; int *p &a; // 指针 p 指向整型变量 aint *k1 p 2; // 向上移动 2 个目标(2个int型数据) int *k2 p - 3; // 向下移动 3 个…

无广告iOS获取设备UDID 简单方便快捷

ps: 为啥不用蒲公英了,就是因为有广告了,获取个UDID还安装游戏,真恶心?,所以找了新的获取UDID都方法,网页直接获取就可以,不会安装软件。 UDID 是一种 iOS 设备的特殊识别码。除序号之外&…

形态学算法之边界提取的简单python实现——图像处理

原理 图像处理中的边界提取是一项基本而重要的任务,主要用于识别和提取图像中物体的轮廓或边界。 具体流程 1.边缘检测 边界提取的第一步通常是边缘检测。边缘是图像亮度变化显著的地方,是物体与背景或不同物体间的分界线。边缘检测算法通过识别图像中…

基于高通滤波器的ECG信号滤波及心率统计matlab仿真

目录 1.课题概述 2.系统仿真结果 3.核心程序与模型 4.系统原理简介 4.1 ECG信号简介 4.2 高通滤波器原理 4.3 心率统计 5.完整工程文件 1.课题概述 通过高通滤波器对ECG信号进行滤波,然后再统计其心率。 2.系统仿真结果 3.核心程序与模型 版本&#xff1a…

手把手教:如何搭建UI自动化测试框架(使用篇Ⅱ)

UI自动化测试框架是有很多的。我们以pytestseleniumallurePO模式为例子给大家简要说明一下。搭建步骤有下面几步: 1、工具环境 2. 依赖包 3. 工程目录 4. 脚本书写步骤 a ) 初始化代码 b ) 一个测试用例脚本编写过程 c ) 引入数据驱动 d ) 引入日志 e ) 生成测试报…

Android用setRectToRect实现Bitmap基于Matrix矩阵scale缩放RectF动画,Kotlin(二)

Android用setRectToRect实现Bitmap基于Matrix矩阵scale缩放RectF动画,Kotlin(二) 文章 https://zhangphil.blog.csdn.net/article/details/135980821 实现了基于Matrix缩放Bitmap的动画,但是从左上角(0,0)位…

如何开发一个游戏平台?

随着科技的进步和互联网的普及,游戏行业正在迅速发展。游戏平台的开发已成为游戏行业的一个重要组成部分。开发一个游戏平台需要深入了解游戏行业,掌握相关技术,并具备创新思维。以下是一些关于如何开发一个游戏平台的建议: 市场调…