結合 Android 淺談 Builder 模式

前言

Builder模式,對象建立型的設計模式。提及設計模式,可能會以爲有點高深莫測,其實否則,咱們天天寫代碼都在多多少少的和各類各樣的設計模式接觸,只是沒有察覺而已。這裏就來講一說Builder模式。java

Android中的Builder模式

在Android開發中,何時會用到Builder模式呢?其實很簡單,就是當你想使用一個控件時或者是一個對象時,沒有辦法直接把他New 出來;那麼這個控件(對象)的實現多半就是用到了Builder模式。android

AlertDialog.Builder

private void InitView() {
        //直接建立對象
        TextView mTextView = new TextView(this);
        Button mButton = new Button(this);

        // 用Builder模式建立Dialog
        AlertDialog.Builder builder=new 
                AlertDialog.Builder(this)
                .setTitle("My Dialog")
                .setMessage("This is Test Dialog")
                .setIcon(R.drawable.application_icon);
        AlertDialog dialog=builder.create();
    }複製代碼

如上面的代碼,咱們能夠按照普通的方式(new)建立TextView對象和Button對象;可是輪到AlertDialog時,卻須要首先建立一個AlertDialog.Builder對象,而後經過這個Builder對象才能建立AlertDialog的一個實例。一樣都是Android的控件,差距爲何這麼大呢?(由於Dialog複雜呀!o(╯□╰)o)。web

下面能夠結合AlertDialog的源碼簡單分析一下。編程

protected AlertDialog(@NonNull Context context) {
        this(context, 0);
    }複製代碼
  • 首先是他的構造方法,能夠看到這個方法是用 protected 修飾的,這意味着除了AlertDialog的子類以外,其餘類是沒法訪問這個方法的。AlertDialog的其餘兩個重載的構造方法也是用到protected關鍵字修飾,有興趣的同窗能夠本身參考源碼;所以,在Activity或者是Fragment裏,咱們是沒法直接建立AlertDialog的實例的,而是須要經過Builder對象。
public static class Builder {
        private final AlertController.AlertParams P;

        public Builder(@NonNull Context context) {
            this(context, resolveDialogTheme(context, 0));
        }
        public Builder(@NonNull Context context, @StyleRes int themeResId) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, themeResId)));
            mTheme = themeResId;
        }
        public Builder setTitle(@Nullable CharSequence title) {
            P.mTitle = title;
            return this;
        }
        public Builder setMessage(@Nullable CharSequence message) {
            P.mMessage = message;
            return this;
        }
        public Builder setIcon(@DrawableRes int iconId) {
            P.mIconId = iconId;
            return this;
        }

    .....
}複製代碼
  • Builder類是AlertDialog內部的一個靜態類。在這個類裏有一個很關鍵的屬性P,能夠關注一下,這個變量是final類型的;除此以外剩下的就是一系列的setxxx 方法,用於設置AlertDialog的不一樣屬性,例如上面列舉的三個方法,能夠分別設置AlertDialog的Title,Message及 Icon 信息。在這些方法中,都是把參數直接傳遞給了以前所說的P這個實例,並且每個方法的返回值都是Builder類自身,這樣就方便開發者鏈式調用每個方法,這樣不只寫起來簡單,並且讀起來也頗有邏輯感
public AlertDialog create() {
            // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param,
            // so we always have to re-set the theme
            final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
            P.apply(dialog.mAlert);
            dialog.setCancelable(P.mCancelable);
            if (P.mCancelable) {
                dialog.setCanceledOnTouchOutside(true);
            }
            dialog.setOnCancelListener(P.mOnCancelListener);
            dialog.setOnDismissListener(P.mOnDismissListener);
            if (P.mOnKeyListener != null) {
                dialog.setOnKeyListener(P.mOnKeyListener);
            }
            return dialog;
        }複製代碼
  • 最後,在Builder類的create方法中完成了AlertDialog的建立;萬變不離其宗,AlertDialog的實例化,仍是經過new建立出來,並經過以前所說的實例P和dialog實例實現了某種關聯(具體如何實現暫不展開討論),總之就是把以前經過Builder方法設置的一系列參數都配置到了最終的AlertDialog之上。

OKHttp中的Request.Builder

對於OKHttp,相信你們都不陌生,在構造Request對象時,就用到了Request.Builder。顧名思義,這裏也用到了Builder模式。設計模式

findViewById(R.id.get).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tv.setText("");
                loading.setVisibility(View.VISIBLE);
                OkHttpClient client = new OkHttpClient();
                Request.Builder builder = new Request.Builder()
                        .url(BASE_URL)
                        .method("GET", null);

                Request request = builder.build();
                Call mCall = client.newCall(request);
                mCall.enqueue(new MyCallback());
            }
        });複製代碼

