JAVA設計模式------構建者模式

前言:

在閱讀android框架源碼的時候,經常可以見到Builder(構建者模式),鑑於這些框架都在大面積的使用這個涉及模式,所以有必要來探究一下這個設計模式的奧祕。(Okhttp的Request,OkHttpClient,Glide,AlertDialog等都使用這一涉及模式)

定義:

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

UML:

這裏寫圖片描述
在這個示意性的系統裏,最終產品Product只有兩個零件,即part1和part2。相應的建造方法也有兩個:buildPart1()和buildPart2()、同時可以看出本模式涉及到四個角色,它們分別是:

  • 抽象建造者(Builder)角色:給 出一個抽象接口,以規範產品對象的各個組成成分的建造。一般而言,此接口獨立於應用程序的商業邏輯。模式中直接創建產品對象的是具體建造者 (ConcreteBuilder)角色。具體建造者類必須實現這個接口所要求的兩種方法:一種是建造方法(buildPart1和 buildPart2),另一種是返還結構方法(retrieveResult)。一般來說,產品所包含的零件數目與建造方法的數目相符。換言之,有多少 零件,就有多少相應的建造方法。

  • 具體建造者(ConcreteBuilder)角色:擔任這個角色的是與應用程序緊密相關的一些類,它們在應用程序調用下創建產品的實例。這個角色要完成的任務包括:1.實現抽象建造者Builder所聲明的接口,給出一步一步地完成創建產品實例的操作。2.在建造過程完成後,提供產品的實例。

  • 導演者(Director)角色:擔任這個角色的類調用具體建造者角色以創建產品對象。應當指出的是,導演者角色並沒有產品類的具體知識,真正擁有產品類的具體知識的是具體建造者角色。不一定有共同的接口,而完全可以是不相關聯的。導演者角色是與客戶端打交道的角色。導演者將客戶端創建產品的請求劃分爲對各個零件的建造請求,再將這些請求委派給具體建造者角色。具體建造者角色是做具體建造工作的,但是卻不爲客戶端所知。

      一般來說,每有一個產品類,就有一個相應的具體建造者類。這些產品應當有一樣數目的零件,而每有一個零件就相應地在所有的建造者角色裏有一個建造方法。

源代碼

 產品類Product
 

public class Product {
    /**
     * 定義一些關於產品的操作
     */
    private String part1;
    private String part2;
    public String getPart1() {
        return part1;
    }
    public void setPart1(String part1) {
        this.part1 = part1;
    }
    public String getPart2() {
        return part2;
    }
    public void setPart2(String part2) {
        this.part2 = part2;
    }
}

 抽象建造者類Builder
 

public interface Builder {
    public void buildPart1();
    public void buildPart2();
    public Product retrieveResult();
}

具體建造者類ConcreteBuilder

public class ConcreteBuilder implements Builder {

    private Product product = new Product();
    /**
     * 產品零件建造方法1
     */
    @Override
    public void buildPart1() {
        //構建產品的第一個零件
     product.setPart1("編號:9527");
    }
    /**
     * 產品零件建造方法2
     */
    @Override
    public void buildPart2() {
        //構建產品的第二個零件
     product.setPart2("名稱:XXX");
    }
    /**
     * 產品返還方法
     */
    @Override
    public Product retrieveResult() {
        return product;
    }

}

導演者類Director

public class Director {
    /**
     * 持有當前需要使用的建造器對象
     */
    private Builder builder;
    /**
     * 構造方法,傳入建造器對象
     * @param builder 建造器對象
     */
    public Director(Builder builder){
        this.builder = builder;
    }
    /**
     * 產品構造方法,負責調用各個零件建造方法
     */
    public void construct(){
        builder.buildPart1();
        builder.buildPart2();
    }
}

客戶端類Client

public class Client {
    public static void main(String[]args){
        Builder builder = new ConcreteBuilder();
        Director director = new Director(builder);
        director.construct();
        Product product = builder.retrieveResult();
        System.out.println(product.getPart1());
        System.out.println(product.getPart2());
    }
}

建造模式分成兩個很重要的部分:

  1. 一個部分是Builder接口,這裏是定義瞭如何構建各個部件,也就是知道每個部件功能如何實現,以及如何裝配這些部件到產品中去;

  2. 另外一個部分是Director,Director是知道如何組合來構建產品,也就是說Director負責整體的構建算法,而且通常是分步驟地來執行。

  不管如何變化,建造模式都存在這麼兩個部分,一個部分是部件構造和產品裝配,另一個部分是整體構建的算法。認識這點是很重要的,因爲在建造模式中,強調的是固定整體構建的算法,而靈活擴展和切換部件的具體構造和產品裝配的方式。

  再直白點說,建造模式的重心在於分離構建算法和具體的構造實現,從而使得構建算法可以重用。具體的構造實現可以很方便地擴展和切換,從而可以靈活地組合來構造出不同的產品對象

