Java设计模式14:建造者模式
什么是建造者模式
发现很多框架的源码使用了建造者模式,看了一下觉得挺实用的,就写篇文章学习一下,顺便分享给大家。
建造者模式是什么呢?用一句话概括就是建造者模式的目的是为了分离对象的属性与创建过程,是的,只要记住并理解红字的几个部分,建造者模式你就懂了。
为什么需要建造者模式
建造者模式是构造方法的一种替代方案,为什么需要建造者模式,我们可以想,假设有一个对象里面有20个属性:
- 属性1
- 属性2
- ...
- 属性20
对开发者来说这不是疯了,也就是说我要去使用这个对象,我得去了解每个属性的含义,然后在构造函数或者Setter中一个一个去指定。更加复杂的场景是,这些属性之间是有关联的,比如属性1=A,那么属性2只能等于B/C/D,这样对于开发者来说更是增加了学习成本,开源产品这样的一个对象相信不会有太多开发者去使用。
为了解决以上的痛点,建造者模式应运而生,对象中属性多,但是通常重要的只有几个,因此建造者模式会让开发者指定一些比较重要的属性或者让开发者指定某几个对象类型,然后让建造者去实现复杂的构建对象的过程,这就是对象的属性与创建分离。这样对于开发者而言隐藏了复杂的对象构建细节,降低了学习成本,同时提升了代码的可复用性。
虽然感觉基本说清楚了,但还是有点理论,具体往下看一下例子。
建造者模式代码示例
举一个实际场景的例子:
大家知道一辆车是很复杂的,有发动机、变速器、轮胎、挡风玻璃、雨刮器、气缸、方向盘等等无数的部件。 用户买车的时候不可能一个一个去指定我要那种类型的变速器、我要一个多大的轮胎、我需要长宽高多少的车,这是不现实的 通常用户只会和销售谈我需要什么什么样的类型的车,马力要不要强劲、空间是否宽敞,这样销售就会根据用户的需要去推荐一款具体的车 这就是一个典型建造者的场景:车是复杂对象,销售是建造者。我告诉建造者我需要什么,建造者根据我的需求给我一个具体的对象
根据这个例子,我们定义一个简单的汽车对象:
public class Car { // 尺寸
private String size; // 方向盘
private String steeringWheel; // 底座
private String pedestal; // 轮胎
private String wheel; // 排量
private String displacement; // 最大速度
private String maxSpeed; public String getSize() {
return size;
} public void setSize(String size) {
this.size = size;
} public String getSteeringWheel() {
return steeringWheel;
} public void setSteeringWheel(String steeringWheel) {
this.steeringWheel = steeringWheel;
} public String getPedestal() {
return pedestal;
} public void setPedestal(String pedestal) {
this.pedestal = pedestal;
} public String getWheel() {
return wheel;
} public void setWheel(String wheel) {
this.wheel = wheel;
} public String getDisplacement() {
return displacement;
} public void setDisplacement(String displacement) {
this.displacement = displacement;
} public String getMaxSpeed() {
return maxSpeed;
} public void setMaxSpeed(String maxSpeed) {
this.maxSpeed = maxSpeed;
} @Override
public String toString() {
return "Car [size=" + size + ", steeringWheel=" + steeringWheel + ", pedestal=" + pedestal + ", wheel=" + wheel
+ ", displacement=" + displacement + ", maxSpeed=" + maxSpeed + "]";
} }
这里简单定义几个参数,然后建造者对象应运而生:
public class CarBuilder { // 车型
private String type; // 动力
private String power; // 舒适性
private String comfort; public Car build() {
Assert.assertNotNull(type);
Assert.assertNotNull(power);
Assert.assertNotNull(comfort); return new Car(this);
} public String getType() {
return type;
} public CarBuilder type(String type) {
this.type = type;
return this;
} public String getPower() {
return power;
} public CarBuilder power(String power) {
this.power = power;
return this;
} public String getComfort() {
return comfort;
} public CarBuilder comfort(String comfort) {
this.comfort = comfort;
return this;
} @Override
public String toString() {
return "CarBuilder [type=" + type + ", power=" + power + ", comfort=" + comfort + "]";
} }
说是建造者,其实也不合适,它只是一个中间对象,用于接收来自外部的信息,比如需要什么样的车型,需要什么样的动力啊这些。
然后大家一定注意到了build方法,这个是建造者模式好像约定俗成的方法名,代表建造,里面把自身对象传给Car,这个构造方法的实现我在第一段代码里面是没有贴的,这段代码的实现为:
public Car(CarBuilder builder) {
if ("紧凑型车".equals(builder.getType())) {
this.size = "大小--紧凑型车";
} else if ("中型车".equals(builder.getType())) {
this.size = "大小--中型车";
} else {
this.size = "大小--其他";
} if ("很舒适".equals(builder.getComfort())) {
this.steeringWheel = "方向盘--很舒适";
this.pedestal = "底座--很舒适";
} else if ("一般舒适".equals(builder.getComfort())) {
this.steeringWheel = "方向盘--一般舒适";
this.pedestal = "底座--一般舒适";
} else {
this.steeringWheel = "方向盘--其他";
this.pedestal = "底座--其他";
} if ("动力强劲".equals(builder.getPower())) {
this.displacement = "排量--动力强劲";
this.maxSpeed = "最大速度--动力强劲";
this.steeringWheel = "轮胎--动力强劲";
} else if ("动力一般".equals(builder.getPower())) {
this.displacement = "排量--动力一般";
this.maxSpeed = "最大速度--动力一般";
this.steeringWheel = "轮胎--动力一般";
} else {
this.displacement = "排量--其他";
this.maxSpeed = "最大速度--其他";
this.steeringWheel = "轮胎--其他";
}
}
这是真实构建对象的地方,无论多复杂的逻辑都在这里实现而不需要暴露给开发者,还是那句核心的话:实现了对象的属性与构建的分离。
这样用起来就很简单了:
@Test
public void test() {
Car car = new CarBuilder().comfort("很舒适").power("动力一般").type("紧凑型车").build(); System.out.println(JSON.toJSONString(car));
}
只需要指定我需要什么什么类型的车,然后具体的每个参数自然根据我的需求列出来了,不需要知道每个细节,我也能得到我需要的东西。
建造者模式在开源框架中的应用
文章的开头有说很多开源框架使用了建造者模式,典型的有Guava的Cache、ImmutableMap,不过感觉MyBatis更为大家熟知,且MyBatis内部大量使用了建造者模式,我们可以一起来看一下。
以原生的MyBatis(即不使用Spring框架进行整合)为例,通常使用MyBatis我们会用以下几句代码:
// MyBatis配置文件路径
String resources = "mybatis_config.xml";
// 获取一个输入流
Reader reader = Resources.getResourceAsReader(resources);
// 获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
// 打开一个会话
SqlSession sqlSession = sqlSessionFactory.openSession();
// 具体操作
...
关键我们看就是这个SqlSessionFactoryBuilder,它的源码核心方法实现为:
public class SqlSessionFactoryBuilder { ... public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
} ... public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
} ... }
因为MyBatis内部是很复杂的,核心类Configuration属性多到爆炸,比如拿数据库连接池来说好了,有POOLED、UNPOOLED、JNDI三种,然后POOLED里面呢又有各种超时时间、连接池数量的设置,这一个一个都要让开发者去设置那简直要命了。因此MyBatis在SqlSessionFactory这一层使用了Builder模式,对开发者隐藏了XML文件解析细节,Configuration内部每个属性赋值细节,开发者只需要指定一些必要的参数(比如数据库地址、用户名密码之类的),就可以直接使用MyBatis了,至于可选参数,配置了就拿开发者配置的,没有配置就默认来一套。
通过这样一种方式,开发者接入MyBatis的成本被降到了最低,这么一种编程方式非常值得大家学习,尤其是自己需要写一些框架的时候。
同样的大家可以看一下Environment,Environment也使用了建造者模式,但是Environment使用建造者模式最大的作用是让用户无法在运行时修改任何环境属性保证了安全与稳定性,同样这也是建造者模式的一种经典实现。
建造者模式的类关系图
其实,建造者模式不像一些设计模式有比较固定或者比较类似的实现方式,它的核心只是分离对象属性与创建,整个实现比较自由,我们可以看到我自己写的造车的例子和SqlSessionFactoryBuilder就明显不是一种实现方式。
看了一些框架源码总结起来,建造者模式的实现大致有两种写法:
这是一种在Builder里面直接new对象的方式,MyBatis的SqlSessionFactoryBuilder就是这种写法,适用于属性之间关联不多且大量属性都有默认值的场景。
另外一种就是间接new的方式了:
我的代码示例,还有例如Guava的Cache都是这种写法,适用于属性之间有一定关联性的场景,例如车的长宽高与轴距都属于车型一类、排量与马力都与性能相关,可以把某几个属性归类,然后让开发者指定大类即可。
总体而言,两种没有太大的优劣之分,在合适的场景下选择合适的写法就好了。
建造者模式的优点及适用场景
建造者模式这种设计模式,优缺点比较明显。从优点来说:
- 客户端不比知道产品内部细节,将产品本身与产品创建过程解耦,使得相同的创建过程可以创建不同的产品对象
- 可以更加精细地控制产品的创建过程,将复杂对象分门别类抽出不同的类别来,使得开发者可以更加方便地得到想要的产品
想了想,说缺点,建造者模式说不上缺点,只能说这种设计模式的使用比较受限:
- 产品属性之间差异很大且属性没有默认值可以指定,这种情况是没法使用建造者模式的,我们可以试想,一个对象20个属性,彼此之间毫无关联且每个都需要手动指定,那么很显然,即使使用了建造者模式也是毫无作用
总的来说,在IT这个行业,复杂的需求、复杂的业务逻辑层出不穷,这必然导致复杂的业务对象的增加,建造者模式是非常有用武之地的。合理分析场景,在合适的场景下使用建造者模式,一定会使得你的代码漂亮得多。
Java设计模式14:建造者模式的更多相关文章
- 折腾Java设计模式之建造者模式
博文原址:折腾Java设计模式之建造者模式 建造者模式 Separate the construction of a complex object from its representation, a ...
- Java 设计模式之建造者模式(四)
原文地址:Java 设计模式之建造者模式(四) 博客地址:http://www.extlight.com 一.前言 今天继续介绍 Java 设计模式中的创建型模式--建造者模式.上篇设计模式的主题为 ...
- java设计模式3——建造者模式
java设计模式3--建造者模式 1.建造者模式介绍: 建造者模式属于创建型模式,他提供了一种创建对象得最佳方式 定义: 将一个复杂对象的构建和与它的表示分离,使得同样的构建过程可以创建不同的表示 主 ...
- Java设计模式之建造者模式(Builder)
前言: 最近一直在学习okHttp,也对其做了一些整理,okHttp和Retrofit结合大大加速我们的开发效率,源码里面采用了很多设计模式,今天我们来学习一下其中的设计模式之一建造者模式. 建造者模 ...
- Java设计模式之三 ----- 建造者模式和原型模式
前言 在上一篇中我们学习了工厂模式,介绍了简单工厂模式.工厂方法和抽象工厂模式.本篇则介绍设计模式中属于创建型模式的建造者模式和原型模式. 建造者模式 简介 建造者模式是属于创建型模式.建造者模式使用 ...
- Java设计模式之三建造者模式和原型模式
建造者模式 简介 建造者模式是属于创建型模式.建造者模式使用多个简单的对象一步一步构建成一个复杂的对象.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式.简单的来说就是将一个复杂的东西 ...
- java设计模式之建造者模式
学习了设计模式,一直感觉有进步又没有进步,与同学.同事探讨了一下.变化不可能一会就可以的,需要努力坚持.不管进步大小,也不管是否进步,做到勿忘初心,做自己喜欢的事情就好.还有几个设计模式一直没有写,原 ...
- java设计模式之四建造者模式(Builder)
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到 ...
- java设计模式3.建造者模式、原型模式
建造者模式 一个产品常有不同的组成部分作为产品的零件,有些情况下,一个对象会有一些重要的性质,在它们没有恰当的值之前,对象不能作为一个完整的产品使用,有些时候,一个对象的一些性质必须按照某个顺序赋值才 ...
- JAVA设计模式总结—建造者模式
建造者模式 模式动机与定义 首先建造者模式的动机是为了创建复杂对象,简化传统的创建方法,提高创建的效率和可读性. 像图中的这个例子,用户的需求是驾驶一辆汽车,但是对于用户来说是不需要了解汽车装 ...
随机推荐
- 掌握简单的Makefile文件编程
Makefile描述整个程序的编译.链接规则 其中还包括了工程中用到的那些源文件及需要产生的目标文件 1)Makefile编程规则 目标(唯一):依赖(可多个) 命令... 伪目标 .PHONY:cl ...
- jsp页面中将CST时间格式化为年月日
引入: <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> 格式化: ...
- Shrio多Realm认证及认证策略
在大型的系统中,安全数据可能会存放在多个数据库中,而且每个数据的加密方式也是不一样的,那么单一的Realm就无法完成. 这时,就需要用到多Realm认证了,多Realm又涉及到认证策略,及在多个Rea ...
- JAVA面试题 请谈谈你对Sychronized关键字的理解?
面试官:sychronized关键字有哪些特性? 应聘者: 可以用来修饰方法; 可以用来修饰代码块; 可以用来修饰静态方法; 可以保证线程安全; 支持锁的重入; sychronized使用不当导致死锁 ...
- JavaSE总结(一)
一.Java 简介 Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计语言和Java平台的总称.由James Gosling和同事们共同研发,并在1995年 ...
- LeetCode 138:复制带随机指针的链表 Copy List with Random Pointer
给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点. 要求返回这个链表的深拷贝. A linked list is given such that each no ...
- 消费端如何保证消息队列MQ的有序消费
消息无序产生的原因 消息队列,既然是队列就能保证消息在进入队列,以及出队列的时候保证消息的有序性,显然这是在消息的生产端(Producer),但是往往在生产环境中有多个消息的消费端(Consumer) ...
- sklearn 第二篇:数据预处理
sklearn.preprocessing包提供了几个常用的转换函数,用于把原始特征向量转换为更适合估计器的表示. 转化器(Transformer)用于对数据的处理,例如标准化.降维以及特征选择等,提 ...
- Linux系统命令。
help:命令用于显示shell内部命令的帮助信息.help命令只能显示shell内部的命令 帮助信息.而对于外部命令的帮助信息只能使用man或者info命令查看 m ...
- 【Git】Found a swap file by the name ".git/.MERGE_MSG.swp"
最近合并分支的时候总是遇到这个问题,导致合并之后还需要再提交一次--有点烦-- 解决方案: 在项目根目录(如/StudioProjects/demo/Leave)下,找到 .git/.MERGE_MS ...