設計模式系列之建造者模式(Builder Pattern)——複雜對象的組裝與建立

說明:設計模式系列文章是讀劉偉所著《設計模式的藝術之道(軟件開發人員內功修煉之道)》一書的閱讀筆記。我的感受這本書講的不錯,有興趣推薦讀一讀。詳細內容也能夠看看此書做者的博客https://blog.csdn.net/LoveLion/article/details/17517213java

模式概述

模式定義

沒有人買車會只買一個輪胎或者方向盤,你們買的都是一輛包含輪胎、方向盤和發動機等多個部件的完整汽車。如何將這些部件組裝成一輛完整的汽車並返回給用戶,這是建造者模式須要解決的問題。建造者模式又稱爲生成器模式,它是一種較爲複雜、使用頻率也相對較低的建立型模式。建造者模式爲客戶端返回的不是一個簡單的產品,而是一個由多個部件組成的複雜產品。web

它將客戶端與包含多個組成部分(或部件)的複雜對象的建立過程分離,客戶端無須知道複雜對象的內部組成部分與裝配方式,只須要知道所需建造者的類型便可。它關注如何一步一步建立一個的複雜對象,不一樣的具體建造者定義了不一樣的建立過程,且具體建造者相互獨立,增長新的建造者很是方便,無須修改已有代碼,系統具備較好的擴展性。spring

建造者模式(Builder Pattern):將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示。建造者模式是一種對象建立型模式。編程

建造者模式一步一步建立一個複雜的對象,它容許用戶只經過指定複雜對象的類型和內容就能夠構建它們,用戶不須要知道內部的具體構建細節。設計模式

模式結構圖

建造者模式結構圖以下所示:mvc

建造者模式結構圖

建造者模式結構圖中包含以下幾個角色:app

  • Builder(抽象建造者):它爲建立一個產品Product對象的各個部件指定抽象接口,在該接口中通常聲明兩類方法,一類方法是buildPartX(),它們用於建立複雜對象的各個部件;另外一類方法是getResult(),它們用於返回複雜對象。Builder既能夠是抽象類,也能夠是接口。框架

  • ConcreteBuilder(具體建造者):它實現了Builder接口,實現各個部件的具體構造和裝配方法,定義並明確它所建立的複雜對象,也能夠提供一個方法返回建立好的複雜產品對象。ide

  • Product(產品角色):它是被構建的複雜對象,包含多個組成部件,具體建造者建立該產品的內部表示並定義它的裝配過程。函數

  • Director(指揮者):指揮者又稱爲導演類,它負責安排複雜對象的建造次序,指揮者與抽象建造者之間存在關聯關係,能夠在其construct()建造方法中調用建造者對象的部件構造與裝配方法,完成複雜對象的建造。客戶端通常只須要與指揮者進行交互,在客戶端肯定具體建造者的類型,並實例化具體建造者對象(也能夠經過配置文件和反射機制),而後經過指揮者類的構造函數或者Setter方法將該對象傳入指揮者類中。

在建造者模式的定義中提到了複雜對象,那麼什麼是複雜對象?簡單來講,複雜對象是指那些包含多個非簡單類型的成員屬性,這些成員屬性也稱爲部件或零件,如汽車包括方向盤、發動機、輪胎等部件,電子郵件包括髮件人、收件人、主題、內容、附件等部件。

模式僞代碼

定義產品角色,典型代碼以下:

public class Product {
    // 定義部件,部件能夠是任意類型,包括值類型和引用類型
    private String partA;

    private String partB;

    private String partC;

    // getter、setter方法省略
}

抽象建造者中定義了產品的建立方法和返回方法,其典型代碼以下

public abstract class Builder {
    // 建立產品對象
    protected Product product = new Product();

    public abstract void buildPartA();

    public abstract void buildPartB();

    public abstract void buildPartC();

    // 返回產品對象
    public Product getResult() {
        return product;
    }
}

在抽象類Builder中聲明瞭一系列抽象的buildPartX()方法用於建立複雜產品的各個部件,具體建造過程在ConcreteBuilder中實現,此外還提供了工廠方法getResult(),用於返回一個建造好的完整產品。

ConcreteBuilder中實現了buildPartX()方法,經過調用ProductsetPartX()方法能夠給產品對象的成員屬性設值。不一樣的具體建造者在實現buildPartX()方法時將有所區別。

在建造者模式的結構中還引入了一個指揮者類Director,該類主要有兩個做用:一方面它隔離了客戶與建立過程;另外一方面它控制產品的建立過程,包括某個buildPartX()方法是否被調用以及多個buildPartX()方法調用的前後次序等。指揮者針對抽象建造者編程,客戶端只須要知道具體建造者的類型,便可經過指揮者類調用建造者的相關方法,返回一個完整的產品對象。在實際生活中也存在相似指揮者同樣的角色,如一個客戶去購買電腦,電腦銷售人員至關於指揮者,只要客戶肯定電腦的類型,電腦銷售人員能夠通知電腦組裝人員給客戶組裝一臺電腦。指揮者類的代碼示例以下:

public class Director {

    private Builder builder;

    public Director(Builder builder) {
        this.builder = builder;
    }

    public void setBuilder(Builder builder) {
        this.builder = builer;
    }

    //產品構建與組裝方法
    public Product construct() {

        builder.buildPartA();
        builder.buildPartB();
        builder.buildPartC();

        return builder.getResult();
    }
}

