設計模式學習筆記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只不過做了層簡單的封裝,用建造者包裝一層建造者。