- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
本文详细介绍了建造者模式的设计思想及其应用场景。主要内容包括建造者模式的由来、定义、适用场景及思考,通过实例讲解了如何使用建造者模式解决复杂对象的创建问题。文章还对比了建造者模式与工厂模式的区别,并分析了建造者模式的优缺点。最后,提供了多个相关资源链接,帮助读者深入理解和应用设计模式.
1.无论是在现实世界中还是在软件系统中,都存在一些复杂的对象,它们拥有多个组成部分.
如汽车,它包括车轮、方向盘、发送机等各种部件。而对于大多数用户而言,无须知道这些部件的装配细节,也几乎不会使用单独某个部件,而是使用一辆完整的汽车,可以通过建造者模式对其进行设计与描述.
建造者模式可以将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节.
2.复杂对象相当于一辆有待建造的汽车,而对象的属性相当于汽车的部件,建造产品的过程就相当于组合部件的过程.
由于组合部件的过程很复杂,因此,这些部件的组合过程往往被“外部化”到一个称作建造者的对象里.
建造者返还给客户端的是一个已经建造完毕的完整产品对象,而用户无须关心该对象所包含的属性以及它们的组装方式,这就是建造者模式的模式动机.
建造者模式(Builder Pattern):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示.
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,用户不需要知道内部的具体构建细节.
建造者模式属于对象创建型模式。根据中文翻译的不同,建造者模式又可以称为生成器模式.
在以下情况下可以使用建造者模式:
建造者模式的原理和代码实现非常简单,掌握起来并不难,难点在于应用场景.
在平时的开发中,创建一个对象最常用的方式是,使用 new 关键字调用类的构造函数来完成.
我的问题是,什么情况下这种方式就不适用了,就需要采用建造者模式来创建对象呢?你可以先思考一下,下面我通过一个例子来带你看一下.
假设有这样一道设计面试题:
我们需要定义一个资源池配置类 ResourcePoolConfig。这里的资源池,你可以简单理解为线程池、连接池、对象池等。在这个资源池配置类中,有以下几个成员变量,也就是可配置项。现在,请你编写代码实现这个 ResourcePoolConfig 类.
最常见、最容易想到的实现思路如下代码所示.
public class BuilderDesign1 {
public static void main(String[] args) {
ResourcePoolConfig resourcePoolConfig = new ResourcePoolConfig("", 1, 2, 3);
}
public static class ResourcePoolConfig {
private static final int DEFAULT_MAX_TOTAL = 8;
private static final int DEFAULT_MAX_IDLE = 8;
private static final int DEFAULT_MIN_IDLE = 0;
private String name;
private int maxTotal = DEFAULT_MAX_TOTAL;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
public ResourcePoolConfig(String name, int maxTotal, int maxIdle, int minIdle) {
this.name = name;
if (maxTotal <= 0) {
throw new IllegalArgumentException("maxTotal should be positive.");
}
this.maxTotal = maxTotal;
if (maxIdle < 0) {
throw new IllegalArgumentException("maxIdle should not be negative.");
}
this.maxIdle = maxIdle;
if (minIdle < 0) {
throw new IllegalArgumentException("minIdle should not be negative.");
}
this.minIdle = minIdle;
}
//...省略getter方法...
}
}
现在,ResourcePoolConfig 只有 4 个可配置项,对应到构造函数中,也只有 4 个参数,参数的个数不多.
但是,如果可配置项逐渐增多,变成了 8 个、10 个,甚至更多,那继续沿用现在的设计思路,构造函数的参数列表会变得很长,代码在可读性和易用性上都会变差.
在使用构造函数的时候,我们就容易搞错各参数的顺序,传递进错误的参数值,导致非常隐蔽的 bug.
// 参数太多,导致可读性差、参数可能传递错误
ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool", 16, null, 8, null, false , true, 10, 20,false, true);
解决这个问题的办法你应该也已经想到了,那就是用set()函数来给成员变量赋值,以替代冗长的构造函数.
我们直接看代码,具体如下所示。其中,配置项name是必填的,所以我们把它放到构造函数中设置,强制创建类对象的时候就要填写.
其他配置项 maxTotal、maxIdle、minIdle 都不是必填的,所以我们通过 set() 函数来设置,让使用者自主选择填写或者不填写.
public static class ResourcePoolConfig {
private static final int DEFAULT_MAX_TOTAL = 8;
private static final int DEFAULT_MAX_IDLE = 8;
private static final int DEFAULT_MIN_IDLE = 0;
private String name;
private int maxTotal = DEFAULT_MAX_TOTAL;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
public ResourcePoolConfig(String name) {
this.name = name;
}
public void setMaxTotal(int maxTotal) {
if (maxTotal <= 0) {
throw new IllegalArgumentException("maxTotal should be positive.");
}
this.maxTotal = maxTotal;
}
public void setMaxIdle(int maxIdle) {
if (maxIdle < 0) {
throw new IllegalArgumentException("maxIdle should not be negative.");
}
this.maxIdle = maxIdle;
}
public void setMinIdle(int minIdle) {
if (minIdle < 0) {
throw new IllegalArgumentException("minIdle should not be negative.");
}
this.minIdle = minIdle;
}
//...省略getter方法...
}
接下来,我们来看新的 ResourcePoolConfig 类该如何使用。我写了一个示例代码,如下所示。没有了冗长的函数调用和参数列表,代码在可读性和易用性上提高了很多.
// ResourcePoolConfig使用举例
ResourcePoolConfig config = new ResourcePoolConfig("dbconnectionpool");
config.setMaxTotal(16);
config.setMaxIdle(8);
至此,我们仍然没有用到建造者模式,通过构造函数设置必填项,通过 set() 方法设置可选配置项,就能实现我们的设计需求.
可以把校验逻辑放置到 Builder 类中,先创建建造者,并且通过 set() 方法设置建造者的变量值,然后在使用 build() 方法真正创建对象之前,做集中的校验,校验通过之后才会创建对象.
除此之外,我们把 ResourcePoolConfig 的构造函数改为 private 私有权限。这样我们就只能通过建造者来创建 ResourcePoolConfig 类对象.
并且,ResourcePoolConfig 没有提供任何 set() 方法,这样我们创建出来的对象就是不可变对象了.
用建造者模式重新实现了上面的需求,具体的代码如下所示:
public class ResourcePoolConfig {
private String name;
private int maxTotal;
private int maxIdle;
private int minIdle;
private ResourcePoolConfig(Builder builder) {
this.name = builder.name;
this.maxTotal = builder.maxTotal;
this.maxIdle = builder.maxIdle;
this.minIdle = builder.minIdle;
}
//...省略getter方法...
//我们将Builder类设计成了ResourcePoolConfig的内部类。
//我们也可以将Builder类设计成独立的非内部类ResourcePoolConfigBuilder。
public static class Builder {
private static final int DEFAULT_MAX_TOTAL = 8;
private static final int DEFAULT_MAX_IDLE = 8;
private static final int DEFAULT_MIN_IDLE = 0;
private String name;
private int maxTotal = DEFAULT_MAX_TOTAL;
private int maxIdle = DEFAULT_MAX_IDLE;
private int minIdle = DEFAULT_MIN_IDLE;
public ResourcePoolConfig build() {
// 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("...");
}
if (maxIdle > maxTotal) {
throw new IllegalArgumentException("...");
}
if (minIdle > maxTotal || minIdle > maxIdle) {
throw new IllegalArgumentException("...");
}
return new ResourcePoolConfig(this);
}
public Builder setName(String name) {
if (StringUtils.isBlank(name)) {
throw new IllegalArgumentException("...");
}
this.name = name;
return this;
}
public Builder setMaxTotal(int maxTotal) {
if (maxTotal <= 0) {
throw new IllegalArgumentException("...");
}
this.maxTotal = maxTotal;
return this;
}
public Builder setMaxIdle(int maxIdle) {
if (maxIdle < 0) {
throw new IllegalArgumentException("...");
}
this.maxIdle = maxIdle;
return this;
}
public Builder setMinIdle(int minIdle) {
if (minIdle < 0) {
throw new IllegalArgumentException("...");
}
this.minIdle = minIdle;
return this;
}
}
}
// 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdle
ResourcePoolConfig config = new ResourcePoolConfig.Builder()
.setName("dbconnectionpool")
.setMaxTotal(16)
.setMaxIdle(10)
.setMinIdle(12)
.build();
实际上,使用建造者模式创建对象,还能避免对象存在无效状态。我再举个例子解释一下.
比如我们定义了一个长方形类,如果不使用建造者模式,采用先创建后 set 的方式,那就会导致在第一个 set 之后,对象处于无效状态。具体代码如下所示:
Rectangle r = new Rectange(); // r is invalid
r.setWidth(2); // r is invalid
r.setHeight(3); // r is valid
为了避免这种无效状态的存在,我们就需要使用构造函数一次性初始化好所有的成员变量.
如果构造函数参数过多,我们就需要考虑使用建造者模式,先设置建造者的变量,然后再一次性地创建对象,让对象一直处于有效状态.
实际上,如果我们并不是很关心对象是否有短暂的无效状态,也不是太在意对象是否是可变的。比如,对象只是用来映射数据库读出来的数据,那我们直接暴露 set() 方法来设置类的成员变量值是完全没问题的.
而且,使用建造者模式来构建对象,代码实际上是有点重复的,ResourcePoolConfig 类中的成员变量,要在 Builder 类中重新再定义一遍.
建造者模式包含如下角色:
建造者模式时序图如下所示:
例如,让我们考虑如何创建一个House(房屋)对象.
为了建造一个简单的房子,您需要建造四堵墙和一层地板,安装一扇门,安装一对窗户,并建造一座屋顶。但是,如果您想要一个更大、更明亮的房子,带有后院和其他设施(如供暖系统、管道和电气布线)呢?
最简单的解决方案是扩展基类House并创建一组子类来涵盖所有参数的组合.
但是,最终您将得到相当数量的子类。任何新的参数,如门廊风格,都将需要进一步扩展这个层次结构。建造者模式允许您逐步构建复杂的对象.
使用普通方式盖房子,代码如下所示:
public class BuilderHouse {
public static void main(String[] args) {
CommonHouse commonHouse = new CommonHouse();
commonHouse.build();
HeightBuilding heightBuilding = new HeightBuilding();
heightBuilding.build();
}
public static abstract class AbstractHouse {
/**
* 打地基
*/
public abstract void buildBasic();
/**
* 砌墙
*/
public abstract void buildWalls();
/**
* 封顶
*/
public abstract void roofed();
public void build() {
buildBasic();
buildWalls();
roofed();
}
}
public static class CommonHouse extends AbstractHouse {
@Override
public void buildBasic() {
System.out.println(" 普通房子打地基 ");
}
@Override
public void buildWalls() {
System.out.println(" 普通房子砌墙 ");
}
@Override
public void roofed() {
System.out.println(" 普通房子封顶 ");
}
}
public static class HeightBuilding extends AbstractHouse {
@Override
public void buildBasic() {
System.out.println(" 高楼打地基 ");
}
@Override
public void buildWalls() {
System.out.println(" 高楼房子砌墙 ");
}
@Override
public void roofed() {
System.out.println(" 高楼房子封顶 ");
}
}
}
分析 。
使用构建者模式实现房子的构建 。
private void test() {
///盖普通房子
//准备创建房子的指挥者
HouseDirector houseDirector = new HouseDirector(new CommonHouse());
//完成盖房子,返回产品(普通房子)
House commonHouse = houseDirector.constructHouse();
System.out.println("普通房子:" + commonHouse.toString());
///盖高楼
//重置建造者,改成修高楼
houseDirector.setHouseBuilder(new HighBuilding());
//完成盖房子,返回产品(高楼)
House highBuilding = houseDirector.constructHouse();
System.out.println("高楼:" + highBuilding.toString());
}
/**
* 产品->Product
*/
public class House {
private String basic;
private String wall;
private String roofed;
public String getBasic() {
return basic;
}
public void setBasic(String basic) {
this.basic = basic;
}
public String getWall() {
return wall;
}
public void setWall(String wall) {
this.wall = wall;
}
public String getRoofed() {
return roofed;
}
public void setRoofed(String roofed) {
this.roofed = roofed;
}
public House(String basic, String wall, String roofed) {
this.basic = basic;
this.wall = wall;
this.roofed = roofed;
}
public House() {
}
@Override
public String toString() {
return "House{" +
"basic='" + basic + '\'' +
", wall='" + wall + '\'' +
", roofed='" + roofed + '\'' +
'}';
}
}
/**
* 抽象的建造者
*/
public abstract class HouseBuilder {
/**
* 组合House
*/
protected House house = new House();
//-------------------------将建造的流程写好--------------------------
/**
* 打地基
*/
public abstract void buildBasic();
/**
* 砌墙
*/
public abstract void buildWalls();
/**
* 封顶
*/
public abstract void roofed();
/**
* 建造好房子后将产品(房子) 返回
*
* @return
*/
public House buildHouse() {
return house;
}
}
/**
* 具体建造者
*/
public class CommonHouse extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println("普通房子打地基5米 ");
super.house.setBasic("地基5米");
}
@Override
public void buildWalls() {
System.out.println("普通房子砌墙10cm ");
super.house.setWall("墙10cm");
}
@Override
public void roofed() {
System.out.println("普通房子屋顶 ");
super.house.setRoofed("普通房子屋顶");
}
}
/**
* 具体建造者
*/
public class HighBuilding extends HouseBuilder {
@Override
public void buildBasic() {
System.out.println("高楼的打地基100米 ");
super.house.setBasic("地基100米");
}
@Override
public void buildWalls() {
System.out.println("高楼的砌墙20cm ");
super.house.setWall("墙20cm");
}
@Override
public void roofed() {
System.out.println("高楼的透明屋顶 ");
super.house.setRoofed("透明屋顶");
}
}
/**
* 指挥者,调用制作方法,返回产品
*/
public class HouseDirector {
/**
* 聚合
*/
HouseBuilder houseBuilder = null;
/**
* 方式一:构造器传入 houseBuilder
*
* @param houseBuilder
*/
public HouseDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
/**
* 方式二:通过setter 传入 houseBuilder
*
* @param houseBuilder
*/
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
/**
* 指挥者统一管理建造房子的流程
*
* @return
*/
public House constructHouse() {
houseBuilder.buildBasic();
houseBuilder.buildWalls();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
可以简化,建造者模式的简化
实际上,工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象.
建造者模式是用来创建一种类型的复杂对象。通过设置不同的可选参数,“定制化”地创建不同的对象.
网上有一个经典的例子很好地解释了两者的区别.
顾客走进一家餐馆点餐,我们利用工厂模式,根据用户不同的选择,来制作不同的食物,比如披萨、汉堡、沙拉。对于披萨来说,用户又有各种配料可以定制,比如奶酪、西红柿、起司,我们通过建造者模式根据用户选择的不同配料来制作披萨.
也不要太学院派,非得把工厂模式、建造者模式分得那么清楚,我们需要知道的是,每个模式为什么这么设计,能解决什么问题。只有了解了这些最本质的东西,我们才能不生搬硬套,才能灵活应用,甚至可以混用各种模式创造出新的模式,来解决特定场景的问题.
建造者优点分析 。
建造者缺点分析 。
Builder模式有几个好处:
当然Builder模式也有缺点:
模块 | 描述 | 备注 |
---|---|---|
GitHub | 多个YC系列开源项目,包含Android组件库,以及多个案例 | GitHub |
博客汇总 | 汇聚Java,Android,C/C++,网络协议,算法,编程总结等 | YCBlogs |
设计模式 | 六大设计原则,23种设计模式,设计模式案例,面向对象思想 | 设计模式 |
Java进阶 | 数据设计和原理,面向对象核心思想,IO,异常,线程和并发,JVM | Java高级 |
网络协议 | 网络实际案例,网络原理和分层,Https,网络请求,故障排查 | 网络协议 |
计算机原理 | 计算机组成结构,框架,存储器,CPU设计,内存设计,指令编程原理,异常处理机制,IO操作和原理 | 计算机基础 |
学习C编程 | C语言入门级别系统全面的学习教程,学习三到四个综合案例 | C编程 |
C++编程 | C++语言入门级别系统全面的教学教程,并发编程,核心原理 | C++编程 |
算法实践 | 专栏,数组,链表,栈,队列,树,哈希,递归,查找,排序等 | Leetcode |
Android | 基础入门,开源库解读,性能优化,Framework,方案设计 | Android |
23种设计模式 。
23种设计模式 & 描述 & 核心作用 | 包括 |
---|---|
创建型模式 提供创建对象用例。能够将软件模块中对象的创建和对象的使用分离 |
工厂模式(Factory Pattern) 抽象工厂模式(Abstract Factory Pattern) 单例模式(Singleton Pattern) 建造者模式(Builder Pattern) 原型模式(Prototype Pattern) |
结构型模式 关注类和对象的组合。描述如何将类或者对象结合在一起形成更大的结构 |
适配器模式(Adapter Pattern) 桥接模式(Bridge Pattern) 过滤器模式(Filter、Criteria Pattern) 组合模式(Composite Pattern) 装饰器模式(Decorator Pattern) 外观模式(Facade Pattern) 享元模式(Flyweight Pattern) 代理模式(Proxy Pattern) |
行为型模式 特别关注对象之间的通信。主要解决的就是“类或对象之间的交互”问题 |
责任链模式(Chain of Responsibility Pattern) 命令模式(Command Pattern) 解释器模式(Interpreter Pattern) 迭代器模式(Iterator Pattern) 中介者模式(Mediator Pattern) 备忘录模式(Memento Pattern) 观察者模式(Observer Pattern) 状态模式(State Pattern) 空对象模式(Null Object Pattern) 策略模式(Strategy Pattern) 模板模式(Template Pattern) 访问者模式(Visitor Pattern) |
最后此篇关于03.建造者模式设计思想的文章就讲到这里了,如果你想了解更多关于03.建造者模式设计思想的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
我想编写一种方法,从单向链表中删除具有重复数据值的连续项。该方法应返回移除的项目数。该方法应根据需要清理内存,并应假定内存是使用 new 分配的。 比如传入列表 ->a->b->c->c->a->b-
大家好! 属性未在 C++ 中实现。我估计我们不能写 myObject.property = value; // try to set field f_ to value 是 property 是
我正在尝试学习 java 中的设计原理...他们说,编程一个接口(interface)并实现一个接口(interface)而不是类.. 记住这一点,这是我的用例.. 从文件中读取两种格式的数据(csv
事件、工作、 self 和联系人只不过是 DTO 对象,每个对象都可以从数据库中添加、编辑和删除。我对用例图不太熟悉,所以我想知道这是否正确或可以改进。 这里有什么可以概括的吗?实现中的添加编辑和删除
我是一名优秀的程序员,十分优秀!