什么是建造者模式

发现很多框架的源码使用了建造者模式,看了一下觉得挺实用的,就写篇文章学习一下,顺便分享给大家。

建造者模式是什么呢?用一句话概括就是建造者模式的目的是为了分离对象的属性与创建过程,是的,只要记住并理解红字的几个部分,建造者模式你就懂了。

为什么需要建造者模式

建造者模式是构造方法的一种替代方案,为什么需要建造者模式,我们可以想,假设有一个对象里面有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:建造者模式的更多相关文章

  1. 折腾Java设计模式之建造者模式

    博文原址:折腾Java设计模式之建造者模式 建造者模式 Separate the construction of a complex object from its representation, a ...

  2. Java 设计模式之建造者模式(四)

    原文地址:Java 设计模式之建造者模式(四) 博客地址:http://www.extlight.com 一.前言 今天继续介绍 Java 设计模式中的创建型模式--建造者模式.上篇设计模式的主题为 ...

  3. java设计模式3——建造者模式

    java设计模式3--建造者模式 1.建造者模式介绍: 建造者模式属于创建型模式,他提供了一种创建对象得最佳方式 定义: 将一个复杂对象的构建和与它的表示分离,使得同样的构建过程可以创建不同的表示 主 ...

  4. Java设计模式之建造者模式(Builder)

    前言: 最近一直在学习okHttp,也对其做了一些整理,okHttp和Retrofit结合大大加速我们的开发效率,源码里面采用了很多设计模式,今天我们来学习一下其中的设计模式之一建造者模式. 建造者模 ...

  5. Java设计模式之三 ----- 建造者模式和原型模式

    前言 在上一篇中我们学习了工厂模式,介绍了简单工厂模式.工厂方法和抽象工厂模式.本篇则介绍设计模式中属于创建型模式的建造者模式和原型模式. 建造者模式 简介 建造者模式是属于创建型模式.建造者模式使用 ...

  6. Java设计模式之三建造者模式和原型模式

    建造者模式 简介 建造者模式是属于创建型模式.建造者模式使用多个简单的对象一步一步构建成一个复杂的对象.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式.简单的来说就是将一个复杂的东西 ...

  7. java设计模式之建造者模式

    学习了设计模式,一直感觉有进步又没有进步,与同学.同事探讨了一下.变化不可能一会就可以的,需要努力坚持.不管进步大小,也不管是否进步,做到勿忘初心,做自己喜欢的事情就好.还有几个设计模式一直没有写,原 ...

  8. java设计模式之四建造者模式(Builder)

    工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到 ...

  9. java设计模式3.建造者模式、原型模式

    建造者模式 一个产品常有不同的组成部分作为产品的零件,有些情况下,一个对象会有一些重要的性质,在它们没有恰当的值之前,对象不能作为一个完整的产品使用,有些时候,一个对象的一些性质必须按照某个顺序赋值才 ...

  10. JAVA设计模式总结—建造者模式

    建造者模式 模式动机与定义 ​ 首先建造者模式的动机是为了创建复杂对象,简化传统的创建方法,提高创建的效率和可读性. ​ 像图中的这个例子,用户的需求是驾驶一辆汽车,但是对于用户来说是不需要了解汽车装 ...

随机推荐

  1. 实验吧--web--天下武功唯快不破

    ---恢复内容开始--- 英文翻译过来嘛,就是:天下武功无快不破嘛.(出题者还是挺切题的) 看看前端源码: 注意这里 please post what you find with parameter: ...

  2. python虚拟环境管理 Pipenv 使用说明

    安装 pip install pipenv 检查是否安装成功 pipenv --version 创建虚拟环境(在工程文件夹下) pipenv install 默认下,Pipenv统一管理所有虚拟环境 ...

  3. 分析了京东内衣销售记录,告诉你妹子们的真Size!

    >今天闲暇之余写了一个爬虫例子.通过爬虫去爬取京东的用户评价,通过分析爬取的数据能得到很多结果,比如,哪一种颜色的胸罩最受女性欢迎,以及中国女性的平均size(仅供参考哦~) 打开开发者工具-n ...

  4. golang从context源码领悟接口的设计

    注:写帖子时go的版本是1.12.7 go语言中实现一个interface不用像其他语言一样需要显示的声明实现接口.go语言只要实现了某interface的方法就可以做类型转换.go语言没有继承的概念 ...

  5. Java基础之方法

    方法 某段代码经常使用,可以使用大括号将这段代码包括起来,起个名字,以后就使用这个名字来代替这段代码. 定义格式: 修饰符 返回值类型 方法名(参数列表) { 方法体语句:   return语句: } ...

  6. ACM线性基学习笔记

    https://www.cnblogs.com/31415926535x/p/11260897.html 概述 最近的几场多校出现了好几次线性基的题目,,会想起之前在尝试西安区域赛的一道区间异或和最大 ...

  7. 1.4.1python下载网页(每天一更)

    # -*- coding: utf-8 -*- ''' Created on 2019年4月27日 @author: lenovo ''' # import urllib3 # def downloa ...

  8. oracle-11g2下载安装笔记

    一.下载链接地址 http://download.oracle.com/otn/nt/oracle11g/112010/win64_11gR2_database_1of2.zip http://dow ...

  9. 佳木斯集训Day2

    D2好点了,最起码不像之前那么水 T1按照常规操作是个找规律,类似于括号匹配的题,但是又不是,推进栈里,然后看最长的左括号有多少个,然后直接cout就可以了 #include <bits/std ...

  10. LR(1)语法分析器生成器(生成Action表和Goto表)java实现(一)

    序言 : 在看过<自己实现编译器链接器>源码之后,最近在看<编译器设计>,但感觉伪代码还是有点太浮空.没有掌握的感觉,也因为内网几乎没有LR(1)语法分析器生成器的内容,于是我 ...