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

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

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

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

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

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

类型

创建型

使用场景

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

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

优点

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

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

缺点

产生多余的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:建造者模式》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

Programming From The Ground Up

Programming From The Ground Up

Jonathan Bartlett / Bartlett Publishing / 2004-07-31 / USD 34.95

Programming from the Ground Up is an introduction to programming using assembly language on the Linux platform for x86 machines. It is a great book for novices who are just learning to program as wel......一起来看看 《Programming From The Ground Up》 这本书的介绍吧!

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具