以上是一個很典型的關於OKHttp的使用方式,這裏Request對象也不是直接建立,而是經過首先建立一個Request.Builder對象,再經過他的build方法建立出最終的request對象。
這裏能夠粗略的看一下Request類的源碼。websocket

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


  private 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 static class Builder {
    private HttpUrl url;
    private String method;
    private Headers.Builder headers;
    private RequestBody body;
    private Object tag;

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


    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 {@code url} is not a valid HTTP or HTTPS URL. Avoid this * exception by calling {@link HttpUrl#parse}; it returns null for invalid URLs. */
    public Builder url(String url) {
      if (url == null) throw new NullPointerException("url == null");

      // Silently replace websocket URLs with HTTP URLs.
      if (url.regionMatches(true, 0, "ws:", 0, 3)) {
        url = "http:" + url.substring(3);
      } else if (url.regionMatches(true, 0, "wss:", 0, 4)) {
        url = "https:" + url.substring(4);
      }

      HttpUrl parsed = HttpUrl.parse(url);
      if (parsed == null) throw new IllegalArgumentException("unexpected url: " + url);
      return url(parsed);
    }

    /** * 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);
    }

    /** * Sets the header named {@code name} to {@code value}. If this request already has any headers * with that name, they are all replaced. */
    public Builder header(String name, String value) {
      headers.set(name, value);
      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 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 Request build() {
      if (url == null) throw new IllegalStateException("url == null");
      return new Request(this);
    }
  }
}複製代碼
  • 首先,Request是final類型的,所以他不會有子類;再有就它惟一的構造方法是private的;所以,對於開發者來講就沒法經過普通的方法(經過new)建立一個Request對象了。網絡

  • Builder類,是一個靜態內部類,經過其構造方法能夠看出,Request 默認的請求方式是GET請求方式,同時當咱們在建立Builder對象時,若是沒有提供url 參數時會拋出異常,這是合理的也是必須的,一個Http請求若是沒有url那麼一切都是空談,這裏拋出異常 十分必要。在method方法中,會根據參數修改具體的請求方法,同時會根據請求方法判斷是否須要RequestBody。app

  • 最後,經過build方法建立了Request,能夠看到這裏調用的就是Request惟一的構造方法,傳遞的參數就是當前Builder實例。這樣建立的Request對象就是根據咱們構造出來的Builder實例所量身定製的Request。便於下一步進行同步或異步的網絡請求。異步

至此,你可能會有疑問,所謂的Builder模式有什麼意義?socket

爲何Android系統中建立一個AlertDialog要這麼複雜,像TextView同樣,直接new出來一個實例而後set各類屬性不也同樣可用嗎?
Request 對象的建立不使用Builder模式同樣也是能夠的呀?上面各類異常處理,方法執行用普通的方式也可實現,Builder模式的價值在哪裏呢?

帶着這些疑問,讓咱們去好好理解一下Builder模式。

Builder 模式

定義

將一個複雜對象的構建與它的表示分離,使得一樣的構建過程能夠建立不一樣的表示

適用場景

1.相同的方法,不一樣的執行順序,產生不一樣的事件結果
2.多個部件或零件,均可以裝配到同一個對象中,可是產生的運行結果又不相同
3.產品類很是複雜,或者產品中的調用順序不一樣產生了不一樣的做用
4.須要初始化一個對象特別複雜的對象,這個對象有不少參數,且有默認值

看這樣的概念也許有些抽象,下面仍是經過代碼來看看。在以前工廠方法模式中,咱們用工廠方法模式列舉了Mobike於Ofo 對象生成的例子。這裏依舊以兩者爲例,看看用Builder模式怎麼寫。

public final class Bicycle {
    public static final int SHARED = 1;
    public static final int PRIVATE = 0;

    @IntDef({SHARED, PRIVATE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface bicycleType {
    }

    protected String color;
    protected String name;
    protected double charge;
    protected int number;
    protected int type;

    protected Bicycle(BicycleBuilder builder) {
        this.color = builder.color;
        this.name = builder.name;
        this.charge = builder.chager;
        this.number = builder.number;
        this.type = builder.type;
    }

    public static class BicycleBuilder {


        private String color;
        private String name;
        private double chager;
        private int number;
        private int type;

        public BicycleBuilder() {
            this.color = "黑色";
            this.name = "永久";
            this.chager = 0;
            this.number = 0;
            this.type = Bicycle.PRIVATE;
        }

        public BicycleBuilder setColor(String color) {
            this.color = color;
            return this;
        }

        public BicycleBuilder setName(String name) {
            this.name = name;
            return this;
        }

        public BicycleBuilder setCharge(double chager) {
            this.chager = chager;
            return this;
        }

        public BicycleBuilder setNumber(int number) {
            this.number = number;
            return this;
        }

        public BicycleBuilder setType(@bicycleType int type) {
            this.type = type;
            return this;
        }

        public Bicycle build(){
            return new Bicycle(this);
        }
    }

    @Override
    public String toString() {
        String typeStr= type == SHARED ? "共享單車": "私人車輛";

        return "Bicycle{" +
                "color='" + color + '\'' +
                ", name='" + name + '\'' +
                ", charge=每分鐘" + charge +"/元"+
                ", number=" + number +
                ", type=" + typeStr +
                '}';
    }
}複製代碼

在這裏Bicycle類包含5個特有的屬性,同時將其構造方法設置爲protected。經過BicycleBuilder 類來真正實現建立Bicycle的實例。這裏BicycleBuilder的默認的構造方法,會建立一個普通的黑色永久牌私人自行車,而經過BicycleBuilder提供的幾個方法咱們即可以建立不一樣的Bicycle實例。好比下面這種實現:

public class BuilderPatternActivity extends AppCompatActivity {
    private TextView bike_result;
    private Bicycle mBicycle;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_builder_pattern);
        bike_result = V.f(this, R.id.bike_result);
    }

    /** * 普通自行車 * @param view */
    public void NormalBike(View view) {
        Bicycle.BicycleBuilder builder=new Bicycle.BicycleBuilder();
        mBicycle=builder.build();
        updateView(mBicycle);
    }
    /** * 膜拜單車 * @param view */
    public void Mobike(View view) {
        Bicycle.BicycleBuilder builder=new Bicycle.BicycleBuilder()
                .setColor("橙色")
                .setName("膜拜單車")
                .setCharge(1.0)
                .setNumber(10010)
                .setType(Bicycle.SHARED);
        mBicycle=builder.build();
        updateView(mBicycle);
    }
    /** * OFO 單車 * @param view */
    public void Ofo(View view) {
        Bicycle.BicycleBuilder builder=new Bicycle.BicycleBuilder()
                .setColor("黃色")
                .setName("OFO單車")
                .setCharge(0.5)
                .setNumber(40010)
                .setType(Bicycle.SHARED);
        mBicycle=builder.build();
        updateView(mBicycle);
    }


    private void updateView(Bicycle mBicycle) {
        bike_result.setText("");
        bike_result.setText(mBicycle.toString());
    }
}複製代碼

經過Bicycle.BicycleBuilder 提供的一系列set方法,咱們建立了mobike實例和ofo單車實例。

這就是Builder模式,正如定義和使用場景中提到的那樣,經過Builder模式,在一樣的構建過程下,咱們能夠建立不一樣的結果;一般來講,咱們要建立的對象是很複雜的,有不少參數,這些參數中有些是必須的,好比OKHttp中Request的url參數,有些參數又會有默認值;總之,Builder模式,一種對象建立型的設計模式;爲咱們建立對象提供了一種思路。

最後,再說一個使用了Builder模式的東西-RxJava。說到RxJava咱們很容易想到觀察者模式。不錯,RxJava最核心的思想就是觀察者模式;可是想想咱們使用RxJava的過程。

ArrayList<String> datas = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            datas.add("item_" + i);
        }
        Observable.just(datas)
                .flatMap(new Func1<ArrayList<String>, Observable<String>>() {
                    @Override
                    public Observable<String> call(ArrayList<String> strings) {
                        return Observable.from(strings);
                    }
                })
                .map(new Func1<String, Integer>() {
                    @Override
                    public Integer call(String s) {
                        return s.hashCode();
                    }
                })
                .subscribe(new Action1<Integer>() {
                    @Override
                    public void call(Integer Integer) {
                        Log.e(MainActivity.class.getSimpleName(), "call---->" + Integer);
                    }
                });複製代碼

如上代碼所示,在subscribe 方法執行以前,經過各類各樣的操做符,原始數據一個ArrayList變成了一個Integer類型的數據,也就是說咱們使用操做符的過程,就是一個Builder模式構建的過程,直到生成咱們最終須要的產品爲止。這和Builder模式的定義以及使用場景是徹底符合的。

Builder模式 VS 工廠方法模式

工廠模式通常都是建立一個產品,注重的是把這個產品建立出來就行,只要建立出來,不關心這個產品的組成部分。從代碼上看,工廠模式就是一個方法,用這個方法就能生產出產品。

建造者模式也是建立一個產品,可是不只要把這個產品建立出來,還要關係這個產品的組成細節,組成過程。從代碼上看,建造者模式在建造產品時,這個產品有不少方法,建造者模式會根據這些相同方法可是不一樣執行順序建造出不一樣組成細節的產品。

工廠模式關心總體,建造者模式關心細節

最後

如今回到咱們以前提出的問題,Builder模式的意義是什麼?看完以後你可能已經獲得答案了,沒有任何實質意義,Builder模式的使用並不會使咱們的代碼運行速度加快。設計模式總的來講就是對是封裝、繼承、多態和關聯的反覆使用;是一種編程技巧,讓咱們能寫出高質量代碼的技巧。

最後再說一句,嚴格來講本文討論的Builder模式並非標準意義上的Builder模式,在這裏咱們從Android源碼的角度出發,簡化了Builder模式,爲了方便鏈式調用及習慣,捨棄了本來應有的Director角色。對正統的Builder模式感興趣的同窗能夠再去深刻研究。

相關文章
相關標籤/搜索