設計模式學習筆記4:建造者模式

定義

將一個複雜的對象與它的表示分離,使得同樣的構建過程可以創建不同的表示。

​ 用戶只需要指定需要建造的類型就可以得到它們,建造過程及細節不需要知道。

​ 【就是如何一步步構建一個包含多個組件的對象,相同的構建過程可以創建不同的產品,比較適用於流程固定但是順序不一定固定】

類型

創建型

使用場景

如果一個對象有非常複雜的內部結構(很多屬性);

想把複雜的對象的創建和使用進行分離

優點

封裝性好,創建和使用分離;

擴展性好、建造類之間獨立,一定程度上解耦

缺點

產生多餘的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圖:

指揮官也就是助理和課程建造者組合,一個助理包含一個(抽象)課程建造者;實際的建造者包含(持有)一個課程;都是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,他和抽象的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方法。

接下來完成剩下的建造屬性的方法:

我們是要通過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的地方,在返回的時候調用了這個方法,這就是在建造者模式中在使用建造者,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

這裏代碼就很明白,主要負責Configuration各個組件的創建及裝配,從上到下就是裝配的流程。那說明XMLConfigBuilder主要負責創建複雜對象的Configuration,SqlSessionFactoryBuilder只不過做了層簡單的封裝,用建造者包裝一層建造者。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章