前言:
在閱讀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與模式》之建造模式