Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >建造者模式

建造者模式

作者头像
mingmingcome
发布于 2021-11-29 07:27:50
发布于 2021-11-29 07:27:50
38100
代码可运行
举报
运行总次数:0
代码可运行

begin 2018年9月12日08:08:17

建造者模式

定义

将一个复杂的对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。 ——《设计模式:可复用面向对象软件的基础》

建造者模式是一种对象创建型模式。

使用场景

从定义中的关键词“复杂的对象”就可以看出来,建造者模式适用于当我们在创建复杂的对象的时候。类似地,“同样的构建过程可以创建不同的表示”,即使客户端执行同样的步骤,也可以得到不一样表示的结果。如:建造者模式可以用于描述KFC如何创建套餐:套餐是一个复杂对象,它一般包含主食(如汉堡、鸡肉卷等)和饮料(如果汁、可乐等)等组成部分,不同的套餐有不同的组成部分,而KFC的服务员可以根据顾客的要求,一步一步装配这些组成部分,构造一个完整的套餐,然后返回给顾客。对于顾客来说,点餐的步骤是固定的,但是从服务员(即下文的指挥者)得到的套餐中主食和饮料是不一样的。

上面为其一,其二还可以作为拥有多个构造参数的需要使用重叠构造器的对象的一种解决方案,使得必要的参数在创建时传入,非必要的参数可以通过建造者的方法设置。后面会有详细的代码示例及分析。

角色

指挥者角色(Director):构建一个使用Builder接口的对象

抽象建造者角色(Builder):为创建一个Product对象的各个部件指定的抽象接口

具体建造者角色(ConcreteBuilder):实现Builder接口,构造和装配各个部件

产品角色(Product)

图示

建造者模式UML类图和序列图:

最终的结果是得到复杂的对象(Complex Object)。本来是客户端直接创建复杂对象,现在是应用建造者模式,通过指挥者指挥具体建造者生成复杂对象的组成部分,然后组装起一个完整的复杂对象。客户端不需要知道复杂对象的构造过程,只需要给指挥者指定具体建造者即可。

放大的类图:

代码示例

指挥者角色(Director.java):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Director {
	private Builder builder;
	
	// 指定建造者
	public Director(Builder builder) {
		this.builder = builder;
	}
	
	public Product construct() {
		builder.setPartA();
		builder.setPartB();
		builder.setPartC();
		return builder.build();
	}
}

指挥者对象创建时需要指定建造者。指挥者对象通常拥有一个指挥创建过程及返回复杂产品对象的方法,如construct()。

抽象建造者角色(Builder.java)

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public interface Builder {
	
	public void setPartA();
	
	public void setPartB();
	
	public void setPartC();
	
	public Product build();
}

抽象建造者接口确定产品由三个部分组成Part A、Part B、Part C,并声明一个组装产品的方法build()。

具体建造者角色(ConcreteBuilder.java):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class ConcreteBuilder implements Builder {
	
	private Product product;
	
	public ConcreteBuilder() {
		product = new Product();
	}

	@Override
	public void setPartA() {
		product.setPartA("Part A");
	}

	@Override
	public void setPartB() {
		product.setPartB("Part B");		
	}

	@Override
	public void setPartC() {
		product.setPartC("Part C");
	}

	@Override
	public Product build() {		
		return product;
	}

}

产品角色(Product.java):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class Product {
	private String partA;
	
	private String partB;
	
	private String partC;

	// 省略get、set及toString方法	
}

测试类(BuilderTest.java):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class BuilderTest {

	public static void main(String[] args) {
		// 建造者
		Builder builder  = new ConcreteBuilder();
		// 指挥者
		Director director = new Director(builder);
		// 指挥者创建产品返回产品
		Product prod = director.construct();
		
		System.out.println(prod);  // 输出:Product [partA=Part A, partB=Part B, partC=Part C]
	}

}
实例

我们有一辆车,这个车有很多选项。我们可以用建造者模式建造车。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 指挥者和客户端
public class CarBuildDirector {
	private CarBuilder builder;
	
	public CarBuildDirector(CarBuilder builder) {
		this.builder = builder;
	}
	
	public Car construct() {
		return builder.setWheels(4)
				.setColor("Red")
				.build();
	}
	public static void main(String[] args) {
		CarBuilder builder = new CarBuilderImpl();
		
		CarBuildDirector carBuildDirector = new CarBuildDirector(builder);
		
		Car car = carBuildDirector.construct();
		
		System.out.println(car);
	}
}
// 产品
class Car {
	private int wheels;
	private String color;
	
	public Car() {}

	public int getWheels() {
		return wheels;
	}

