建造者模式源碼分析

建造者模式,對於後端開發人員來說應該是很熟悉的,我們比較常用的HttpClient框架在構建Client時就用到了建造者模式。

定義 
慣例先來看看建造者模式的定義:將一個複雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。

UriComponents 
可以說建造者模式理解起來是比較的容易的。它就是將複雜類的構建與其本身解耦合,並在其構造類中完成對它不同形式的創建。 
在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進行處理的。

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 methods

    /**
     * 默認的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類或其子類中,它的代碼將變得非常龐大和冗餘。而建造者模式可以幫助我們很好的解決這一問題。 
所以,如果我們在寫代碼時,某個複雜的類有多種初始化形式或者初始化過程及其繁瑣,並且還對應多個複雜的子類(總之就是構造起來很麻煩),我們就可以用建造者模式,將該類和該類的構造過程解耦哦!

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