1、盖房需求
盖房项目需求
1)需要建房子:这一过程为打桩、砌墙、封顶
2)房子有各种各样的,比如普通房,高楼,别墅,各种房子的过程虽然一样,但是要求不要相同的.
3)请编写程序,完成需求.
2、传统方式
public class Client {
public static void main(String[] args) {
CommonHouse commonHouse = new CommonHouse();
/**
* 普通房子打地基
* 普通房子砌墙
* 普通房子封顶
*/
commonHouse.build();
}
}
3、传统方式优缺点
-
优点是比较好理解,简单易操作。
-
设计的程序结构,过于简单,没有设计缓存层对象,程序的扩展和维护不好.也就是说
- 这种设计方案,把产品(即:房子)和创建产品的过程(即:建房子流程)封装在一起,耦合性增强了。
-
解决方案:将产品和产品建造过程解耦 => 建造者模式.
产品和过程封装在一起了,耦合性增强
4、基本介绍
**建造者模式(Builder Pattern
)**又叫生成器模式,是一种对象构建模式。
-
它可以将复杂对象的建造过程抽象出来(抽象类别),
-
使这个抽象过程的不同实现方法可以构造出不同表现(属性)的对象。
建造者模式是一步一步创建一个复杂的对象,它允许用户只通过指定复杂对象的类型和内容就可以构建它们,
用户不需要知道内部的具体构建细节。
5、四个角色 !!
Product
(产品角色):
- 一个具体的产品对象。
Builder
(抽象建造者):
- 创建一个
Product
对象的各个部件指定的 接口/抽象类。
ConcreteBuilder
(具体建造者):
- 实现接口,构建和装配各个部件。
Director
(指挥者) (工厂类):
-
就是如何组合各个部分
-
构建一个使用
Builder
接口/抽象类的对象。它主要是用于创建一个复杂的对象。 -
它主要有两个作用,
- 一是:隔离了客户与对象的生产过程,
- 二是:负责控制产品对象的生产过程。
6、案例
Client
public class Client {
public static void main(String[] args) {
//盖普通房子
HouseBuilder builder = new CommonHouseBuilder();
//准备创建房子的指挥者,进行各个部分的组合
HouseDirector houseDirector = new HouseDirector(builder);
//返回产品(房子)
House house = houseDirector.constructHouse();
System.out.println(house);
//盖高楼
builder = new HighBuildingBuilder();
//准备创建房子的指挥者,进行各个部分的组合
houseDirector.setHouseBuilder(builder);
//返回产品(房子)
House highHouse = houseDirector.constructHouse();
System.out.println(highHouse);
}
}
HouseDirector
//去动态的指定制作流程,返回产品
public class HouseDirector {
HouseBuilder houseBuilder;
public HouseDirector() {
}
//构造器传入 houseBuilder
public HouseDirector(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
//通过setter
public void setHouseBuilder(HouseBuilder houseBuilder) {
this.houseBuilder = houseBuilder;
}
//如何处理建造房子的流程,交给指挥者
public House constructHouse(){
houseBuilder.buildBasic();
houseBuilder.buildWalls();
houseBuilder.roofed();
return houseBuilder.buildHouse();
}
}
HouseBuilder
//抽象的建造者
public abstract class HouseBuilder {
protected House house = new House();
//将建造的流程写好,抽象的方法
public abstract void buildBasic();
public abstract void buildWalls();
public abstract void roofed();
//建造房子好,将产品(房子)返回
public House buildHouse(){
return house;
}
}
House
@Data
public class House {
private String baise;
private String walls;
private String roofed;
}
7、建造者模式-jdk源码
Appendable
- 接口定义了多个
append
方法(抽象方法),即Appendable
为抽象建造者,定义了抽象方法
AbstractStringBuilder
- 已经是建造者,只是不能实例化
StringBuilder
- 即充当了指挥者角色,同时充当了具体的建造者模式,
- 建造方法的实现是由
AbstractStringBuilder
完成 ,而StringBuilder
继承了AbstractStringBuilder
8、SpringMVC中的应用
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
进行处理的。
8.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
,这里不继续展开了。
8.3、总结
-
从
springMVC
通过UriComponentsBuilder
构建UriComponents
类的整个源码与流程中,我们可以窥见建造者模式在其中发挥的巨大作用。 -
它通过builder类,提供了多种
UriComponents
的初始化方式,并能根据不同情况,返回不同的UriComponents
子类。充分的将UriComponents
类本身与它的构造过程解耦合。 -
试想一下,如果不使用建造者模式,而是将大量的初始化方法直接塞到
UriComponents
类或其子类中,它的代码将变得非常庞大和冗余。而建造者模式可以帮助我们很好的解决这一问题。 -
所以,如果我们在写代码时,某个复杂的类有多种初始化形式或者初始化过程及其繁琐,并且还对应多个复杂的子类(总之就是构造起来很麻烦),我们就可以用建造者模式,将该类和该类的构造过程解耦哦!
9、注意事项与细节
1、客户端(使用程序)不必知道产品内部组成的细节,将产品本身与产品的创建过程解耦,使得相同的创建过程可以创建不同的产品对象
2、每一个具体建造者都相对独立,而与其他的具体建造者无关,因此可以很方便地替换具体建造者或增加新的具体建造者,用户使用不同的具体建造者即可得到不同的产品对象
3、可以更加精细地控制产品的创建过程。将复杂产品的创建步骤分解在不同的方法中,使得创建过程更加清晰,也更方便使用程序来控制创建过程
4、增加新的具体建造者无须修改原有类库的代码,
- 指挥者类针对抽象建造者类编程,系统扩展方便,符合“开闭原则”
5、建造者模式所创建的产品一般具有较多的共同点,其组成部分相似,如果产品之间的差异性很大,则不适合使用建造者模式,因此其使用范围受到一定的限制。
6、如果产品的内部变化复杂,可能会导致需要定义很多具体建造者类来实现这种变化,导致系统变得很庞大,因此在这种情况下,要考虑是否选择建造者模式.
7、抽象工厂模式vs建造者模式
抽象工厂模式实现对产品家族的创建,
- 一个产品家族是这样的一系列产品:
- 具有不同分类维度的产品组合,采用抽象工厂模式不需要关心构建过程,只关心什么产品由什么工厂生产即可。
- 关心结果
- 只需创建产品,创建对象
- 不注重产品的细节
而建造者模式
- 则是要求按照指定的蓝图建造产品,它的主要目的是通过组装零配件而产生一个新产品
- 注重产品的细节,这个产品很复杂
- 关心流程(指挥者)