設計模式學習筆記——建造者模式

 

定義

將複雜對象與其表示分離容許相同的構建過程建立不一樣的表示。前端

用戶只須要指定要構建的類型來獲取它們,而且不須要知道構建過程和細節。java

這就是如何逐步構建具備多個組件的對象。相同的施工工藝能夠產生不一樣的產品,這更適合於固定工藝,但不必定是固定順序web

類型

建立型spring

使用場景

若是一個對象具備很是複雜的內部結構(許多屬性);apache

但願分離複雜對象的建立和使用編程

優勢

良好的封裝性、製做和使用的分離性; 後端

良好的可擴展性,構造類之間的獨立性,在必定程度上解耦session

缺點

產生多餘的Builder對象;app

產品內部發生變化,建造者都要修改框架

和工廠模式的區別

建造者模式和工廠模式比較相近,但仍是有區別的:

業務場景:建造在線學習網站的視頻教學課程,就好比建造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只作了一個簡單的封裝層,用一個構建器包裝一個構建器層。

相關文章
相關標籤/搜索