《重学Java设计模式》笔记——建造者模式
1. 建造者模式可以解决什么问题
我家里有各种形状的瓷器,盘子或者碗。虽然形状不同,但是所用的材料基本上是一样的,比如土、水、釉、彩这些基本的东西。
但是做不同款式的瓷器,方法是不同的。假如说我现在已经写好了一段代码来生成白瓷碗和白瓷盘,正常运行起来了,这倒也没什么,可是,如果我现在要增加一个工作,制作彩绘马克杯,这样我就得修改已有的代码了。
最简单的修改方式就是加if-else。就像是这样:
if (type = "白瓷碗") {
makeWhiteBowl(...);
} else if (type = "白瓷盘") {
makeWhitePlate(...);
} else if (type = "彩绘马克杯") {
makeColorCup(...);
}
...
Well,看起来还不很严重,那么如果你的业务越来越复杂呢?每次新添加一种type,首先就要去改代码,也许还不止这一处,这是不可忍受的,是很烂的设计。
建造者模式,就是为了解决这种问题出现的一种设计模式。
在GoF的《设计模式》一书中,明确的提及了建造者模式的意图:
将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
这一论断说明了这么几点:
- 构建过程和对象相分离,也就是说不要直接去new这个对象;
- 构建过程抽象出来到一个高层次上。
回到我上面的例子,就是制造瓷器的人和瓷器相分离。客户交给匠人材料和订单,匠人自行制造交付就可以了。
2. 建造者模式的理论
建造者模式中会存在下面几种角色:
- 建造者(Builder):接口,用于创建产品的各个部分的抽象;
- ConcreteBuilder:实现Builder接口,是具体的建造者;
- Director:这个名字看着像“导演”,其实是用于构造Builder对象的;
- Product:各类产品,最终被Builder生产出来的东西。
下图是GoF给出的类图:

知道了建造者模式的基本类图结构以后,还需要知道这个模式是如何使用的,如下时序图:

3. 具体编码实现
在《重学Java设计模式》中的建造者模式章节提到了一个装修的例子,但是从这个例子上来看,和GoF的描述还是有一些区别的。
还是以装修为例,书中提到了集中装修风格,其实就对应了几种ConcreteBuilder,例如田园奢华欧式、轻奢田园和现代简约。而这些建造者们都有相同的几种方法:
- 吊顶
- 涂墙
- 铺地板或者地砖
另外还有物料,比如各种品牌的装修材料等等。下面是我根据GoF的理论,对《重学Java设计模式》的建造者代码的改造。
首先是物料,可以抽象出一个Matter接口,所有的地板,涂料都是Matter。
/**
* 物料的五种属性
*/
public interface Matter {
//类型,比如地板
String scene();
//品牌
String brand();
//型号
String model();
//价格
BigDecimal price();
String desc();
}
物料并非建造者模式中的核心元素,接下来就可以抽象建造者了。
package com.example.pattern.builder.iface;
public interface IBuilder {
//装吊顶
IBuilder appendCeiling(Matter ceiling);
//涂墙
IBuilder appendCoat(Matter coat);
//铺地板
IBuilder appendFloor(Matter floor);
//铺地砖
IBuilder appendTitle(Matter title);
//得到装修报价单
String getResult();
}
建造者要做的工作就是来料加工,也就是一个装修工人的手艺了。
下面是对Director的定义,可以认为Director是监工吧,业主只和监工或者工头直接联系,不会给工人直接下达任务:
package com.example.pattern.builder.builder;
import com.example.pattern.builder.iface.IBuilder;
import com.example.pattern.builder.iface.Matter;
import lombok.Data;
@Data
public class Director {
private IBuilder builder;
private Matter ceiling;
private Matter coat;
private Matter floor;
private Matter title;
public Director(IBuilder builder) {
this.builder = builder;
}
public String construct() {
return builder.appendCeiling(ceiling).appendCoat(coat)
.appendFloor(floor).appendTitle(title).getResult();
}
}
其实Director也不知道自己的物料是什么。从时序图上来看,不管是具体的建造者还是Director都是使用者(就像业主)提供的,因此使用者的代码如下:
public class BuilderApp {
public static void main(String[] args) {
IBuilder europeBuilder = new EuropeBuilder(new BigDecimal(160));
Director director = new Director(europeBuilder);
director.setCeiling(new LevelTwoCeiling());
director.setFloor(new MarcoPoloTitle());
director.setCoat(new NipponCoat());
String result = director.construct();
System.out.println(result);
}
}
这个结果就是计算一下装修的账单,很简单的一个示例,最终的打印是总报价。
4. 进一步的思考
在结城浩的《图解设计模式》一书中提到了“谁知道什么”的概念。在上面的代码中,这个概念体现了两次:
- BuilderApp类并没有直接调用Builder,BuilderApp并不知道Builder能做什么;
- Director也不知道自己到底调用的是哪个Builder,它使用的是一个接口。
OOP一定是面向接口最好的,因为可以随时替换掉具体的实现,不同的类之间耦合度不高。
在新增一个产品的时候,只需要添加一个具体的Builder就可以了,不需要修改现有代码。
学习一种设计模式总是写这种简单的例子也感觉没什么意思。我在之前的工作中因为一些原因不能用现成的第三方包,就只能写了一个Map的生成器来做事情,我觉得这个倒是挺有建造者模式的味道的。
首先我不需要定义Map了,因为Java.util已经提供了足够多的,而且是接口和实现分离的。
下面需要定义一个Builder:
package com.example.pattern.builder.maphelper;
import java.util.Map;
public class MapBuilder<K, V> {
private Map<K, V> map;
public MapBuilder(Map<K, V> map) {
this.map = map;
}
public MapBuilder<K, V> put(K k, V v) {
map.put(k, v);
return this;
}
public Map<K, V> build() {
return map;
}
}
这个Builder更好的地方就是自己都不知道自己制造什么产品。接下来时Director的角色:
package com.example.pattern.builder.maphelper;
import java.util.Map;
public class MapHelper {
public static <K, V> MapBuilder<K, V> builder(Map<K, V> map) {
return new MapBuilder<>(map);
}
}
这简直太让人舒适了,因为调用者的代码是链式的:
Map<String, String> map
= MapHelper.builder(new HashMap<String, String>()).put("foo", "bar")
.build();
这个建造者和GoF的理论有一点差异,就是MapHelper并没有持有任何对象,没有构造方法。其实如果一定要改造也是可以的。
Map本身是一个比较复杂的构建,因为要不停的put,代码也显得不够漂亮,链式put则显得优雅一些。而且这个Builder本身也实现了构建和表示分离的原则。
《重学Java设计模式》笔记——建造者模式的更多相关文章
- 重学 Java 设计模式:实战桥接模式(多支付渠道「微信、支付宝」与多支付模式「刷脸、指纹」场景)
作者:小傅哥 博客:https://bugstack.cn - 编写系列原创专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 为什么你的代码那么多ifelse 同类的业务.同样的功能, ...
- 重学 Java 设计模式:实战装饰器模式(SSO单点登录功能扩展,增加拦截用户访问方法范围场景)
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 对于代码你有编程感觉吗 很多人写代码往往是没有编程感觉的,也就是除了可以把功能按照固 ...
- 重学 Java 设计模式:实战外观模式「基于SpringBoot开发门面模式中间件,统一控制接口白名单场景」
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你感受到的容易,一定有人为你承担不容易 这句话更像是描述生活的,许许多多的磕磕绊绊总 ...
- 重学 Java 设计模式:实战享元模式「基于Redis秒杀,提供活动与库存信息查询场景」
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 程序员的上下文是什么? 很多时候一大部分编程开发的人员都只是关注于功能的实现,只 ...
- 重学 Java 设计模式:实战代理模式「模拟mybatis-spring中定义DAO接口,使用代理类方式操作数据库原理实现场景」
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 难以跨越的瓶颈期,把你拿捏滴死死的! 编程开发学习过程中遇到的瓶颈期,往往是由于看不 ...
- 重学 Java 设计模式:实战抽象工厂模式
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获!
- 重学 Java 设计模式:实战责任链模式「模拟618电商大促期间,项目上线流程多级负责人审批场景」
作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 场地和场景的重要性 射击
- 重学 Java 设计模式:实战迭代器模式「模拟公司组织架构树结构关系,深度迭代遍历人员信息输出场景」
作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 相信相信的力量! 从懵懂的少年,到拿起键盘,可以写一个Hell ...
- 重学 Java 设计模式:实战备忘录模式「模拟互联网系统上线过程中,配置文件回滚场景」
作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 实现不了是研发的借口? 实现不了,有时候是功能复杂度较高难以实 ...
- 重学 Java 设计模式:实战状态模式「模拟系统营销活动,状态流程审核发布上线场景」
作者:小傅哥 博客:https://bugstack.cn - 原创系列专题文章 沉淀.分享.成长,让自己和他人都能有所收获! @ 目录 一.前言 二.开发环境 三.状态模式介绍 四.案例场景模拟 1 ...
随机推荐
- Maven配置阿里云镜像和本地仓库路径
配置阿里云镜像仓库 在settings > mirrors标签下添加以下内容 <!-- Aliyun Mirror --> <mirror> <id>alim ...
- 没有 Git,如何下载 Gitee 代码?
目录 没有 Git,如何下载 Gitee 代码? 注册 Gitee 账号 下载代码压缩包 没有 Git,如何下载 Gitee 代码? 鉴于看我博客的人很多都是大学本科生.非 CS 专业,大部分人都不会 ...
- python重拾第九天-进程、线程、协程
本节内容 操作系统发展史介绍 进程.与线程区别 python GIL全局解释器锁 线程 语法 join 线程锁之Lock\Rlock\信号量 将线程变为守护进程 Event事件 queue队列 生产者 ...
- C#/.NET这些实用的技巧和知识点你都知道吗?
前言 今天大姚给大家分享一些C#/.NET中的实用的技巧和知识点,它们可以帮助我们提升代码质量和编程效率,希望可以帮助到有需要的同学. .NET使用CsvHelper快速读取和写入CSV文件 本文主要 ...
- Python——比 Seaborn 更好的相关性热力图:Biokit Corrplot
目录 前言:我们需要更好的相关性热力图 对比 Python Seaborn 与 R corrplot 传统的 Seaborn 相关性热力图 R 语言中的相关性热力图 关于 Biokit 简介 库的安装 ...
- 关于docker-compose up -d 出现超时情况处理
由于要搭建一个ctf平台,用docker一键搭建是出现超时情况 用了很多办法,换源,等之类的一样没办法,似乎它就是只能用官方那个一样很怪. 只能用一种笨办法来处理了,一个个pull. 打个比如: 打开 ...
- WPF在.NET9中的重大更新:Windows 11 主题
在2023年的2月20日,在WPF的讨论区,WPF团队对路线的优先级发起了一次讨论. 对三个事项发起了投票. 第一个是Windows 11 主题 第二个是更新的控件 第三个是可空性注释 最终Windo ...
- PHP接入苹果支付
Ios苹果支付流程: 客户端先从苹果获取内购Id. 客户端将内购id,金额.用户id等传给服务端获取一个自己服务端生成的订单号. 客户端向苹果发起支付. 支付成功后,客户端从本地拿支付凭证.将支付凭证 ...
- 【仿真】Carla之Docker 运行 及 渲染相关 [6]
参考与前言 carla官方对于docker 运行的描述: CARLA in Docker Docker的使用:[暂时没贴] 相关已知issue,欢迎补充 https://github.com/carl ...
- SpringCloud注册中心切换nacos
SpringBoot与nacos版本对应关系 https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF ...