【源碼分析設計模式 10】SpringMVC中的建造者模式

一、基本介紹

建造者模式就是將一個個簡單的對象一步一步構建成一個複雜的對象。

我們生活中有很多可以用建造者模式來解釋。譬如在生產汽車的流水線作業中,我們需要先將生產汽車所需的一個一個的內部構建建造出來,例如發動機,車門,車輪,方向盤,水箱等。對於我們用戶來說,我們並不需要知道這個汽車是怎麼建造出來的,各個部件是怎麼組裝的,銷售人員也不需要知道這個汽車是怎麼組裝建造的,我們只需要知道這是一輛汽車,我們可以銷售和使用就行了,對於銷售人員只需要知道客戶需要什麼樣的汽車,他告訴建造者模式中的指揮者去生產對應的車就可以了,哈哈哈,這都是工業4.0了;同樣,我們看一個簡單的例子,在一家快餐店中,服務員也不需要知道廚師是怎麼炒菜和做飯的,她只需要告訴廚師客戶需要什麼樣的套餐類型就可以了。像這樣,建造者返回給客戶一個完整的的產品對象,而客戶端無須關心該對象所包含的額屬性和組建方式,這就是建造者模式的設計動機。

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

二、建造者模式的結構

1、抽象建造者(Builder)

它聲明爲創建一個產品對象的各個部件指定的抽象接口,在該接口中一般聲明兩類方法,一類方法是buildPatX(),他們用於創建複雜對象的各個部件;另一類方法是getResult(),它們用於返回複雜對象。Builder既可以是抽象類,也可以是接口。

2、具體建造者(ConcreteBuilder)

實現抽象建造者接口,構建和裝配各個部件,定義並明確它所創建的複雜對象,也可以提供一個方法返回創建好的複雜產品對象。

3、指揮者(Director)

它負責安排複雜對象的建造次序,指揮者與抽象建造者之間存在關聯關係,可以在其construct()構造方法中調用建造者對象的部件構造與裝配方法,完成複雜對象的建造。客戶端一般只需要與指揮者進行交互,在客戶端確定具體建造者的類型,並實例化具體建造者對象(也可以通過配置文件和反射機制),然後通過指揮者類的構造函數或者setter方法將該對象傳入指揮者類中。它主要是用於創建一個複雜對象,它主要有兩個作用①隔離了客戶與對象的生成過程②負責控制產品對象的生產過程。

4、產品(Product)

產品角色,一個具體的產品對象。

三、建造者模式的優缺點

1、優點

(1)在建造者模式中,客戶端不必知道產品內部組成的細節,將產品本身與產品的創建過程解耦,使得相同的創建過程可以創建不同的產品對象。

(2)每一個具體建造者都相對獨立,而與其它的具體建造者無關,因此可以很方便地替換具體建造者或增加新的具體建造者,用戶使用不同的具體建造者即可得到不同的產品對象。由於指揮者類針對抽象建造者編程,增加新的具體建造者無須修改原有類庫的代碼,系統擴展方便,符合“開閉原則”。

(3)可以更加精細的控制產品的創建過程,將複雜產品的創建步驟分解在不同的方法中,使得創建過程更加清晰,也更方便使用程序來控制創建過程。

2、缺點

(1)建造者模式所創建的產品一般具有較多的共同點,其組成部分相似,如果產品之間的差異性很大,例如很多組成部分都不相同,不適合使用建造者模式,因此其使用範圍受到一定的限制。

(2)如果產品的內部變化複雜,可能會導致需要定義很多具體建造者類來實現這種變化,導致系統變得很龐大,增加系統的理解難度和運行成本。

四、建造者模式的使用場景

1、需要生成的產品對象有複雜的內部結構,這些產品對象通常包含多個成員屬性。

2、需要生成的產品對象的屬性相互依賴,需要指定其生成順序。

3、對象的創建過程獨立於創建該對象的類。

在建造者模式中通過引入指揮者類,將創建過程封裝在指揮者類中,而不在建造者類和客戶類中。

4、隔離複雜對象的創建和使用,並使得相同的創建過程可以創建不同的產品。

五、代碼實例

1、產品類House 

package designMode.advance.builder;

public class House {
    private String baise;
    private String wall;
    private String roofed;
    public String getBaise() {
        return baise;
    }
    public void setBaise(String baise) {
        this.baise = baise;
    }
    public String getWall() {
        return wall;
    }
    public void setWall(String wall) {
        this.wall = wall;
    }
    public String getRoofed() {
        return roofed;
    }
    public void setRoofed(String roofed) {
        this.roofed = roofed;
    }
}

2、抽象建造者HouseBuilder 

package designMode.advance.builder;

public abstract class HouseBuilder {
    protected House house = new House();
    //將建造的流程寫好, 抽象的方法
    public abstract void buildBasic();
    public abstract void buildWalls();
    public abstract void roofed();
    //建造房子好, 將產品(房子) 返回
    public House buildHouse() {
        return house;
    }
}

3、具體建造者

(1)CommonHouse 

package designMode.advance.builder;

public class CommonHouse extends HouseBuilder {
    @Override
    public void buildBasic() {
        System.out.println(" 普通房子打地基5米 ");
    }

    @Override
    public void buildWalls() {
        System.out.println(" 普通房子砌牆10cm ");
    }

    @Override
    public void roofed() {
        System.out.println(" 普通房子屋頂 ");
    }
}

(2)HighBuilding 

package designMode.advance.builder;

public class HighBuilding extends HouseBuilder {
    @Override
    public void buildBasic() {
        System.out.println(" 高樓的打地基100米 ");
    }

    @Override
    public void buildWalls() {
        System.out.println(" 高樓的砌牆20cm ");
    }

    @Override
    public void roofed() {
        System.out.println(" 高樓的透明屋頂 ");
    }
}

4、指揮者HouseDirector 

package designMode.advance.builder;

public class HouseDirector {
    HouseBuilder houseBuilder = null;

    //構造器傳入 houseBuilder
    public HouseDirector(HouseBuilder houseBuilder) {
        this.houseBuilder = houseBuilder;
    }

    //通過setter 傳入 houseBuilder
    public void setHouseBuilder(HouseBuilder houseBuilder) {
        this.houseBuilder = houseBuilder;
    }

    //如何處理建造房子的流程,交給指揮者
    public House constructHouse() {
        houseBuilder.buildBasic();
        houseBuilder.buildWalls();
        houseBuilder.roofed();
        return houseBuilder.buildHouse();
    }
}

5、測試類

package designMode.advance.builder;

public class Client {
    public static void main(String[] args) {
        //蓋普通房子
        CommonHouse commonHouse = new CommonHouse();
        //準備創建房子的指揮者
        HouseDirector houseDirector = new HouseDirector(commonHouse);
        //完成蓋房子,返回產品(普通房子)
        House house = houseDirector.constructHouse();

        System.out.println("--------------------------");
        //蓋高樓
        HighBuilding highBuilding = new HighBuilding();
        //重置建造者
        houseDirector.setHouseBuilder(highBuilding);
        //完成蓋房子,返回產品(高樓)
        houseDirector.constructHouse();
    }
}

6、控制檯輸出

六、建造者模式在SpringMVC中的實現

1、在springMVC中,我們就可以看到建造者模式的身影。

springMVC在構建UriComponents的內容時,就用到了建造者模式,我們先來看看UriComponents這個類是提供了哪些Components

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);

上面的代碼不包括UriComponents類下其餘的靜態輔助方法,單單從此類的包含多種components中,就可以看出UriComponents的複雜程度。這些components大都對應了url的某個部分,能幫助springMVC對請求的url內容進行識別。springMVC就是通過將uri構建成這個類,再對uri進行處理的。

2、UriComponentsBuilder

那麼springMVC究竟是如何讓請求的uri生成相應的UriComponents類呢?就要看看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,這裏就不擴展來說了。看完了UriComponentsBuilder的構造方法,我們來看它是如何將給定的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:[email protected]
        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,這裏不繼續展開了。

3、總結

從springMVC通過UriComponentsBuilder構建UriComponents類的整個源碼與流程中,我們可以窺見建造者模式在其中發揮的巨大作用。
它通過builder類,提供了多種UriComponents的初始化方式,並能根據不同情況,返回不同的UriComponents子類。充分的將UriComponents類本身與它的構造過程解耦合。
試想一下,如果不使用建造者模式,而是將大量的初始化方法直接塞到UriComponents類或其子類中,它的代碼將變得非常龐大和冗餘。而建造者模式可以幫助我們很好的解決這一問題。
所以,如果我們在寫代碼時,某個複雜的類有多種初始化形式或者初始化過程及其繁瑣,並且還對應多個複雜的子類(總之就是構造起來很麻煩),我們就可以用建造者模式,將該類和該類的構造過程解耦哦!

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