	public void setWheels(int wheels) {
		this.wheels = wheels;
	}

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}

	@Override
	public String toString() {
		return "Car [wheels=" + wheels + ", color=" + color + "]";
	}
}
// 抽象建造者
interface CarBuilder {
	Car build();
	
	CarBuilder setWheels(int wheels);
	
	CarBuilder setColor(String color);
}
// 具体建造者
class CarBuilderImpl implements CarBuilder{
	private Car car;
	
	public CarBuilderImpl() {
		this.car = new Car();
	}

	@Override
	public Car build() {
		return car;
	}

	@Override
	public CarBuilder setWheels(int wheels) {
		car.setWheels(wheels);
		// 注意这里返回的是this对象,就是对象本身
		// 这样的话就可以在Director使用类似链式调用连续调用这个对象的所有方法
		return this;
	}

	@Override
	public CarBuilder setColor(String color) {
		car.setColor(color);
		return this;
	}
}

不同的地方是CarBuilderImpl.setWheels返回的是对象本身,这样我们在创建不同部分的时候可以连续调用对象的方法,代码简化,不好就是不习惯的话就不好理解了。

遇到多个构造器参数时考虑建造者模式

一个类表示包装食品外面的显示的营养成分标签。这些标签中有几个域是必须的:每份的含量、每罐的含量以及每份的卡路里,还有超过20个可选域:总脂肪量、饱和脂肪量、转化脂肪、胆固醇、钠等等。

营养成分类(NutritionFacts.java):

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class NutritionFacts {
	private final int servingSize;  //(ml)            required
	private final int servings;     //(per container) required
	private final int calories;     //                optional  
	private final int fat;          //(g)             optional  
	private final int sodium;       //(mg)            optional
	private final int carbohydrate; //(g)             optional  
	public NutritionFacts(int servingSize, int servings) {
		this(servingSize, servings, 0);
	}
	public NutritionFacts(int servingSize, int servings, int calories) {
		this(servingSize, servings, calories, 0);
	}
	public NutritionFacts(int servingSize, int servings, int calories, int fat) {
		this(servingSize, servings, calories, fat, 0);
	}
	public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium) {
		this(servingSize, servings, calories, fat, sodium, 0);
	}
	public NutritionFacts(int servingSize, int servings, int calories, int fat, int sodium, int carbohydrate) {
		this.servingSize = servingSize;
		this.servings = servings;
		this.calories = calories;
		this.fat = fat;
		this.sodium = sodium;
		this.carbohydrate = carbohydrate;
	}
	@Override
	public String toString() {
		return "NutritionFacts [servingSize=" + servingSize + ", servings=" + servings + ", calories=" + calories
				+ ", fat=" + fat + ", sodium=" + sodium + ", carbohydrate=" + carbohydrate + "]";
	}
}

这么多参数的构造函数,你怎么记得住。而且如果在未来的某天要增加一个参数什么的,还要修改现有的构造函数、然后添加新的构造函数。

在《Effective Java》第二版第2条的题目就是我上面的小标题,这个例子也是书上的例子。书上第三个解决方法:建造者模式。不直接生成想要的对象,而是让客户端利用所有必要的参数调用构造器(或静态工厂),得到一个builder对象。然后客户端在builder对象上调用类似与setter的方法,来设置每个相关的可选参数。最后,客户端调用哪个无参的build方法来生成不可变的对象。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
public class NutritionFactsWithBuilder {
	private final int servingSize;  
	private final int servings;     
	private final int calories;       
	private final int fat;            
	private final int sodium;       
	private final int carbohydrate; 
	
	public static class Builder {
		// Required parameters必要参数
		private final int servingSize;
		private final int servings;
		
		// Optional parameters - initialized to default values
		// 可选参数(初始化默认值)
		private int calories = 0;
		private int fat = 0;
		private int carbohydrate = 0;
		private int sodium = 0;
		// 创建builder时初始化必要参数
		public Builder(int servingSize, int servings) {
			this.servingSize = servingSize;
			this.servings = servings;
		}
		// 可选参数通过各自的方法设置
		public Builder calories(int val) {
			calories = val;
			return this;
		}
		
		public Builder fat(int val) {
			fat = val;
			return this;
		}
		
		public Builder carbohydrate(int val) {
			carbohydrate = val;
			return this;
		}
		
		public Builder sodium(int val) {
			sodium = val;
			return this;
		}
		
		public NutritionFactsWithBuilder build() {
			return new NutritionFactsWithBuilder(this);
		}
	}
	
