設計模式學習筆記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方法。
接下來完成剩下的建造屬性的方法:

我們是要通過CourseBuilde r返回一個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只不過做了層簡單的封裝,用建造者包裝一層建造者。