设计模式学习笔记——建造者模式

 

定义

将复杂对象与其表示分离允许相同的构建过程创建不同的表示。

用户只需要指定要构建的类型来获取它们,并且不需要知道构建过程和细节。

这就是如何逐步构建具有多个组件的对象。相同的施工工艺可以产生不同的产品,这更适合于固定工艺,但不一定是固定顺序

类型

创建型

使用场景

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

希望分离复杂对象的创建和使用

优点

良好的封装性、制作和使用的分离性; 

良好的可扩展性,构造类之间的独立性,在一定程度上解耦

缺点

产生多余的Builder对象;

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

和工厂模式的区别

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

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

首先新建builder包:

创建课程实体类Course,给这个课程设置些属性,设置get/set方法以及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(); } 

然后,使用抽象构建器,我们需要实现一个真正的课程构建器,课程实际构建器,以继承课程构建器。实现方法:

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 类:

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图:

 

 

指挥官也就是助理和课程建造者组合,一个助理包含一个(抽象)课程建造者;实际的建造者包含(持有)一个课程;都是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类图:

 

 

这里我们主要看Test。这与抽象的建筑师或特定的课程无关,但是与助手无关。测试创建助手。助手以组合的方式使用CourseBuilder类,但是在这种情况下,实际的构建器CourseActual Builder用于创建Course。最后,考试通过这个助手得到具体的课程。

现在有一个CourseBuilder的继承实现类,Test负责创建特定的Builder,然后有很多不同的Builder,每个Builder都有不同的特性,比如前端过程Builder,它也需要前端资源(图片等),所有的应用层都可以根据t.o不同新事物的实际需求。将特定构建器注入到助手的责任现在交给Test了。

还有另一种方式,例如,这种教学是后端课程教学(如JavaEE Advanced),不需要前端课程图片和其他资源,那么后端课程的构建者默认可以注入到负责后端课程的教学助理中,从而应用程序层不需要关心。关于特定的构建器(没有新的课程实际构建器),应用层。它只是关于特定的课程助理。

代码实现演进

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

​ 在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方法。

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

 

 

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

 

 

所以这样我们还要在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类图:

 

 

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

源码分析

jdk源码:

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

 

 

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

Guava源码:

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

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

 

 

还有add方法:

 

 

返回的是ArrayBasedBuilder:

 

 

那么这个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:

 

 

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

Mybatis源码:

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

org.apache.ibatis.session.SqlSessionFactoryBuilder

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

 

 

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

 

 

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

public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config); }

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

 

 

我们发现,当从刚刚解析XML的地方返回时,将调用此方法。这是在构建器模式中使用构建器。解析器是XMLConfigBuilder的类型。然后调用他的解析方法并输入解析方法:

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

 

 

这里的代码非常清晰,主要负责创建和组装各种组件的配置,从上到下是组装过程。这意味着XML Config Builder主要负责创建复杂对象的配置。SqlSession Factory Builder只做了一个简单的封装层,用一个构建器包装一个构建器层。

 

转载于:https://www.cnblogs.com/chenshengjava/p/10126460.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值