设计模式学习笔记4:建造者模式

栏目: 后端 · 发布时间: 7年前

内容简介:将一个复杂的对象与它的表示分离,使得同样的构建过程可以创建不同的表示。​ 用户只需要指定需要建造的类型就可以得到它们,建造过程及细节不需要知道。​ 【就是如何一步步构建一个包含多个组件的对象,相同的构建过程可以创建不同的产品,比较适用于流程固定但是顺序不一定固定】

将一个复杂的对象与它的表示分离,使得同样的构建过程可以创建不同的表示。

​ 用户只需要指定需要建造的类型就可以得到它们,建造过程及细节不需要知道。

​ 【就是如何一步步构建一个包含多个组件的对象,相同的构建过程可以创建不同的产品,比较适用于流程固定但是顺序不一定固定】

类型

创建型

使用场景

如果一个对象有非常复杂的内部结构(很多属性);

想把复杂的对象的创建和使用进行分离

优点

封装性好,创建和使用分离;

扩展性好、建造类之间独立,一定程度上解耦

缺点

产生多余的Builder对象;

产品内部发生变化,建造者都要修改

和工厂模式的区别

建造者模式和工厂模式比较相近,但还是有区别的:

不同点 建造者模式 工厂模式
着重点 更注重方法的调用顺序 注重创建产品
创建力度 可以创建一些复杂的产品 创建的都是一个模样
关注点 不止创造这个产品,还要知道产品的组成部件 创建出想要的对象就行

代码实现

业务场景:建造在线学习网站的视频教学课程,就比如建造 Java 课程。

首先新建builder包:

创建课程实体类Course,给这个课程设置些属性,设置get/set方法以及toString:

package com.ljm.design.pattern.creational.builder;

/**
 * 课程实体类
 */
public class Course {
    //为了方便全使用String,因为这不是重点
    private String courseName;//名字
    private String coursePPT;//PPT
    private String courseVideo;//视频
    private String courseArticle;//文章
    private String courseQA;//问答
    
	//为节省空间,set/get/toString省略
}
复制代码

接下来创建一个抽象类。这个类是课程的建造者,然后根据课程类Course的属性声明他们的建造方法,最后再声明一个构造课程整体的抽象方法:

package com.ljm.design.pattern.creational.builder;

//抽象的课程建造者
public abstract class CourseBuilder {

    public abstract void builderCourseName(String courseName);
    public abstract void builderCoursePPT(String coursePPT);
    public abstract void builderCourseVideo(String courseVideo);
    public abstract void builderCourseArticle(String courseArticle);
    public abstract void builderCourseQA(String courseQA);

    //属性就建造完成后,建造课程并返回
    public abstract Course makeCourse();

}
复制代码

接下有了抽象建造者,就要来实现一个真正的课程建造者CourseActualBuilder继承CourseBuilder。,实现里面的方法:

package com.ljm.design.pattern.creational.builder;

//课程的实际建造者
public class CourseActualBuilder extends CourseBuilder {
    //这里简单实现以下,直接设置属性,最后返回
    private Course course = new Course();
    
    @Override
    public void builderCourseName(String courseName) {
        course.setCourseName(courseName);
    }
    @Override
    public void builderCoursePPT(String coursePPT) {
        course.setCoursePPT(coursePPT);
    }
    @Override
    public void builderCourseVideo(String courseVideo) {
        course.setCourseVideo(courseVideo);
    }
    @Override
    public void builderCourseArticle(String courseArticle) {
        course.setCourseArticle(courseArticle);
    }
    @Override
    public void builderCourseQA(String courseQA) {
        course.setCourseQA(courseQA);
    }

    @Override
    public Course makeCourse() {
        return course;
    }
}
复制代码

在这里引入一个课程助理Assistant ,课程讲师和学习网站合作时,网站老板肯定不会和讲师谈业务,而是会指派一个业务人员和讲师对接,那这个人可以称之为课程助理。网站老板在下达课程任务,会告诉课程助理,然后助理和对应的讲师进行对接,然后来共同制作这个课程。

这里可以认为助理是一个指挥官,讲师负责课程(提交课程属性),课程助理通过讲师提交的资料拼接成一个完整的课程。

接下来完成Assistant 类:

package com.ljm.design.pattern.creational.builder;

//课程助理类
public class Assistant {
    //助理负责组装课程,可定有CourseBuilder
    private CourseBuilder courseBuilder;
    //通过set注入
    public void setCourseBuilder(CourseBuilder courseBuilder) {
        this.courseBuilder = courseBuilder;
    }

    //声明组装行为,返回课程
    public Course makeCourse(String courseName, String coursePPT,
                             String courseVideo, String courseArticle,
                             String courseQA){
        this.courseBuilder.builderCourseName(courseName);
        this.courseBuilder.builderCoursePPT(coursePPT);
        this.courseBuilder.builderCourseVideo(courseVideo);
        this.courseBuilder.builderCourseArticle(courseArticle);
        this.courseBuilder.builderCourseQA(courseQA);
        
        return this.courseBuilder.makeCourse();
    }
}
复制代码

现在来看看这几个类的UML图:

设计模式学习笔记4:建造者模式

指挥官也就是助理和课程建造者组合,一个助理包含一个(抽象)课程建造者;实际的建造者包含(持有)一个课程;都是1:1关系。

最后来创建测试类Test:

public class Test {
    public static void main(String[] args) {
        //抽象的父类引用来创建子类实现
        CourseBuilder courseBuilder = new CourseActualBuilder();
        //new一个助理
        Assistant assistant = new Assistant();
        //注入builder
        assistant.setCourseBuilder(courseBuilder);

        //调用助产生课程的方法
        Course course = assistant.makeCourse("JavaEE高级","JavaEE高级PPT",
                "JavaEE高级视频","JavaEE高级文章","JavaEE高级问答");
        System.out.println(course);
    }
}
复制代码

再来看一下UML类图:

设计模式学习笔记4:建造者模式

这里主要看Test,他和抽象的builder和具体的课程都没有关系,但是和助理有关系,Test来创建助理,助理通过组合的方式使用CourseBuilder类,但是着这个例子中实际使用的是实际的建造者CourseActualBuilder来创建Course。最后Test通过这个助理拿到具体的课程类。

​ 现在对于CourseBuilder有一个继承的实现类,而Test负责创建具体的Builder,那么就可以有很多不同的Builder,每个Builder他们特点都不一样,就比如还有个前端课程的Builder,里面还要builder一个前端资源(图片等),所有可以再应用层根据实际需求的不同new出实际建造者传给助理,也就是说注入具体建造者到助理的职责现在交给Test。

​ 还有一种方式,就比如这个教学就是一个后端课程的教学(就比如JavaEE高级),完全不需要前端课程的图片等资源,那么就可以把后端课程的builder默认注入负责后端课程的教学助理当中,这样应用层就不用关心具体的建造者(不用 new CourseActualBuilder),应用层只和具体的课程助理有关。

代码实现演进

​ 上面代码实现中引入了一个助理类,但这个助理类不是必须的

​ 在builder包创建一个文件夹,com.ljm.design.pattern.creational.builder.v2,表示版本2.

先来创建一个课程类Course,这里要使用静态内部类,这个内部类就是建造者,收先还是设置跟前面一样的属性,重写toString方便测试,然后声明一个静态内部类CourseBuilder,静态内部类还是有一样的五个属性,直接写建造每个属性的方法,

package com.ljm.design.pattern.creational.builder.v2;

/**
 * 课程实体类
 * v2
 */
public class Course {
    private String courseName;//名字
    private String coursePPT;//PPT
    private String courseVideo;//视频
    private String courseArticle;//文章
    private String courseQA;//问答

    @Override
    public String toString() {
        return "Course{" +
                "courseName='" + courseName + '\'' +
                ", coursePPT='" + coursePPT + '\'' +
                ", courseVideo='" + courseVideo + '\'' +
                ", courseArticle='" + courseArticle + '\'' +
                ", courseQA='" + courseQA + '\'' +
                '}';
    }

    //静态内部类:建造者
    public static class CourseBuilder{
        private String courseName;//名字
        private String coursePPT;//PPT
        private String courseVideo;//视频
        private String courseArticle;//文章
        private String courseQA;//问答

        /**
         * 建造属性
         */
        public void builderCourseName(String courseName){
        	this.courseName = courseName;
        }
    }
}
复制代码

演进班的核心在于 链式调用 ,所以要把建造属性的方法的返回改成这个静态内部类本身,所以上面的建造属性的方法应该这样写:

/**
 * 建造属性
 */
public CourseBuilder builderCourseName(String courseName){
    this.courseName = courseName;
    return this;//返回的是本身
}
复制代码

这样,返回本身之后就还可以调用其他的builder方法。

接下来完成剩下的建造属性的方法:

设计模式学习笔记4:建造者模式

我们是要通过CourseBuilde r返回一个Course ,那么在Course类中写一个(空)构造器,但是构造器的参数改为CourseBuilder,而这个参数正式Course的静态内部内CourseBuilder创建的对象:

设计模式学习笔记4:建造者模式

所以这样我们还要在CourseBuilder类中在写一个方法builder,返回Course:

public Course builder(){
    return new Course(this);
}
复制代码

然后再来完善一下Course的构造器:

public Course(CourseBuilder courseBuilder) {
    this.courseName = courseBuilder.courseName;
    this.coursePPT = courseBuilder.coursePPT;
    this.courseVideo = courseBuilder.courseVideo;
    this.courseArticle = courseBuilder.courseArticle;
    this.courseQA = courseBuilder.courseQA;
}
复制代码