	public NutritionFactsWithBuilder(Builder builder) {
		servingSize = builder.servingSize;
		servings = builder.servings;
		calories = builder.calories;
		fat = builder.fat;
		sodium = builder.sodium;
		carbohydrate = builder.carbohydrate;
	}

	@Override
	public String toString() {
		return "NutritionFactsWithBuilder [servingSize=" + servingSize + ", servings=" + servings + ", calories="
				+ calories + ", fat=" + fat + ", sodium=" + sodium + ", carbohydrate=" + carbohydrate + "]";
	}
}

这个缺点也很明显啊,多了一个builder类。

优点

1、客户端不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象。用户使用不同的具体建造者即可得到不同的产品对象,新增具体建造者符合“开闭原则”。

2、可以更精细地控制产品的创建过程。

缺点

1、不适用于内部变化复杂的产品。如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大。

总结

建造者模式,适用于创建有复杂内部结构的对象,对象属性之间相互依赖,且又可能要使用到一些其他不易得到的对象。

图是借用了维基百科英文版的,下面的有关Car代码例子也是。

维基百科Builder_pattern

end 2018年9月12日20:50:17

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-09-12 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
纯CSS实现iOS风格打开关闭选择框
在html中,<label>标签通常和<input>标签一起使用,<label>标签为input元素定义标注(标记)。label 元素不会向用户呈现任何特殊效果,<label>标签的作用是为鼠标用户改进了可用性,当用户点击<label>标签中的内容时,浏览器就会自动将焦点转到和该标签相关联的控件上;<label>标签在单选按钮和复选按钮上经常被使用,使用该标签后,你点击label标签内的内容,也可以选中对应的单选按钮或复选按钮。
AlbertYang
2020/09/08
1.1K0
纯CSS实现iOS风格打开关闭选择框
使用HTML和CSS编写无JavaScript的Todo应用
本文介绍了一个使用HTML、CSS和JavaScript实现的无JavaScript的Todo应用。通过使用CSS的伪类选择器,可以实现添加、删除、编辑和标记任务的功能。同时,通过存储和访问数据,可以在不依赖JavaScript的情况下进行实时更新。
IMWeb前端团队
2017/12/29
3.8K0
使用HTML和CSS编写无JavaScript的Todo应用
使用 CSS Checkbox Hack 技术制作一个手风琴组件
在本篇文章里,我们一起学习下如何使用 CSS checkbox hack 技巧制作一个响应式的手风琴组件,这个组件完全基于CSS,没有JavaScript脚本,基于窗口大小进行水平和垂直之间进行切换。为了让大家更好理解,我将和大家一起一步步的进行完成。
前端达人
2019/12/23
5.5K0
使用 CSS Checkbox Hack 技术制作一个手风琴组件
CSS(CSS3)选择器(2)
该部分主要为CSS3新增的选择器 接上一篇 CSS(CSS3)选择器(1) 一.通用兄弟选择器:                         24:E ~ F,匹配任何E元素之后的同级F元素。 div ~ p{ background-color:#00FF00; } 二.属性选择器:                         25:E[att ^= val],匹配属性att的值以”val“开头的元素。 [id ^= start]{ background-color:red; ]
zaking
2018/05/02
1K0
css3实战汇总(附源码)
利用css3的新特性可以帮助我们实现各种意想不到的特效,接下来的几个案例我们来使用css3的box-shdow来实现,马上开始吧!
徐小夕
2019/09/25
7660
css3实战汇总(附源码)
CSS设置复选框和开关的样式
我可能不是唯一一个对浏览器的默认设置感到沮丧的开发人员<input type="checkbox">。
哈德森sir
2024/05/16
7030
CSS设置复选框和开关的样式
分享15个高级前端开发小技巧
我们将提供真实世界的示例,并将它们与旧的基于 JavaScript 的方法进行比较,展示现代 Web 技术的力量。
前端达人
2024/03/25
4240
分享15个高级前端开发小技巧
Imooc之Html与CSS
1、ID选择器只能在文档中使用一次。与类选择器不同,在一个HTML文档中,ID选择器只能使用一次,而且仅一次。而类选择器可以使用多次。
ZHaos
2019/02/27
6.9K0
【Java 进阶篇】JQuery 案例:全选全不选,为选择添彩
在前端的舞台上,用户交互是一场精彩的表演,而全选全不选的功能则是其中一段引人入胜的剧情。通过巧妙运用 JQuery,我们可以为用户提供便捷的全选和全不选操作,让页面更富交互性。本篇博客将深入探讨 JQuery 中全选全不选的实现原理和实际应用,为你揭开这段前端小剧场的神秘面纱。
繁依Fanyi
2023/11/15
4360
【Java 进阶篇】JQuery 案例:全选全不选,为选择添彩
如何使用纯 CSS 制作四子连珠游戏
实验是学习新技巧、思考新想法、并突破自身极限的有趣的方式。“纯 CSS”演示很早就有了,但是随着浏览器和CSS的发展,新的挑战又出现了。CSS 和 HTML 预处理器也促进了纯 CSS 演示的发展。有时候,预处理程序用于硬编码每个可能的场景,比如 :checked 的长字符串和相邻兄弟选择器。
叙帝利
2018/09/28
2K0
如何使用纯 CSS 制作四子连珠游戏
iOS开关按钮,纯CSS给你安排上了
今早搭完电梯出来信号不太好,切换飞行模式重启信号,发现iOS的「开关按钮」挺好玩的,顺便用纯CSS实现一番。
JowayYoung
2020/09/18
1.8K0
学习jQuery?这篇文章就够了
jQuery 使用户能更方便地处理 HTML(标准通用标记语言下的一个应用)、events、实现动画效果,并且方便地为网站提供 AJAX 交互。 jQuery 的语法设计可以使开发者更加便捷,例如操作文档对象、选择 DOM 元素、制作动画效果、事件处理、使用 AJAX 以及其他功能。
上分如喝水
2021/08/16
12.4K0
使用 CSS Checkbox Hack 技术纯手工撸一个手风琴组件
在本篇文章里,我们将一起学习下如何使用 CSS checkbox hack 技术纯手工撸一个响应式的手风琴组件,这个组件完全基于CSS,没有JavaScript脚本,同时又基于窗口大小进行水平和垂直之间进行样式切换。为了让大家更好理解本案例,我将和大家一起一步步的完成。
前端达人
2019/12/25
3.3K0
使用 CSS Checkbox Hack 技术纯手工撸一个手风琴组件
CSS3新特性应用之用户体验
一、光标 新增加not-allowed光标,不允许访问 隐藏光标,在触模应用上很有用,css2.1需要一个透明的图片来实现,而css3直接用cursor:none即可。 完整代码: curosr: url(transparent.gif'); cursor: none; 二、扩大热区 应用在小按钮的情况下,不好被鼠标点击到 代码如下: .btn{ position: relative; cursor: pointer; } .btn:after{ position: absolute
sam dragon
2018/01/17
8550
从0开始编写一个开关组件
开关按钮似乎是开发人员和设计师最喜欢的展示他们的动画、设计和双关语技能的方式。甚至还有一个专门用于开关按钮的Codepen集合。
coder_koala
2020/08/27
2.5K0
从0开始编写一个开关组件
【QT】QT界面的美容院 -- QSS
🔥 在网页前端开发领域中,CSS 是一个至关重要的部分,描述了一个网页的 “样式”,从而起到对网页 美化 的作用。
IsLand1314
2025/04/15
2570
【QT】QT界面的美容院 -- QSS
纯CSS改写radio和checkbox,单选框、复选框样式优化
CSS: 将单选框或者复选框隐藏,在其后面写一个 span 利用 CSS 的相邻兄弟选择器  input + span 选中它,对它的样式进行美化。
德顺
2019/11/13
5.5K0
纯CSS改写radio和checkbox,单选框、复选框样式优化
动手练一练,用纯 CSS 制作一款侧滑显示留言面板的网页组件
大家好,不知道你们是否和我一样存在这样的困惑呢,虽然css入门容易,但是其内容太多,好多属性看了似是而非,觉得自己看懂了,到自己用的时候又很犯难了,看到漂亮的效果还是无从下手,这主要还是自己对新属性实践太少了,不能进行灵活应用,CSS总让一些人找不到感觉。其实学好CSS真的没有太多捷径,和JS编程一样,要重视对待,要多看和多练,因为现在的CSS不再是以前的CSS啦。
前端达人
2020/02/18
1.1K0
动手练一练,用纯 CSS 制作一款侧滑显示留言面板的网页组件
前端之form表单与css(1)
form表单用于用户界面向服务器传输数据,实现用户和web服务器的交互。表单包含input系列标签如文本字段、复选框、单选框、提交按钮等。
GH
2019/12/16
2K0
前端之form表单与css(1)
Vue表单输入绑定
  表单控件的数据绑定就是用v-model指令实现的,它会根据控件类型自动选取正确的方法更新元素。由于表单控件有不同的类型,如文本输入框、复选框、单选按钮、选择框等,v-model指令在不同的表单控件上应用时也会有所差异。
别团等shy哥发育
2023/02/25
7.6K0
Vue表单输入绑定
推荐阅读
相关推荐
纯CSS实现iOS风格打开关闭选择框
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验