前言:最近閒來無事的時候想着看看一些日常用的三方庫源碼,沒想到看了以後才知道直接擼源碼好傷身體,通常設計優秀的開源庫都會涉及不少的設計模式,就好比 android 開發使用頻繁的 okHttp 打開源碼一看,納尼?Builder 模式隨處可見,因而乎,這篇文章就來對 Builder 模式進行一個簡單總結,主要針對便於分析 android 相關源碼,以實際應用出發~
在 oop 編碼設計中,咱們有句經典的話叫作 "萬物皆對象".實際開發中,咱們只要能拿到類的實例,即對象。就能夠開始搞事情啦,能夠命令對象去作一些事情,固然啦~每一個對象的能力都是不一樣的,能作的事情也是不一樣。對象中存儲着類的成員屬性(成員變量和成員方法)。咱們命令對象去爲咱們工做,其實就是調用對象特有的屬性。剛剛咱們也說了,每一個對象的能力是不一樣的,對象所能作的事情,在一開始被建立的時候就決定了。下面先來講一下對象的構建方法。android
假設一個場景:咱們用一個class來表示車,車有一些必需的屬性,好比:車身,輪胎,發動機,方向盤等。也有一些可選屬性,假設超過10個,好比:車上的一些裝飾,安全氣囊等等很是多的屬性。git
若是咱們用構造器來構造對象,咱們的作法是 提供第一個包含4個必需屬性的構造器,接下來再按可選屬性依次重載不一樣的構造器,這樣是可行的,可是會有如下一些問題:程序員
public class Car { /** * 必需屬性 */ private String carBody;//車身 private String tyre;//輪胎 private String engine;//發動機 private String aimingCircle;//方向盤 /** * 可選屬性 */ private String decoration;//車內裝飾品 /** * 必需屬性構造器 * * @param carBody * @param tyre * @param engine */ public Car(String carBody, String tyre, String engine) { this.carBody = carBody; this.tyre = tyre; this.engine = engine; } /** * 假如咱們須要再添加車內裝飾品,即在原來構造器基礎上再重載一個構造器 * * @param carBody * @param tyre * @param engine * @param aimingCircle * @param decoration */ public Car(String carBody, String tyre, String engine, String aimingCircle, String decoration) { this.carBody = carBody; this.tyre = tyre; this.engine = engine; this.aimingCircle = aimingCircle; this.decoration = decoration; } }
提供無參的構造函數,暴露一些公共的方法讓用戶本身去設置對象屬性,這種方法較之第一種彷佛加強了靈活度,用戶能夠根據本身的須要隨意去設置屬性。可是這種方法自身存在嚴重的缺點:
由於構造過程被分到了幾個調用中,在構造中 JavaBean 可能處於不一致的狀態。類沒法僅僅經過判斷構造器參數的有效性來保證一致性。還有一個嚴重的弊端是,JavaBeans 模式阻止了把類作成不可變的可能。,這就須要咱們付出額外的操做來保證它的線程安全。github
public class Car { /** * 必需屬性 */ private String carBody;//車身 private String tyre;//輪胎 private String engine;//發動機 private String aimingCircle;//方向盤 /** * 可選屬性 */ private String decoration;//車內裝飾品 public void setCarBody(String carBody) { this.carBody = carBody; } public void setTyre(String tyre) { this.tyre = tyre; } public void setEngine(String engine) { this.engine = engine; } public void setAimingCircle(String aimingCircle) { this.aimingCircle = aimingCircle; } public void setDecoration(String decoration) { this.decoration = decoration; } }
那麼有沒有什麼方法能夠解決以上問題呢?固然有啦~下面咱們的主角上場-----Builder 模式設計模式
咱們用戶通常不會本身來完成 car 組裝這些繁瑣的過程,而是把它交給汽車製造商。由汽車製造商去完成汽車的組裝過程,這裏的 Builder 就是汽車製造商,咱們的 car 的建立都交由他來完成,咱們只管開車就是啦, 先來個代碼實際體驗一下~安全
public final class Car { /** * 必需屬性 */ final String carBody;//車身 final String tyre;//輪胎 final String engine;//發動機 final String aimingCircle;//方向盤 final String safetyBelt;//安全帶 /** * 可選屬性 */ final String decoration;//車內裝飾品 /** * car 的構造器 持有 Builder,將builder製造的組件賦值給 car 完成構建 * @param builder */ public Car(Builder builder) { this.carBody = builder.carBody; this.tyre = builder.tyre; this.engine = builder.engine; this.aimingCircle = builder.aimingCircle; this.decoration = builder.decoration; this.safetyBelt = builder.safetyBelt; } ...省略一些get方法 public static final class Builder { String carBody; String tyre; String engine; String aimingCircle; String decoration; String safetyBelt; public Builder() { this.carBody = "寶馬"; this.tyre = "寶馬"; this.engine = "寶馬"; this.aimingCircle = "寶馬"; this.decoration = "寶馬"; } /** * 實際屬性配置方法 * @param carBody * @return */ public Builder carBody(String carBody) { this.carBody = carBody; return this; } public Builder tyre(String tyre) { this.tyre = tyre; return this; } public Builder safetyBelt(String safetyBelt) { if (safetyBelt == null) throw new NullPointerException("沒系安全帶,你開個毛車啊"); this.safetyBelt = safetyBelt; return this; } public Builder engine(String engine) { this.engine = engine; return this; } public Builder aimingCircle(String aimingCircle) { this.aimingCircle = aimingCircle; return this; } public Builder decoration(String decoration) { this.decoration = decoration; return this; } /** * 最後創造出實體car * @return */ public Car build() { return new Car(this); } } }
如今咱們的類就寫好了,咱們調用的時候執行一下代碼:框架
Car car = new Car.Builder() .build();
打斷點,debug運行看看效果:ide
能夠看到,咱們默認的 car 已經制造出來了,默認的零件都是 "寶馬",滴滴滴~來不及解釋了,快上車。假如咱們不使用默認值,須要本身定製的話,很是簡單。只須要拿到 Builder 對象以後,依次調用指定方法,最後再調用 build 返回 car 便可。下面代碼示例:函數
//配置car的車身爲 奔馳 Car car = new Car.Builder() .carBody("奔馳") .build();
依舊 debug 看看 car 是否認製成功~ oop
咦,神奇的定製 car 定製成功了,話很少說,繼續開車~~
咱們在 Builder 類中的一系列構建方法中還能夠加入一些咱們對配置屬性的限制。例如咱們給 car 添加一個安全帶屬性,在 Buidler 對應方法出添加如下代碼:
public Builder safetyBelt(String safetyBelt) { if (safetyBelt == null) throw new NullPointerException("沒系安全帶,你開個毛車啊"); this.safetyBelt = safetyBelt; return this; }
而後調用的時候:
//配置car的車身爲 奔馳 Car car = new Car.Builder() .carBody("奔馳") .safetyBelt(null) .build();
咱們給配置安全帶屬性加了 null 判斷,一但配置了null 屬性,即會拋出異常。好了 car 構建好了,咱們來開車看看~
依舊 debug 開車走起~
bom~~~不出意外,翻車了。。。
最後有客戶說了,你製造出來的 car 體驗不是很好,想把車再改造改造,但是車已經出廠了還能改造嗎?那這應該怎麼辦呢?不要急,好說好說,咱們只要能再拿到 Builder 對象就有辦法。下面咱們給 Builder 添加以下構造,再對比下 Car 的構造看看有啥奇特之處:
/** * 回廠重造 * @param car */ public Builder(Car car) { this.carBody = car.carBody; this.safetyBelt = car.safetyBelt; this.decoration = car.decoration; this.tyre = car.tyre; this.aimingCircle = car.aimingCircle; this.engine = car.engine; } /** * car 的構造器 持有 Builder,將 builder 製造的組件賦值給 car 完成構建 * * @param builder */ public Car(Builder builder) { this.carBody = builder.carBody; this.tyre = builder.tyre; this.engine = builder.engine; this.aimingCircle = builder.aimingCircle; this.decoration = builder.decoration; this.safetyBelt = builder.safetyBelt; }
咦,彷佛有着對稱的關係,沒錯。咱們提供對應的構造。調用返回對應的對象,能夠實現返回的效果。在 Car 中添加方法
/** * 從新拿回builder 去改造car * @return */ public Builder newBuilder() { return new Builder(this); }
如今來試試能不能返廠重建?把原來的寶馬車重形成奔馳車,調用代碼:
Car newCar = car.newBuilder() .carBody("奔馳") .safetyBelt("奔馳") .tyre("奔馳") .aimingCircle("奔馳") .decoration("奔馳") .engine("奔馳") .build();
行,車改造好了,咱們繼續 debug ,試試改造完滿不滿意
哈哈,已經改造好了,客戶至關滿意~~
下面分析一下具體是怎麼構建的。
至此,咱們的 Builder 模式體驗就結束了,這裏講的只是 Builder 模式的一個變種,即在 android 中應用較爲普遍的模式,下面總結一下優缺點:
優勢:
缺點:
解決方法: 不會偷懶的程序猿不是好程序猿,針對以上缺點,IDEA 系列的 ide ,有相應的插件 InnerBuilder
能夠自動生成 builder 相關代碼,安裝自行 google,使用的時候只須要在實體類中 alt + insert 鍵,會有個 build 按鈕提供代碼生成。
使用場景
通常若是類屬性在4個以上的話,建議使用 此模式。還有若是類屬性存在不肯定性,可能之後還會新增屬性時使用,便於擴展。
開篇咱們也說到了 Builder 模式在 okHttp 中隨處可見。好比在OkHttpClient,Request,Response 等類都使用了此模式。下面以
Request 類爲例簡要說明,具體的能夠去下載源碼查看,按照上面的套路基本沒問題。
Request 有6個屬性,按照套路 構造方法持有一個 Builder ,在構造中將 builder 製造的組件賦值給 Request 完成構建,提供 newBuilder 用於從新得到 Builder 返廠重建:
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 Builder newBuilder() { return new Builder(this); }
Builder 有兩個構造,第一個空構造中初始化兩個默認值。第二個構造持有 Request 用於從新構建 Builder 返廠重建。
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(); }
剩下的就是一些屬性初始化的方法,返回值爲 Builder 方便鏈式調用。這裏就列出一個方法,詳細的請查看源碼,最後調用 build() 方法 初始化 Request 傳入 Builder 完成構建。
public Builder url(HttpUrl url) { if (url == null) throw new NullPointerException("url == null"); this.url = url; return this; } ...此處省略部分方法 public Request build() { if (url == null) throw new IllegalStateException("url == null"); return new Request(this); }
在 AlertDialog 中使用到的 Builder 模式也是這種套路,我相信若是前面理解了,本身去看看源碼應該是手到擒來的事。因爲篇幅緣由,在這裏就不展開了。
結語:我的以爲 對於設計模式的學習是至關有必要的,有時候咱們須要去讀一下經常使用開源框架的源碼,不只能夠從中學習到一些設計思想,還能夠方便平常使用。在一篇博客上面看到這句話 " 咱們不重複造輪子不表示咱們不須要知道輪子該怎麼造及如何更好的造!",而設計模式即是讀懂框架源碼的基石,由於每每優秀的框架都會涉及不少設計模式。後面本人也會不斷更新,不斷學習新的設計模式,進而總結出來~
聲明:以上僅僅是本人的一點拙見,若有不足之處,還望指出
更多原創文章會在公衆號第一時間推送,歡迎掃碼關注 張少林同窗