一、概述
建造者模式的定義:將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。
工廠類模式提供的是創建單個類的模式,而建造者模式則是將各種產品集中起來進行管理,用來創建復合對象,所謂復合對象就是指某個類具有不同的屬性,其實建造者模式就是前面抽象工廠模式和最后的Test結合起來得到的。
所以,如果我們在寫代碼時,某個復雜的類有多種初始化形式或者初始化過程及其繁瑣,並且還對應多個復雜的子類(總之就是構造起來很麻煩),我們就可以用建造者模式,將該類和該類的構造過程解耦!
1.1、適用場景
- 如果一個對象有非常復雜的內部結構,在用戶不知道對象的建造過程和細節的情況下就可以直接創建復雜的對象。
- 想把復雜對象的創建和使用分離。
1.2、優缺點
優點
- 封裝性好,創建和使用分離
- 擴展性好、建造類之間獨立、一定程度上解耦
缺點
- 產生多余的builder對象
- 產品內部發送變化,建造者都要修改,成本比較大
1.3、類圖角色及其職責
- Director:指揮者/導演類,負責安排已有模塊的順序,然后告訴Builder開始建造。
- Builder:抽象建造者,規范產品的組建,一般由子類實現。
- ConcreteBuilder:具體建造者,實現抽象類定義的所有方法,並且返回一個組建好的對象。
- Product:產品類,通常實現了模板方法模式。
1.4、演進
初始寫法
Person person =new Person(); person.setName("lhx"); person.setAge(1);
鏈式setter寫法【需要修改類的setter方法】
Person person =new Person().setName("lhx").setAge(1);
根據setter鏈式調用的思路,演進出另一套解決方案Builder模式
Builder模式
創建一個人對象,屬性有name,number,class,sex,age,school等屬性,如果每一個屬性都可以為空,也就是說我們可以只用一個name,也可以用一個school,name,或者一個class,number,或者其他任意的賦值來創建一個學生對象,這時該怎么構造?
難道我們寫6個1個輸入的構造函數,15個2個輸入的構造函數.......嗎?這個時候就需要用到Builder模式了。示例:
public class Person { private String name; private Integer number; private String sex; private Integer age; private String school; public Person(Builder builder) { this.age = builder.age; this.name = builder.name; this.number = builder.number; this.school = builder.school; this.sex = builder.sex; } //構建器,利用構建器作為參數來構建Student對象 public static class Builder { private String name; private Integer number; private String sex; private Integer age; private String school; public Builder setName(String name) { this.name = name; return this; } public Builder setNumber(int number) { this.number = number; return this; } public Builder setSex(String sex) { this.sex = sex; return this; } public Builder setAge(int age) { this.age = age; return this; } public Builder setSchool(String school) { this.school = school; return this; } public Person build() { return new Person(this); } } }
測試
@Test public void testBuilder() { Person student = new Person.Builder().setName("aaa").setAge(11).build(); }
二、擴展
2.1、JDK1.7源碼中的建造者模式
2.1.1、StringBuilder
中一部分源碼,典型的建造者模式
public StringBuilder append(boolean b) { super.append(b); return this; } public StringBuilder append(char c) { super.append(c); return this; } public StringBuilder append(int i) { super.append(i); return this; } public StringBuilder append(long lng) { super.append(lng); return this; } public StringBuilder append(float f) { super.append(f); return this; } public StringBuilder append(double d) { super.append(d); return this; }
2.1.2、StringBuffer
StringBuffer中一部分源碼,比StringBuilder多一個synchronized,典型的建造者模式
public synchronized StringBuffer append(boolean b) { super.append(b); return this; } public synchronized StringBuffer append(char c) { super.append(c); return this; } public synchronized StringBuffer append(int i) { super.append(i); return this; }
2.2、spring中的建造者模式
2.2.1、UriComponents和UriComponentsBuilder
UriComponents基本方法
public abstract class UriComponents implements Serializable { private static final String DEFAULT_ENCODING = "UTF-8"; // 用於分割uri的正則表達式,下面會說到 private static final Pattern NAMES_PATTERN = Pattern.compile("\\{([^/]+?)\\}"); private final String scheme; private final String fragment; protected UriComponents(String scheme, String fragment) { this.scheme = scheme; this.fragment = fragment; } // 多個Components對應的getter方法 /** * 返回URL的scheme. */ public final String getScheme() { return this.scheme; } /** * 返回URL的fragment. */ public final String getFragment() { return this.fragment; } /** * 返回URL的schemeSpecificPar */ public abstract String getSchemeSpecificPart(); /** * 返回userInfo */ public abstract String getUserInfo(); /** * 返回URL的host */ public abstract String getHost(); /** * 返回URL的port */ public abstract int getPort(); /** * 返回URL的path */ public abstract String getPath(); /** * 返回URL的path部分的集合 */ public abstract List<String> getPathSegments(); /** * 返回URL的query部分 */ public abstract String getQuery(); /** * 返回URL的query參數map */ public abstract MultiValueMap<String, String> getQueryParams(); /** * 將URL的components用特定的編碼規則編碼並返回,默認為utf-8 */ public final UriComponents encode() { try { return encode(DEFAULT_ENCODING); } catch (UnsupportedEncodingException ex) { // should not occur throw new IllegalStateException(ex); } } /** * 編碼的抽象方法,傳入相應的編碼規則 */ public abstract UriComponents encode(String encoding) throws UnsupportedEncodingException; /** * 將URL中的模板參數換成對應的值 */ public final UriComponents expand(Map<String, ?> uriVariables) { Assert.notNull(uriVariables, "'uriVariables' must not be null"); return expandInternal(new MapTemplateVariables(uriVariables)); } /** * 將URL中的模板參數換成對應的值,輸入為數組 */ public final UriComponents expand(Object... uriVariableValues) { Assert.notNull(uriVariableValues, "'uriVariableValues' must not be null"); return expandInternal(new VarArgsTemplateVariables(uriVariableValues)); } /** * 將URL中的模板參數換成對應的值,輸入為UriTemplateVariables */ public final UriComponents expand(UriTemplateVariables uriVariables) { Assert.notNull(uriVariables, "'uriVariables' must not be null"); return expandInternal(uriVariables); } /** * 將URL中的模板參數換成對應的值的最終的實現方法 */ abstract UriComponents expandInternal(UriTemplateVariables uriVariables); /** * 處理URL */ public abstract UriComponents normalize(); /** * 返回URL的string */ public abstract String toUriString(); /** * 返回URI格式的方法 */ public abstract URI toUri(); @Override public final String toString() { return toUriString(); } /** * 將這些Components的值賦給其builder類 */ protected abstract void copyToUriComponentsBuilder(UriComponentsBuilder builder); //…… }
UriComponentsBuilder類
構造函數:
/** * 默認構造方法,其中path的構造類為CompositePathComponentBuilder,它為UriComponentsBuilder的內部靜態類,主要實現對url的path部分進行構造。 */ protected UriComponentsBuilder() { this.pathBuilder = new CompositePathComponentBuilder(); } /** * 創建一個傳入UriComponentsBuilder類的深拷貝對象 */ protected UriComponentsBuilder(UriComponentsBuilder other) { this.scheme = other.scheme; this.ssp = other.ssp; this.userInfo = other.userInfo; this.host = other.host; this.port = other.port; this.pathBuilder = other.pathBuilder.cloneBuilder(); this.queryParams.putAll(other.queryParams); this.fragment = other.fragment; }
由於url的path部分是比較復雜的,這邊springMVC用了內部類的方式,為path單獨加了兩個builder類,分別是CompositePathComponentBuilder、FullPathComponentBuilder。
它是如何將給定的uri生成為相應的UriComponents的。這里就從比較容易理解的fromUriString方法入手:
// 靜態方法,從uri的字符串中獲得uri的各種要素 public static UriComponentsBuilder fromUriString(String uri) { Assert.notNull(uri, "URI must not be null"); // 利用正則表達式,獲得uri的各個組成部分 Matcher matcher = URI_PATTERN.matcher(uri); if (matcher.matches()) { UriComponentsBuilder builder = new UriComponentsBuilder(); // 獲得對應要素的字符串 String scheme = matcher.group(2); String userInfo = matcher.group(5); String host = matcher.group(6); String port = matcher.group(8); String path = matcher.group(9); String query = matcher.group(11); String fragment = matcher.group(13); // uri是否透明的標志位 boolean opaque = false; // uri存在scheme且后面不跟:/則為不透明uri 例如mailto:java-net@java.sun.com if (StringUtils.hasLength(scheme)) { String rest = uri.substring(scheme.length()); if (!rest.startsWith(":/")) { opaque = true; } } builder.scheme(scheme); // 如果為不透明uri,則具備ssp,需要設置ssp if (opaque) { String ssp = uri.substring(scheme.length()).substring(1); if (StringUtils.hasLength(fragment)) { ssp = ssp.substring(0, ssp.length() - (fragment.length() + 1)); } builder.schemeSpecificPart(ssp); } // 如果為絕對uri(通常意義上的uri),則設置各個component else { builder.userInfo(userInfo); builder.host(host); if (StringUtils.hasLength(port)) { builder.port(port); } builder.path(path); builder.query(query); } if (StringUtils.hasText(fragment)) { builder.fragment(fragment); } return builder; } // 傳入uri格式不對,拋出異常 else { throw new IllegalArgumentException("[" + uri + "] is not a valid URI"); } }
從上面的方法中,我們可以看到,UriComponentsBuilder從一個uri的字符串中,通過正則匹配的方式,獲取到不同Components的信息並賦值。UriComponentsBuilder除了fromUriString這一種構建方法外,還提供fromUri,fromHttpUrl,fromHttpRequest,fromOriginHeader等好幾種構建的方法。
在通過各種構建后,獲取到了對應的Components信息,最后的一步,也是最重要的一步,build,將會返回我們需要的UriComponents類。UriComponentsBuilder提供了兩類build方法,我們主要看默認的build方法:
/** * 默認的build方法 */ public UriComponents build() { return build(false); } /** * 具體的build實現方法,它通過ssp是否為空,判斷構造的uri屬於相對uri還是絕對uri,生成OpaqueUriComponents類(相對)或HierarchicalUriComponents類(絕對),它們都為UriComponents的子類 */ public UriComponents build(boolean encoded) { if (this.ssp != null) { return new OpaqueUriComponents(this.scheme, this.ssp, this.fragment); } else { // 調用pathBuilder的build方法,構造對應的path return new HierarchicalUriComponents(this.scheme, this.userInfo, this.host, this.port, this.pathBuilder.build(), this.queryParams, this.fragment, encoded, true); } }
可以看到,UriComponentsBuilder的build方法很簡單,就是返回相應的UriComponents類。其中,在構造HierarchicalUriComponents時,還調用了pathBuilder的build方法生成uri對應的path
小結
從springMVC通過UriComponentsBuilder構建UriComponents類的整個源碼與流程中,我們可以窺見建造者模式在其中發揮的巨大作用。
它通過builder類,提供了多種UriComponents的初始化方式,並能根據不同情況,返回不同的UriComponents子類。充分的將UriComponents類本身與它的構造過程解耦合。
試想一下,如果不使用建造者模式,而是將大量的初始化方法直接塞到UriComponents類或其子類中,它的代碼將變得非常龐大和冗余。而建造者模式可以幫助我們很好的解決這一問題。
2.2.2、BeanDefinitionBuilder
一系列的方法
public BeanDefinitionBuilder setParentName(String parentName) { this.beanDefinition.setParentName(parentName); return this; } public BeanDefinitionBuilder setFactoryMethod(String factoryMethod) { this.beanDefinition.setFactoryMethodName(factoryMethod); return this; } public BeanDefinitionBuilder setFactoryMethodOnBean(String factoryMethod, String factoryBean) { this.beanDefinition.setFactoryMethodName(factoryMethod); this.beanDefinition.setFactoryBeanName(factoryBean); return this; }
示例、Spring中實現動態注冊bean
@Test public void testSpringBuilder() { ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext-budiler.xml"); PersonSpringBeanService personSpringBeanService = (PersonSpringBeanService) context.getBean("personSpringBeanService"); if (personSpringBeanService != null) { personSpringBeanService.test(); } else { System.out.println("沒有personSpringBeanService bean"); } //將applicationContext轉換為ConfigurableApplicationContext ConfigurableApplicationContext configurableApplicationContext = (ConfigurableApplicationContext) context; // 獲取bean工廠並轉換為DefaultListableBeanFactory DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableApplicationContext.getBeanFactory(); // 通過BeanDefinitionBuilder創建bean定義 BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(PersonSpringBeanService.class); // 設置屬性personSpringBeanDao,此屬性引用已經定義的bean:personSpringBeanDao // beanDefinitionBuilder.addPropertyReference("personSpringBeanDao", "personSpringBeanDao"); // 注冊bean,第一個參數為BeanName defaultListableBeanFactory.registerBeanDefinition("personSpringBeanService", beanDefinitionBuilder.getRawBeanDefinition()); PersonSpringBeanService personSpringBeanService1 = (PersonSpringBeanService) context.getBean("personSpringBeanService"); personSpringBeanService1.test(); }
2.3、其他
2.3.1、mybatis中的SqlSessionFactoryBuilder