在指揮者類中能夠注入一個抽象建造者類型的對象,其核心在於提供了一個建造方法construct(),在該方法中調用了builder對象的構造部件的方法,最後返回一個產品對象。

對於客戶端而言,只需關心具體的建造者便可,代碼片斷以下所示:

public static void main(String[] args) {
    Builder builder = new ConcreteBuilder();

    Director director = new Director(builder);

    Product product = director.construct();
}

模式簡化

在有些狀況下,爲了簡化系統結構,能夠將Director和抽象建造者Builder進行合併,在Builder中提供逐步構建複雜產品對象的construct()方法。

public abstract class Builder {
    // 建立產品對象
    protected Product product = new Product();

    public abstract void buildPartA();

    public abstract void buildPartB();

    public abstract void buildPartC();

    // 返回產品對象
    public Product construct() {
        buildPartA();
        buildPartB();
        buildPartC();
        return product;
    }
}

模式應用

模式在JDK中的應用

java.util.stream.Stream.Builder

public interface Builder<T> extends Consumer<T> {
    /**
     * Adds an element to the stream being built.
     */
    default Builder<T> add(T t) {
        accept(t);
        return this;
    }

    /**
     * Builds the stream, transitioning this builder to the built state
     */
    Stream<T> build();
}

模式在開源項目中的應用

看下Spring是如何構建org.springframework.web.servlet.mvc.method.RequestMappingInfo

/**
 * Defines a builder for creating a RequestMappingInfo.
 * @since 4.2
 */
public interface Builder {
  /**
   * Set the path patterns.
   */
  Builder paths(String... paths);

  /**
   * Set the request method conditions.
   */
  Builder methods(RequestMethod... methods);

  /**
   * Set the request param conditions.
   */
  Builder params(String... params);

  /**
   * Set the header conditions.
   * <p>By default this is not set.
   */
  Builder headers(String... headers);

  /**
   * Build the RequestMappingInfo.
   */
  RequestMappingInfo build();
}

Builder接口的默認實現,以下:

private static class DefaultBuilder implements Builder {

  private String[] paths = new String[0];

  private RequestMethod[] methods = new RequestMethod[0];

  private String[] params = new String[0];

  private String[] headers = new String[0];

  public DefaultBuilder(String... paths) {
    this.paths = paths;
  }

  @Override
  public Builder paths(String... paths) {
    this.paths = paths;
    return this;
  }

  @Override
  public DefaultBuilder methods(RequestMethod... methods) {
    this.methods = methods;
    return this;
  }

  @Override
  public DefaultBuilder params(String... params) {
    this.params = params;
    return this;
  }

  @Override
  public DefaultBuilder headers(String... headers) {
    this.headers = headers;
    return this;
  }
  
  @Override
  public RequestMappingInfo build() {
    ContentNegotiationManager manager = this.options.getContentNegotiationManager();

    PatternsRequestCondition patternsCondition = new PatternsRequestCondition(
        this.paths, this.options.getUrlPathHelper(), this.options.getPathMatcher(),
        this.options.useSuffixPatternMatch(), this.options.useTrailingSlashMatch(),
        this.options.getFileExtensions());

    return new RequestMappingInfo(this.mappingName, patternsCondition,
        new RequestMethodsRequestCondition(this.methods),
        new ParamsRequestCondition(this.params),
        new HeadersRequestCondition(this.headers),
        new ConsumesRequestCondition(this.consumes, this.headers),
        new ProducesRequestCondition(this.produces, this.headers, manager),
        this.customCondition);
  }
}

Spring框架中許多構建類的實例化使用了相似上面方式,總結有如下特色:

  1. Builder大可能是構建類的內部類,構建類提供了一個靜態建立Builder的方法
  2. Builder返回構建類的實例,大多經過build()方法
  3. 構建過程有大量參數,除了幾個必要參數,用戶可根據本身所需選擇設置其餘參數實例化對象

模式總結

建造者模式的核心在於如何一步步構建一個包含多個組成部件的完整對象,使用相同的構建過程構建不一樣的產品,在軟件開發中,若是咱們須要建立複雜對象並但願系統具有很好的靈活性和可擴展性能夠考慮使用建造者模式。

建造者模式抽象工廠模式有點類似,可是建造者模式返回一個完整的複雜產品,而抽象工廠模式返回一系列相關的產品;在抽象工廠模式中,客戶端經過選擇具體工廠來生成所需對象,而在建造者模式中,客戶端經過指定具體建造者類型並指導Director類如何去生成對象,側重於一步步構造一個複雜對象,而後將結果返回。若是將抽象工廠模式當作一個汽車配件生產廠,生成不一樣類型的汽車配件,那麼建造者模式就是一個汽車組裝廠,經過對配件進行組裝返回一輛完整的汽車。

主要優勢

  1. 將產品自己與產品的建立過程解耦,使得相同的建立過程能夠建立不一樣的產品對象
  2. 能夠更加精細地控制產品的建立過程。將複雜產品的建立步驟分解在不一樣的方法中,使得建立過程更加清晰,也更方便使用程序來控制建立過程。

適用場景

(1) 須要生成的產品對象有複雜的內部結構,這些產品對象一般包含多個成員屬性。

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

(3) 隔離複雜對象的建立和使用,並使得相同的建立過程能夠建立不一樣的產品。

相關文章
相關標籤/搜索