这样呢,Course里面的的所有属性就通过CourseBuilder构建成功了

最后再来写一个测试类:

public class Test {
    public static void main(String[] args) {
        /**
         * 这就是链式调用(也叫链式编程)的效果,可以一直调用
         * 并且可以选择性调用
         * 因为使用Course接收,所以最后要调用CourseBuilder的builder方法
         */
        Course course = new Course.CourseBuilder().builderCourseName("JavaEE高级")
                .builderCoursePPT("JavaEE高级PPT").builderCourseVideo("JavaEE高级Video")
                .builderCourseQA("JavaEE高级QA").builder();
        System.out.println(course);
    }
}
复制代码

大家可以和之前的Test的代码对比,感受一下演进版的好处。

再来看一下v2版本的UML类图:

设计模式学习笔记4:建造者模式

这个图现在非常简单,Test创建Course具体的建造者CourseBuilder,在通过CourseBuilder建造Course。

源码分析

jdk源码:

以java.lang.StringBuilder为例,从这个类名就可以看出他是一个Builder,他的append方法是我们经常用的,里面很多重载:

设计模式学习笔记4:建造者模式

StringBuffer也是一样的,只不过StringBuffer里面加了同步锁。

Guava源码:

除了jdk,很多开源框架也大量使用建造者模式,Google的开源框架Guava为例,找到avro.shaded.com.google.common.collect.ImmutableSet,这个类本身就是不可变的Set,

里面的copyOf方法返回值也是ImmutableSet

设计模式学习笔记4:建造者模式

还有add方法:

设计模式学习笔记4:建造者模式

返回的是ArrayBasedBuilder:

设计模式学习笔记4:建造者模式

那么这个Builder肯定存在一个builder方法,Ctrl+F12 搜索发现最后确实有一个builder方法:

/**
 * Returns a newly-created {@code ImmutableSet} based on the contents of
 * the {@code Builder}.
 */
@Override public ImmutableSet<E> build() {
  ImmutableSet<E> result = construct(size, contents);
  // construct has the side effect of deduping contents, so we update size
  // accordingly.
  size = result.size();
  return result;
}
复制代码

这个就很像我们写的v2版本的代码。

可以实际写一下,还是在v2包的Test代码中写(暂时忽略前面写的):

Set<String> set = ImmutableSet.<String>builder().add("a").add("b").build();
System.out.println(set);
复制代码

Spring源码:

再看一个Spring中的org.springframework.beans.factory.support.BeanDefinitionBuilder:

设计模式学习笔记4:建造者模式

可以看到它里面的方法返回的都是自己本身。也是一个典型的建造者模式。

Mybatis源码:

看一些Mybatis中对于建造者模式的典型应用:

org.apache.ibatis.session.SqlSessionFactoryBuilder

从名字就可以看出这也是一个Builder,

设计模式学习笔记4:建造者模式

这个Builder返回的都是SqlSessionFactory,里面还有一个:

设计模式学习笔记4:建造者模式

这个就是解析Mybatis的xml文件,这里面的核心就是builder方法:

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}
复制代码

这个builder传入的是Configuration配置,再把配置传给DefaultSqlSessionFactory进行构造,看一下哪里使用了这个方法(方法名选中,Alt+F7,双击进入):

设计模式学习笔记4:建造者模式

发现还是在刚刚解析xml的地方,在返回的时候调用了这个方法,这就是在建造者模式中在使用建造者,parser就是XMLConfigBuilder类型,然后调用他的parse方法,进入parse方法:

public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}
复制代码

而parse方法调用了parseConfiguration,进入parseConfiguration

设计模式学习笔记4:建造者模式

这里代码就很明白,主要负责Configuration各个组件的创建及装配,从上到下就是装配的流程。那说明XMLConfigBuilder主要负责创建复杂对象的Configuration,SqlSessionFactoryBuilder只不过做了层简单的封装,用建造者包装一层建造者。


以上所述就是小编给大家介绍的《设计模式学习笔记4:建造者模式》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

深入理解计算机系统(英文版·第2版)

深入理解计算机系统(英文版·第2版)

[美] Randal E. Bryant、[美] David R. O'Hallaron / 机械工业出版社 / 2011-1 / 128.00元

本书是一本将计算机软件和硬件理论结合讲述的经典教程,内容覆盖计算机导论、体系结构和处理器设计等多门课程。本书的最大优点是为程序员描述计算机系统的实现细节,通过描述程序是如何映射到系统上,以及程序是如何执行的,使读者更好地理解程序的行为为什么是这样的,以及造成效率低下的原因。 相对于第1版,本版主要是反映了过去十年间硬件技术和编译器的变化,具体更新如下: 1. 对系统的介绍(特别是实际使......一起来看看 《深入理解计算机系统(英文版·第2版)》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试