使用建造模式構建複雜對象

  考慮這樣一個實際應用,要創建一個保險合同的對象,裏面很多屬性的值都有約束,要求創建出來的對象是滿足這些約束規則的。約束規則比如:保險合同通常情況下可以和個人簽訂,也可以和某個公司簽訂,但是一份保險合同不能同時與個人和公司簽訂。這個對象裏有很多類似這樣的約束,採用建造模式來構建複雜的對象,通常會對建造模式進行一定的簡化,因爲目標明確,就是創建某個複雜對象,因此做適當簡化會使程序更簡潔。大致簡化如下:

  ●由於是用Builder模式來創建某個對象,因此就沒有必要再定義一個Builder接口,直接提供一個具體的建造者類就可以了。

  ●對於創建一個複雜的對象,可能會有很多種不同的選擇和步驟,乾脆去掉“導演者”,把導演者的功能和Client的功能合併起來,也就是說,Client這個時候就相當於導演者,它來指導構建器類去構建需要的複雜對象。
  我們來看okhttp中Request的源碼:(去掉了一些代碼)
  

public final class Request {
  final HttpUrl url;
  final String method;
  final Headers headers;
  final RequestBody body;
  final Object tag;

  private volatile CacheControl cacheControl; // Lazily initialized.

  Request(Builder builder) {
    this.url = builder.url;
    this.method = builder.method;
    this.headers = builder.headers.build();
    this.body = builder.body;
    this.tag = builder.tag != null ? builder.tag : this;
  }

  public HttpUrl url() {
    return url;
  }

  public String method() {
    return method;
  }

  public Headers headers() {
    return headers;
  }

  public String header(String name) {
    return headers.get(name);
  }

  public List<String> headers(String name) {
    return headers.values(name);
  }

  public RequestBody body() {
    return body;
  }

  public Object tag() {
    return tag;
  }

  public Builder newBuilder() {
    return new Builder(this);
  }


  public static class Builder {
    HttpUrl url;
    String method;
    Headers.Builder headers;
    RequestBody body;
    Object tag;

    public Builder() {
      this.method = "GET";
      this.headers = new Headers.Builder();
    }

    Builder(Request request) {
      this.url = request.url;
      this.method = request.method;
      this.body = request.body;
      this.tag = request.tag;
      this.headers = request.headers.newBuilder();
    }

    public Builder url(HttpUrl url) {
      if (url == null) throw new NullPointerException("url == null");
      this.url = url;
      return this;
    }



    /**
     * Sets the URL target of this request.
     *
     * @throws IllegalArgumentException if the scheme of {@code url} is not {@code http} or {@code
     * https}.
     */
    public Builder url(URL url) {
      if (url == null) throw new NullPointerException("url == null");
      HttpUrl parsed = HttpUrl.get(url);
      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
      return url(parsed);
    }


    public Builder header(String name, String value) {
      headers.set(name, value);
      return this;
    }

    public Builder addHeader(String name, String value) {
      headers.add(name, value);
      return this;
    }

    public Builder removeHeader(String name) {
      headers.removeAll(name);
      return this;
    }

    /** Removes all headers on this builder and adds {@code headers}. */
    public Builder headers(Headers headers) {
      this.headers = headers.newBuilder();
      return this;
    }

    public Builder get() {
      return method("GET", null);
    }

    public Builder head() {
      return method("HEAD", null);
    }

    public Builder post(RequestBody body) {
      return method("POST", body);
    }

    public Builder delete(RequestBody body) {
      return method("DELETE", body);
    }

    public Builder delete() {
      return delete(Util.EMPTY_REQUEST);
    }

    public Builder put(RequestBody body) {
      return method("PUT", body);
    }

    public Builder patch(RequestBody body) {
      return method("PATCH", body);
    }

    public Builder method(String method, RequestBody body) {
      if (method == null) throw new NullPointerException("method == null");
      if (method.length() == 0) throw new IllegalArgumentException("method.length() == 0");
      if (body != null && !HttpMethod.permitsRequestBody(method)) {
        throw new IllegalArgumentException("method " + method + " must not have a request body.");
      }
      if (body == null && HttpMethod.requiresRequestBody(method)) {
        throw new IllegalArgumentException("method " + method + " must have a request body.");
      }
      this.method = method;
      this.body = body;
      return this;
    }

    public Builder tag(Object tag) {
      this.tag = tag;
      return this;
    }

    public Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }
  }
}

在本例中將具體建造者合併到了產品對象中,並將產品對象的構造函數私有化,防止客戶端不使用構建器來構建產品對象,而是直接去使用new來構建產品對象所導致的問題。另外,這個構建器的功能就是爲了創建被構建的對象,完全可以不用單獨一個類。
參考自《JAVA與模式》之建造模式

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