一、概述
Builder设计模式是什么,什么场景需要,如何用,起到什么样的作用,在优秀源码中有没有使用案例,他有缺点吗,希望本文可以解答您的疑惑。
二、Builder模式是什么
2.1 定义
Builder 模式(又叫建造者模式或生成器模式),它属于对象创建型模式,是将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
2.2 具体是什么
2.2.1 Builder模式主要涉及哪几个角色
要建造的产品Product
指挥者(Director),负责和客户(Client)对话
指挥者将客户的产品需求划分为比较稳定的建造过程(抽象的Builder)
指挥者指挥具体的建造者(ConcreteBuilder)干活
2.2.2 示例
1.首先创建一个Product类
public class Product {
private String param1;
private String param2;
public String getParam1() {
return param1;
}
public void setParam1(String param1) {
this.param1 = param1;
}
public String getParam2() {
return param2;
}
public void setParam2(String param2) {
this.param2 = param2;
}
}
2.将客户对产品的需求划分比较稳定的建造过程,ProductBuilder就是将Product产品的建造过程抽象出来的Builder类
public interface ProductBuilder {
void setParam1();
void setParam2();
Product getProduct();
}
3.刚才的Builder类是Product的抽象建造过程,接下来的OneProductBuilder和TwoProductBuilder类都是具体的Product的建造过程。
public class OneProuductBuilder implements ProductBuilder{
private Product product;
public OneProuductBuilder(){
this.product = new Product();
}
@Override
public void setParam1() {
product.setParam1("oneProduct_Param1");
}
@Override
public void setParam2() {
product.setParam2("oneProduct_Param2");
}
@Override
public Product getProduct() {
return product;
}
}
public class TwoProuductBuilder implements ProductBuilder{
private Product product;
public TwoProuductBuilder(){
this.product = new Product();
}
@Override
public void setParam1() {
product.setParam1("twoProduct_param1");
}
@Override
public void setParam2() {
product.setParam2("twoProduct_param2");
}
@Override
public Product getProduct() {
return product;
}
}
4.这是指挥者,通过setProductBuilder方法可以告诉他客户需要什么样的产品,然后通过createProduct方法来一步步建造产品,最后通过getProduct方法获取客户想要的产品
public class Director {
private ProductBuilder productBuilder;
public void setProductBuilder(ProductBuilder productBuilder){
this.productBuilder = productBuilder;
}
public void createProduct(){
productBuilder.setParam1();
productBuilder.setParam2();
}
public Product getProduct(){
return productBuilder.getProduct();
}
}
5.这是客户类,客户想要一个什么样的产品,只要告诉指挥者我需要什么样的产品,然后指挥者就开始指挥具体工人(也就是具体建造实现者)开始建造产品,最后客户从指挥者那儿拿到自己想要的产品。
public class Client {
public static void main(String[] args){
Director director = new Director();
director.setProductBuilder(new OneProuductBuilder());
director.createProduct();
Product product = director.getProduct();
}
}
2.2.3 什么场景适合使用Builder模式
当Product类可能存在以下情况:
有很多属性
属性分必选属性和非必选属性
产品之间的组成部分相似,具有很多共同点
当Product存在很多属性和可选属性时,我们如何构造这个类的实例呢,通常有两种常用的方式:
折叠构造函数模式
public class Product {
private String param1;
private String param2;
private String param3;
private String param4;
public Product(String param1){
this(param1,"param2");
}
public Product(String param1,String param2){
this(param1,param2,"param3");
}
public Product(String param1,String param2,String param3){
this(param1,param2,param3,"param4");
}
public Product(String param1,String param2,String param3,String param4){
this.param1 = param1;
this.param2 = param2;
this.param3 = param3;
this.param4 = param4;
}
}
JavaBean模式
public class Product {
private String param1;
private String param2;
private String param3;
private String param4;
public String getParam1() {
return param1;
}
public void setParam1(String param1) {
this.param1 = param1;
}
public String getParam2() {
return param2;
}
public void setParam2(String param2) {
this.param2 = param2;
}
...
}
这两种方式都存在弊端,第一种方式在使用和阅读上都不方便,当很多参数类型是相同时很容易传混。第二种方式在构建过程中对象的属性容易发生变化,造成错误。
为了更好的解决多属性设置的问题,我们可以通过链式调用的方式来很优雅的设置属性,例如以下代码:
public class Product {
private String param1;
private String param2;
private String param3;
private String param4;
public Product(String param1,String param2)
public void setParam3(String param3) {
this.param3 = param3;
return this;
}
public void setParam4(String param4) {
this.param4 = param4;
return this;
}
...
}
Product product = new Product("xx1","xx2").setParam3("3").setParam4("4")..
通过这种方式可以很优雅的设置对象的属性。对于必选属性可以放在构造函数里,对于可选属性可以通过set的方式任意设置。
三、Builder模式的使用
3.1 Builder的另一种写法
Product是我们要创建的产品,而ProductBuilder是我们将Product的建造过程抽象得到的类,跟上面例子不同的是,这边我们将ProductBuilder作为一个静态内部类放在Product类中。
public class Product {
private int index;
private String name;
Product(int index, String name){
this.index = index;
this.name = name;
}
public static ProductBuilder builder() {
return new ProductBuilder();
}
private static class ProductBuilder {
private int index;
private String name;
public ProductBuilder setIndex(int index) {
this.index = index;
return this;
}
public ProductBuilder setName(String name){
this.name = name;
return this;
}
public Product build(){
Product product = new Product(index,name);
return product;
}
}
}
Product name = Product.builder().setIndex(1).setName("name").build();
第二节的例子具体的创建过程都是由具体实现的Builder的来实现的,而这边是通过在客户端创建时自定义实现。上面还有一个指挥者Director类,主要用来与客户端沟通,客户端需要什么样的实现,指挥者就根据客户端指定的实现找到具体的实现者并建造具体产品。但因为这里将自定义实现放给了客户端,由客户端自由实现,便就不需要Director类了。这两个例子只是使用场景不同,但本质都是Builder模式。
3.2 Fluent Builder
虽然builder模式可以解决多属性的复杂对象的建造问题,但还是很容易获得不完整的(部分初始化的)对象。为了缓解这个问题,我们一般会在build方法上进行校验检查。但这种检查方式将检查转移到了运行时。
那有没有更好的办法将这种检查转移到编译时呢? 答:Fluent API模式
Fluent API有两个部分:
提供一种方便的方式来链式调用方法,并将调用链中的每个后续调用都限制为一组允许调用的方法集
通过限制建造对象的每个步骤中可以调用的方法集,我们可以强制执行特定的调用序列,并且在所有方法调用之后才能调用build方法。
通过Fluent API模式可以将检查转移到编译时。如下面的例子:
public class Product {
private int index;
private String name;
Product(int index, String name){
this.index = index;
this.name = name;
}
public static ProductBuilder builder() {
return new ProductBuilder();
}
private static class ProductBuilder {
public ProductBuilder1 setIndex(int index) {
return new ProductBuilder1() {
@Override
public ProductBuilder2 setName(final String name) {
return new ProductBuilder2() {
@Override
public Product build() {
return new Product(index, name);
}
};
}
};
}
public interface ProductBuilder1 {
ProductBuilder2 setName(final String name);
}
public interface ProductBuilder2 {
Product build();
}
}
}
Product name = Product.builder().setIndex(1).setName("name").build();
为了实现Fluent API的功能,我们可以通过定义接口方式来定义一组调用方法集,每次调用都返回这样的专用接口。然后通过匿名内部类的方式来实现这些接口。并且build方法定义在ProudctBuilder2接口里,这样用户可以避免用户过早构建对象。
当然,我们可以通过Lambda表达式来替代匿名内部类的写法,这样代码可以看起来更精简点
public class Product {
private int index;
private String name;
Product(int index, String name){
this.index = index;
this.name = name;
}
public static ProductBuilder builder() {
return new ProductBuilder();
}
private static class ProductBuilder {
public ProductBuilder1 setIndex(int index) {
return name -> () -> new Product(index, name);
}
public interface ProductBuilder1 {
ProductBuilder2 setName(final String name);
}
public interface ProductBuilder2 {
Product build();
}
}
}
3.3 Builder模式的优缺点
优点:
将产品本身内部组成细节和创建过程进行解耦,这样客户端就无需关心产品内部细节,只需要通过抽象出来的相同的创建过程来创建不同的产品对象
对于抽象的创建过程(Builder),我们可以进行自由的实现具体的建造者(ConcreteBuilder),符合开闭原则。用户使用不同的具体建造者(ConcreteBuilder)就可以得到不同的产品对象
缺点:
对产品足够复杂的情况下,我们的具体建造者会越来越多,不利于管理
产品之间的构建过程差异比较大的情况下,不太适合使用Builder模式
四、Builder模式在开源框架的使用案例
4.1 UriComponents和UriComponentsBuilder
UriComponents uriComponents = UriComponentsBuilder.fromUriString(
"http://127.0.0.1:80/queryUserInfo/{userId}").build();
URI uri = uriComponents.expand("66").encode().toUri();

根据上图分析各个角色的对应情况:
UriComponentsBuilder:具体建造者
UriBuilder:抽象建造者,定义了创建对象的接口
UriComponents:产品角色
通过UriComponentsBuilder类,提供了多种UriComponents的建造方式,将UriComponents类本身与它的建造过程解耦
4.2 SqlSessionFactoryBuilder
ClassPathResource resource = new ClassPathResource("mybatis-config.xml");
InputStream inputStream = resource.getInputStream();
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
通过上面代码发现,创建SqlSessionFactory的代码在SqlSessionFactoryBuilder中
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.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}

根据上图分析各个角色的对应情况:
SqlSessionFactoryBuilder:指挥者角色
BaseBuilder:抽象Builder
XMLConfigBuilder:具体的Builder
SqlSessionFactory:需要被创建的产品
五、参考
https://zhuanlan.zhihu.com/p/58093669
https://www.jianshu.com/p/afe090b2e19c
https://www.jdon.com/48272
https://dzone.com/articles/why-builder-often-is-antipattern-and-how-to-replac
https://blog.csdn.net/weixin_44848573/article/details/106130651
https://www.cnblogs.com/54chensongxia/p/12409493.html
https://blog.csdn.net/guorui_java/article/details/106684541




