创建型模式
目录
如下图所示,大家还记得,在服务端启动的时候有个启动辅助类ServerBootStrap,我们调用group方法、channel方法设置参数。这里面也使用了链式编程来设置相关参数。
1、建造者模式
当我们创建一个复杂对象时,可能大家的第一反应就是使用工厂模式。但是如果构建一个对象非常复杂,而且有些比如说属性之类的是可选的,而且需要支持我们自己随意的动态搭配,那么这时候如果要用工厂设计模式就不太好实现了,所以这就需要配合我们的建造者模式来实现。
建造者模式(Builder Pattern)是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
建造者模式属于创建型模式,对使用者而言,只需要指定需要建造的类型就可以获得对象,建造过程和细节不需要了解。
1.1 建造者模式的UML图
1.2 日常生活中看建造者模式
肯德基做汉堡的过程都是有严格的规范的,不管是麦香鸡腿堡还是新奥尔良烤鸡腿堡,他们的制作步骤都是有严格规定,做汉堡的人既不能多做一步,也不能少做一步。对于不同的汉堡来说,虽然每一步加的料所有不同,但做汉堡的步骤都是一样的。因为有了对做汉堡过程的严格控制,因而全国所有的肯德基店做出来的汉堡味道都是一样的。
这些汉堡就是一个个对象,刚被创建出来的时候它就是两片面包片,没有任何内容;然后我们把它扔到一个流水线上,这个流水线会按照指定的步骤往汉堡对象中逐步添加材料;当汉堡走完一遍流程后,一个汉堡就做好了。
在这个过程中,汉堡就是一个需要被构造的对象,做汉堡的所有步骤就是一个个函数,他们被封装在一个建造者类中,流水线就是一个控制类,确保每一个步骤依次执行。
1.3 建造者模式角色
建造者模式的设计中,主要有4个角色。
- 产品(Product):要创建的产品对象
- 抽象建造者(Builder):建造者的抽象类,规范产品对象的各个组成部分的建造,一般由子类实现具体建造过程
- 建造者(ConcreteBuilder):具体的Builder类,根据不同的业务逻辑,具体到各个对象的各个组成部分的建造
- 调用者(Director):调用具体的建造者来创建各个对象的各个部分
1.4 具体例子
1.4.1 产品
首先我们创建一个产品,我们以家庭作业为例,假设老师会根据每个不同基础的同学布置不同难度的题目为例
package com.zwx.design.pattern.builder;
/**
* 建造者模式-产品(Product)角色
*/
public class Homework {
private String easyQc;//简答题目
private String normalQc;//正常题目
private String MediumQc;//中等难度题目
private String HardQc;//困难题目
public String getEasyQc() {
return easyQc;
}
public void setEasyQc(String easyQc) {
this.easyQc = easyQc;
}
public String getNormalQc() {
return normalQc;
}
public void setNormalQc(String normalQc) {
this.normalQc = normalQc;
}
public String getMediumQc() {
return MediumQc;
}
public void setMediumQc(String mediumQc) {
MediumQc = mediumQc;
}
public String getHardQc() {
return HardQc;
}
public void setHardQc(String hardQc) {
HardQc = hardQc;
}
}
1.4.2 抽象建造者(Builder)
接下来再创建一抽象个建造者,通过建造者来创建不同的产品
package com.zwx.design.pattern.builder.standard;
import com.zwx.design.pattern.builder.Homework;
/**
* 建造者模式-抽象建造者(Builder)
*/
public abstract class HomeworkBuilder {
public abstract HomeworkBuilder buildEasyQc(String easyQc);
public abstract HomeworkBuilder buildNormalQc(String normalQc);
public abstract HomeworkBuilder buildMediumQc(String mediumQc);
public abstract HomeworkBuilder buildHardQc(String hardQc);
public abstract Homework build();
}
这个类定义了5个方法,前面4个是产品类,实际开发中可以任意定义,相当于一个方法就是一个产品中的某一个部分,然后最后一个方法就是返回一个构建好的产品。
1.4.3 建造者(ConcreteBuilder)
创建一个实际的建造者角色类:
package com.zwx.design.pattern.builder.standard;
import com.zwx.design.pattern.builder.Homework;
/**
* 建造者模式-具体建造者(ConcreteBuilder)
*/
public class ConcreateBuilder extends HomeworkBuilder {
private Homework homework;
public ConcreateBuilder(Homework homework) {
this.homework = homework;
}
@Override
public HomeworkBuilder buildEasyQc(String easyQc) {
homework.setEasyQc(easyQc);
return this;
}
@Override
public HomeworkBuilder buildNormalQc(String normalQc) {
homework.setEasyQc(normalQc);
return this;
}
@Override
public HomeworkBuilder buildMediumQc(String mediumQc) {
homework.setEasyQc(mediumQc);
return this;
}
@Override
public HomeworkBuilder buildHardQc(String hardQc) {
homework.setEasyQc(hardQc);
return this;
}
@Override
public Homework build() {
return homework;
}
}
这个类里面初始产品我们是通过构造器传进去,实际上也可以不传,通过其他方法初始化也是可以的,这个看需求或者个人喜好。
注意:每个创建部分产品最后的返回值返回的都是this,这样就可以实现链式写法,具体看下面的调用者写法
1.4.4 调用者(Director)
最后,通过调用者来构建不同的产品
package com.zwx.design.pattern.builder.standard;
import com.alibaba.fastjson.JSONObject;
import com.zwx.design.pattern.builder.Homework;
/**
* 建造者模式-调用者(Director)
*/
public class HomeworkDirector {
public static void main(String[] args) {
Homework homework = new Homework();
HomeworkBuilder homeworkBuilder = new ConcreateBuilder(homework);
homeworkBuilder.buildEasyQc("我是一道简单题目")
.buildNormalQc("我是一道标准难度题目")
.buildMediumQc("我是一道中等难度题目")
.buildMediumQc("我是一道高难度题目");
homework = homeworkBuilder.build();
System.out.println(JSONObject.toJSONString(homework));
}
}
可以看到,这里不用和之前一样一行行去调用,直接通过链式写法一直往下build,最终调用build()方法返回完整的产品对象。
PS:很多地方的写法是把调用者根据不同组合封装起一个个方法供外界调用,这也是一种最严格标准的写法,只适合于排列组合较少结果的产品。如果有很多种搭配,还是目前这种写法比较合适,直接由使用者自己来选择搭配。
1.5 适用场景
建造者模式适用于一个具有较多的零件的复杂产品创建过程,而且产品的各个组成零件还会经常发生变化或者说需要支持动态变化,但是零件的种类却总体稳定的场景:
- 相同的方法,不同的执行顺序需要产生不同的执行结果
- 产品类非常复杂,调用不同的零件或者按照不同的顺序来组装产品后需要得到不同的产品
- 当初始化一个对象非常复杂,而且很多参数都具有默认值
2、建造者模式在源码中的应用
2.1 Netty中怎么使用Builder设计模式
如下图所示,大家还记得,在服务端启动的时候有个启动辅助类ServerBootStrap,我们调用group方法、channel方法设置参数。这里面也使用了链式编程来设置相关参数。
我们到源码中看一下group方法。调用完设置参数后,返回this对象。
我们再看看其他方法,比如childOption方法,最后设置完参数依然返回this。
其他方法依然如此。这样设计的目的在于可以自由选择设置相关参数而不强求绑定。这样可以根据使用场景进行自由的设置参数。但是缺点也是明显的,那就是使用者自己要清楚设置哪些参数。
该设计模式对我们的最大启发就是设置参数,我们可以将参数定义在一个类中,然后通过链式调用来设置参数。我们需要设置哪些参数就调用哪些方法。实际上这个设计模式在我们日常开发中都能使用到,你注意了吗?
2.2 建造者模式在MyBatis源码中的应用
MyBatis 中 SqlSessionFactoryBuiler 类用到了建造者模式。且在 MyBatis 中 SqlSessionFactory是由 SqlSessionFactoryBuilder 产生的,代码如下:
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
DefaultSqlSessionFactory 的构造器需要传入 MyBatis 核心配置类 Configuration 的对象作为参数,而 Configuration 庞大复杂,初始化比较麻烦,因此使用了专门的建造者 XMLConfigBuilder 进行构建。
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
// 创建建造者XMLConfigBuilder实例
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// XMLConfigBuilder的parse()构建Configuration实例
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.
}
}
}
XMLConfigBuilder 负责 Configuration 各个组件的创建和装配,整个装配的流程化过程如下:
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
// Configuration#
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
XMLConfigBuilder 负责创建复杂对象 Configuration,其实就是一个具体建造者角色。SqlSessionFactoryBuilder 只不过是做了一层封装去构建 SqlSessionFactory 实例,这就是建造者模式简化构建的过程。
2.3 建造者模式在SpringMVC中的实现
2.3.1 UriComponents
springMVC在构建UriComponents的内容时,就用到了建造者模式,我们先来看看UriComponents这个类是提供了哪些Components
public abstract class UriComponents implements Serializable {
private static final String DEFAULT_ENCODING = "UTF-8";
// 用于分割uri的正则表达式,下面会说到
private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
private final String scheme;
private final String fragment;
protected UriComponents(String scheme, String fragment) {
this.scheme = scheme;
this.fragment = fragment;
}
// 多个Components对应的getter方法
/**
* 返回URL的scheme.
*/
public final String getScheme() {
return this.scheme;
}
/**
* 返回URL的fragment.
*/
public final String getFragment() {
return this.fragment;
}
/**
* 返回URL的schemeSpecificPar
*/
public abstract String getSchemeSpecificPart();
/**
* 返回userInfo
*/
public abstract String getUserInfo();
/**
* 返回URL的host
*/
public abstract String getHost();
/**
* 返回URL的port
*/
public abstract int getPort();
/**
* 返回URL的path
*/
public abstract String getPath();
/**
* 返回URL的path部分的集合
*/
public abstract List<String> getPathSegments();
/**
* 返回URL的query部分
*/
public abstract String getQuery();
/**
* 返回URL的query参数map
*/
public abstract MultiValueMap<String, String> getQueryParams();
/**
* 将URL的components用特定的编码规则编码并返回,默认为utf-8
*/
public final UriComponents encode() {
try {
return encode(DEFAULT_ENCODING);
}
catch (UnsupportedEncodingException ex) {
// should not occur
throw new IllegalStateException(ex);
}
}
/**
* 编码的抽象方法,传入相应的编码规则
*/
public abstract UriComponents encode(String encoding) throws UnsupportedEncodingException;
/**
* 将URL中的模板参数换成对应的值
*/
public final UriComponents expand(Map<String, ?> uriVariables) {
Assert.notNull(uriVariables, "'uriVariables' must not be null");
return expandInternal(new MapTemplateVariables(uriVariables));
}
/**
* 将URL中的模板参数换成对应的值,输入为数组
*/
public final UriComponents expand(Object... uriVariableValues) {
Assert.notNull(uriVariableValues, "'uriVariableValues' must not be null");
return expandInternal(new VarArgsTemplateVariables(uriVariableValues));
}
/**
* 将URL中的模板参数换成对应的值,输入为UriTemplateVariables
*/
public final UriComponents expand(UriTemplateVariables uriVariables) {
Assert.notNull(uriVariables, "'uriVariables' must not be null");
return expandInternal(uriVariables);
}
/**
* 将URL中的模板参数换成对应的值的最终的实现方法
*/
abstract UriComponents expandInternal(UriTemplateVariables uriVariables);
/**
* 处理URL
*/
public abstract UriComponents normalize();
/**
* 返回URL的string
*/
public abstract String toUriString();
/**
* 返回URI格式的方法
*/
public abstract URI toUri();
@Override
public final String toString() {
return toUriString();
}
/**
* 将这些Components的值赋给其builder类
*/
protected abstract void copyToUriComponentsBuilder(UriComponentsBuilder builder);
上面的代码不包括UriComponents类下其余的静态辅助方法,单单从此类的包含多种components中,就可以看出UriComponents的复杂程度。这些components大都对应了url的某个部分,能帮助springMVC对请求的url内容进行识别。springMVC就是通过将uri构建成这个类,再对uri进行处理的。
2.3.2 UriComponentsBuilder
那么springMVC究竟是如何让请求的uri生成相应的UriComponents类呢?就要看看UriComponentsBuilder这个类了。
首先看看它的两个构造函数:
/**
* 默认构造方法,其中path的构造类为CompositePathComponentBuilder,它为UriComponentsBuilder的内部静态类,主要实现对url的path部分进行构造。
*/
protected UriComponentsBuilder() {
this.pathBuilder = new CompositePathComponentBuilder();
}
/**
* 创建一个传入UriComponentsBuilder类的深拷贝对象
*/
protected UriComponentsBuilder(UriComponentsBuilder other) {
this.scheme = other.scheme;
this.ssp = other.ssp;
this.userInfo = other.userInfo;
this.host = other.host;
this.port = other.port;
this.pathBuilder = other.pathBuilder.cloneBuilder();
this.queryParams.putAll(other.queryParams);
this.fragment = other.fragment;
}
由于url的path部分是比较复杂的,这边springMVC用了内部类的方式,为path单独加了两个builder类,分别是CompositePathComponentBuilder、FullPathComponentBuilder,这里就不扩展来说了。看完了UriComponentsBuilder的构造方法,我们来看它是如何将给定的uri生成为相应的UriComponents的。这里就从比较容易理解的fromUriString方法入手吧:
// 静态方法,从uri的字符串中获得uri的各种要素
public static UriComponentsBuilder fromUriString(String uri) {
Assert.notNull(uri, "URI must not be null");
// 利用正则表达式,获得uri的各个组成部分
Matcher matcher = URI_PATTERN.matcher(uri);
if (matcher.matches()) {
UriComponentsBuilder builder = new UriComponentsBuilder();
// 获得对应要素的字符串
String scheme = matcher.group(2);
String userInfo = matcher.group(5);
String host = matcher.group(6);
String port = matcher.group(8);
String path = matcher.group(9);
String query = matcher.group(11);
String fragment = matcher.group(13);
// uri是否透明的标志位
boolean opaque = false;
// uri存在scheme且后面不跟:/则为不透明uri
例如mailto:java-net@java.sun.com
if (StringUtils.hasLength(scheme)) {
String rest = uri.substring(scheme.length());
if (!rest.startsWith(":/")) {
opaque = true;
}
}
builder.scheme(scheme);
// 如果为不透明uri,则具备ssp,需要设置ssp
if (opaque) {
String ssp = uri.substring(scheme.length()).substring(1);
if (StringUtils.hasLength(fragment)) {
ssp = ssp.substring(0, ssp.length() - (fragment.length() + 1));
}
builder.schemeSpecificPart(ssp);
}
// 如果为绝对uri(通常意义上的uri),则设置各个component
else {
builder.userInfo(userInfo);
builder.host(host);
if (StringUtils.hasLength(port)) {
builder.port(port);
}
builder.path(path);
builder.query(query);
}
if (StringUtils.hasText(fragment)) {
builder.fragment(fragment);
}
return builder;
}
// 传入uri格式不对,抛出异常
else {
throw new IllegalArgumentException("[" + uri + "] is not a valid URI");
}
}
从上面的方法中,我们可以看到,UriComponentsBuilder从一个uri的字符串中,通过正则匹配的方式,获取到不同Components的信息并赋值。UriComponentsBuilder除了fromUriString这一种构建方法外,还提供fromUri,fromHttpUrl,fromHttpRequest,fromOriginHeader等好几种构建的方法,感兴趣的小伙伴可以自己去看。
那么在通过各种构建后,获取到了对应的Components信息,最后的一步,也是最重要的一步,build,将会返回我们需要的UriComponents类。UriComponentsBuilder提供了两类build方法,我们主要看默认的build方法:
/**
* 默认的build方法
*/
public UriComponents build() {
return build(false);
}
/**
* 具体的build实现方法,它通过ssp是否为空,判断构造的uri属于相对uri还是绝对uri,生成OpaqueUriComponents类(相对)或HierarchicalUriComponents类(绝对),它们都为UriComponents的子类
*/
public UriComponents build(boolean encoded) {
if (this.ssp != null) {
return new OpaqueUriComponents(this.scheme, this.ssp, this.fragment);
}
else {
// 调用pathBuilder的build方法,构造对应的path
return new HierarchicalUriComponents(this.scheme, this.userInfo, this.host, this.port,
this.pathBuilder.build(), this.queryParams, this.fragment, encoded, true);
}
}
可以看到,UriComponentsBuilder的build方法很简单,就是返回相应的UriComponents类。其中,在构造HierarchicalUriComponents时,还调用了pathBuilder的build方法生成uri对应的path,这里不继续展开了。
2.3.3 springMVC源码建造者模式小结
从springMVC通过UriComponentsBuilder构建UriComponents类的整个源码与流程中,我们可以窥见建造者模式在其中发挥的巨大作用。
它通过builder类,提供了多种UriComponents的初始化方式,并能根据不同情况,返回不同的UriComponents子类。充分的将UriComponents类本身与它的构造过程解耦合。
试想一下,如果不使用建造者模式,而是将大量的初始化方法直接塞到UriComponents类或其子类中,它的代码将变得非常庞大和冗余。而建造者模式可以帮助我们很好的解决这一问题。
所以,如果我们在写代码时,某个复杂的类有多种初始化形式或者初始化过程及其繁琐,并且还对应多个复杂的子类(总之就是构造起来很麻烦),我们就可以用建造者模式,将该类和该类的构造过程解耦哦!
3、建造者模式优缺点
3.1 优点
- 封装性好,创建和使用分离
- 扩展性好,建造类之间独立,一定程度上实现了解耦
3.2 缺点
- 产生多余的Builder对象
- 产品内部发生变化时,建造者都需要修改,成本较大
4、建造者模式与工厂模式区别
建造者模式优点类似于工厂模式,都是用来创建一个对象,但是他们还是有很大的区别,主要区别如下:
- 1、建造者模式更加注重方法的调用顺序,工厂模式注重于创建完整对象
- 2、建造者模式根据不同的产品零件和顺序可以创造出不同的产品,而工厂模式创建出来的产品都是一样的
- 3、建造者模式使用者需要知道这个产品有哪些零件组成,而工厂模式的使用者不需要知道,直接创建就行
参考文章:
https://blog.csdn.net/u010425776/article/details/48104143
https://www.cnblogs.com/qbin/p/11738735.html
https://zhuanlan.zhihu.com/p/266392844
https://cloud.tencent.com/developer/news/290406