002-创建型-04-建造者模式(Builder)、JDK1.7源码中的建造者模式、Spring中的建造者模式
一、概述
建造者模式的定义:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
工厂类模式提供的是创建单个类的模式,而建造者模式则是将各种产品集中起来进行管理,用来创建复合对象,所谓复合对象就是指某个类具有不同的属性,其实建造者模式就是前面抽象工厂模式和最后的Test结合起来得到的。
所以,如果我们在写代码时,某个复杂的类有多种初始化形式或者初始化过程及其繁琐,并且还对应多个复杂的子类(总之就是构造起来很麻烦),我们就可以用建造者模式,将该类和该类的构造过程解耦!
1.1、适用场景
- 如果一个对象有非常复杂的内部结构,在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。
- 想把复杂对象的创建和使用分离。
1.2、优缺点
优点
- 封装性好,创建和使用分离
- 扩展性好、建造类之间独立、一定程度上解耦
缺点
- 产生多余的builder对象
- 产品内部发送变化,建造者都要修改,成本比较大
1.3、类图角色及其职责
- Director:指挥者/导演类,负责安排已有模块的顺序,然后告诉Builder开始建造。
- Builder:抽象建造者,规范产品的组建,一般由子类实现。
- ConcreteBuilder:具体建造者,实现抽象类定义的所有方法,并且返回一个组建好的对象。
- Product:产品类,通常实现了模板方法模式。
1.4、演进
初始写法
- Person person =new Person();
- person.setName("lhx");
- person.setAge(1);
链式setter写法【需要修改类的setter方法】
- Person person =new Person().setName("lhx").setAge(1);
根据setter链式调用的思路,演进出另一套解决方案Builder模式
Builder模式
创建一个人对象,属性有name,number,class,sex,age,school等属性,如果每一个属性都可以为空,也就是说我们可以只用一个name,也可以用一个school,name,或者一个class,number,或者其他任意的赋值来创建一个学生对象,这时该怎么构造?
难道我们写6个1个输入的构造函数,15个2个输入的构造函数.......吗?这个时候就需要用到Builder模式了。示例:
- public class Person {
- private String name;
- private Integer number;
- private String sex;
- private Integer age;
- private String school;
- public Person(Builder builder) {
- this.age = builder.age;
- this.name = builder.name;
- this.number = builder.number;
- this.school = builder.school;
- this.sex = builder.sex;
- }
- //构建器,利用构建器作为参数来构建Student对象
- public static class Builder {
- private String name;
- private Integer number;
- private String sex;
- private Integer age;
- private String school;
- public Builder setName(String name) {
- this.name = name;
- return this;
- }
- public Builder setNumber(int number) {
- this.number = number;
- return this;
- }
- public Builder setSex(String sex) {
- this.sex = sex;
- return this;
- }
- public Builder setAge(int age) {
- this.age = age;
- return this;
- }
- public Builder setSchool(String school) {
- this.school = school;
- return this;
- }
- public Person build() {
- return new Person(this);
- }
- }
- }
测试
- @Test
- public void testBuilder() {
- Person student = new Person.Builder().setName("aaa").setAge(11).build();
- }
二、扩展
2.1、JDK1.7源码中的建造者模式
2.1.1、StringBuilder
中一部分源码,典型的建造者模式
- public StringBuilder append(boolean b) {
- super.append(b);
- return this;
- }
- public StringBuilder append(char c) {
- super.append(c);
- return this;
- }
- public StringBuilder append(int i) {
- super.append(i);
- return this;
- }
- public StringBuilder append(long lng) {
- super.append(lng);
- return this;
- }
- public StringBuilder append(float f) {
- super.append(f);
- return this;
- }
- public StringBuilder append(double d) {
- super.append(d);
- return this;
- }
2.1.2、StringBuffer
StringBuffer中一部分源码,比StringBuilder多一个synchronized,典型的建造者模式
- public synchronized StringBuffer append(boolean b) {
- super.append(b);
- return this;
- }
- public synchronized StringBuffer append(char c) {
- super.append(c);
- return this;
- }
- public synchronized StringBuffer append(int i) {
- super.append(i);
- return this;
- }
2.2、spring中的建造者模式
2.2.1、UriComponents和UriComponentsBuilder
UriComponents基本方法
- 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);
- //……
- }
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。
它是如何将给定的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
小结
从springMVC通过UriComponentsBuilder构建UriComponents类的整个源码与流程中,我们可以窥见建造者模式在其中发挥的巨大作用。
它通过builder类,提供了多种UriComponents的初始化方式,并能根据不同情况,返回不同的UriComponents子类。充分的将UriComponents类本身与它的构造过程解耦合。
试想一下,如果不使用建造者模式,而是将大量的初始化方法直接塞到UriComponents类或其子类中,它的代码将变得非常庞大和冗余。而建造者模式可以帮助我们很好的解决这一问题。
2.2.2、BeanDefinitionBuilder
一系列的方法
- public BeanDefinitionBuilder setParentName(String parentName) {
- this.beanDefinition.setParentName(parentName);
- return this;
- }
- public BeanDefinitionBuilder setFactoryMethod(String factoryMethod) {
- this.beanDefinition.setFactoryMethodName(factoryMethod);
- return this;
- }
- public BeanDefinitionBuilder setFactoryMethodOnBean(String factoryMethod, String factoryBean) {
- this.beanDefinition.setFactoryMethodName(factoryMethod);
- this.beanDefinition.setFactoryBeanName(factoryBean);
- return this;
- }
示例、Spring中实现动态注册bean
- @Test
- public void testSpringBuilder() {
- ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-budiler.xml");
- PersonSpringBeanService personSpringBeanService = (PersonSpringBeanService) context.getBean("personSpringBeanService");
- if (personSpringBeanService != null) {
- personSpringBeanService.test();
- } else {
- System.out.println("没有personSpringBeanService bean");
- }
- //将applicationContext转换为ConfigurableApplicationContext
- ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) context;
- // 获取bean工厂并转换为DefaultListableBeanFactory
- DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory();
- // 通过BeanDefinitionBuilder创建bean定义
- BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(PersonSpringBeanService.class);
- // 设置属性personSpringBeanDao,此属性引用已经定义的bean:personSpringBeanDao
- // beanDefinitionBuilder.addPropertyReference("personSpringBeanDao", "personSpringBeanDao");
- // 注册bean,第一个参数为BeanName
- defaultListableBeanFactory.registerBeanDefinition("personSpringBeanService", beanDefinitionBuilder.getRawBeanDefinition());
- PersonSpringBeanService personSpringBeanService1 = (PersonSpringBeanService) context.getBean("personSpringBeanService");
- personSpringBeanService1.test();
- }
2.3、其他
2.3.1、mybatis中的SqlSessionFactoryBuilder
002-创建型-04-建造者模式(Builder)、JDK1.7源码中的建造者模式、Spring中的建造者模式的更多相关文章
- OpenJDK源码研究笔记(十三):Javac编译过程中的上下文容器(Context)、单例(Singleton)和延迟创建(LazyCreation)3种模式
在阅读Javac源码的过程中,发现一个上下文对象Context. 这个对象用来确保一次编译过程中的用到的类都只有一个实例,即实现我们经常提到的"单例模式". 今天,特意对这个上下文 ...
- 基于Ubuntu 14.04 LTS编译Android4.4.2源码
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/gobitan/article/details/24367439 基于Ubuntu 14.04 LTS ...
- 【JUC】JDK1.8源码分析之ArrayBlockingQueue(三)
一.前言 在完成Map下的并发集合后,现在来分析ArrayBlockingQueue,ArrayBlockingQueue可以用作一个阻塞型队列,支持多任务并发操作,有了之前看源码的积累,再看Arra ...
- 集合之HashSet(含JDK1.8源码分析)
一.前言 我们已经分析了List接口下的ArrayList和LinkedList,以及Map接口下的HashMap.LinkedHashMap.TreeMap,接下来看的是Set接口下HashSet和 ...
- 【1】【JUC】JDK1.8源码分析之ArrayBlockingQueue,LinkedBlockingQueue
概要: ArrayBlockingQueue的内部是通过一个可重入锁ReentrantLock和两个Condition条件对象来实现阻塞 注意这两个Condition即ReentrantLock的Co ...
- 【1】【JUC】JDK1.8源码分析之ReentrantLock
概要: ReentrantLock类内部总共存在Sync.NonfairSync.FairSync三个类,NonfairSync与FairSync类继承自Sync类,Sync类继承自AbstractQ ...
- JDK1.8源码阅读系列之三:Vector
本篇随笔主要描述的是我阅读 Vector 源码期间的对于 Vector 的一些实现上的个人理解,用于个人备忘,有不对的地方,请指出- 先来看一下 Vector 的继承图: 可以看出,Vector 的直 ...
- 【集合框架】JDK1.8源码分析之HashMap(一) 转载
[集合框架]JDK1.8源码分析之HashMap(一) 一.前言 在分析jdk1.8后的HashMap源码时,发现网上好多分析都是基于之前的jdk,而Java8的HashMap对之前做了较大的优化 ...
- 【集合框架】JDK1.8源码分析之ArrayList详解(一)
[集合框架]JDK1.8源码分析之ArrayList详解(一) 一. 从ArrayList字表面推测 ArrayList类的命名是由Array和List单词组合而成,Array的中文意思是数组,Lis ...
随机推荐
- Shell脚本字符串匹配及日常命令工具 - 用法总结(技巧指南)
Shell提供了很多字符串和文件处理的命令,如awk.expr.grep.sed等命令,还有文件的排序.合并和分割等一系列的操作命令.下面重点总结下Shell字符串处理.文本处理以及各类命令及函数用法 ...
- sqlalchemy.exc.CompileError: (in table 'user', column 'username'): VARCHAR requires a length on dialect mysql
映射数据库时报错:sqlalchemy.exc.CompileError: (in table 'user', column 'username'): VARCHAR requires a lengt ...
- 转【Ubuntu】添加虚拟网卡的三种方式
原文:https://blog.csdn.net/White_Idiot/article/details/82934338 ------------------------------ 1. ifco ...
- flutter,flutter版本version/channel切换问题
flutter go,官方的指南版本如下: 如何设置版本和channel,尝试 flutter help,发现原来flutter version不单是可以查所有版本(--version查当前版本)还可 ...
- Vue 之 slot(插槽)
前言: vue中关于插槽的文档说明很短,语言又写的很凝练,再加上其和methods,data,computed等常用选项在使用频率.使用先后上的差别,这就有可能造成初次接触插槽的开发者容易产生“算了吧 ...
- 关于LinkedList for OpenJDK
概述 LinkedList采用底层采用双向链表结构,与ArrayList的数组结构不一样.LinkedList因数据结构不一样,不需要申请连续内存,可以利用碎片内存.元素保存数据内容外还需要 ...
- layui table 跨页记忆选择
layui 表格功能目前默认不支持跨页记忆选择 下面来实现layui table跨页记忆选择实现 基于layui版本 1.4.5 表格跨页通用方法 //表格分页复选框 layui.define(['j ...
- 搭建gitlab服务
安装依赖 sudo yum install curl policycoreutils openssh-server openssh-clients sudo systemctl enable sshd ...
- Shared Nothing、Shared Everthting、Shared Disk
数据库构架设计中主要有Shared Everthting.Shared Nothing.和Shared Disk:1.Shared Everything:一般是针对单个主机,完全透明共享CPU/MEM ...
- NetworkX系列教程(10)-算法之五:广度优先与深度优先
小书匠Graph图论 重头戏部分来了,写到这里我感觉得仔细认真点了,可能在NetworkX中,实现某些算法就一句话的事,但是这个算法是做什么的,用在什么地方,原理是怎么样的,不清除,所以,我决定先把图 ...