【設計模式筆記】(二)- Builder模式

weixin_34019929發表於2018-03-15
595349-6dd228cf6cee0592.png

1.簡述

Builder模式也就是建造者模式,先說定義,將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。

首先,將複雜物件的建立過程和部件的表示分離出來,其實就是把建立過程和自身的部件解耦,使得構建過程和部件都可以自由擴充套件,兩者之間的耦合降到最低。然後,再是相同的構建過程可以建立不同的表示,相同的組合也可以通過不同的部件建立出不同的物件。

可能使用場景

  • 相同的方法,不同的執行順序,產生不同的結果時
  • 多個部件(程式碼中就對應類的屬性),都可以裝配到一個物件中,但是產生的執行結果又不相同時
  • 初始化一個物件特別複雜,引數多且很多引數都具有預設值時

這只是舉幾個比較合適的例子而已

2.實現

595349-465cc196e0c64aae.png
  • Product —— 產品的抽象類
  • Builder —— 抽象Builder類,規範產品的元件
  • ConcreteBuilder —— 具體的Builder類,實現具體的組建過程
  • Director —— 統一組裝過程

看到這裡可能回覺得這個模式怎麼和android平時使用的不太一樣啊!Builder一般不是隻有Product和Builder麼?

關於這點,有度娘過一下Builder模式,基本介紹都是這種結構,相對標準,這個標準的意思就是幫助學習該設計模式,並不是指一種用法,畢竟設計模式還是需要靈活使用。而沒有Director該環節的實現方式就是對Builder模式的一種簡化。

這是一個手機配置簡化的抽象類,設定CPU、OS以、記憶體大小以及運存大小

public abstract class Phone {
    protected String mCPU;
    protected String mOS;
    protected int mMemorySize;
    protected int mStorageSize;

    public abstract void setCPU();

    public abstract void setOS();

    public void setMemorySize(int memorySize) {
        this.mMemorySize = memorySize;
    }

    public void setStorageSize(int storageSize) {
        this.mStorageSize = storageSize;
    }

    @Override
    public String toString() {
        return "{ \n CPU : " + mCPU + "\n OS : " + mOS + 
            " \n MemorySize : " + mMemorySize + "GB" + 
            " \n StorageSize : " + mStorageSize + "GB \n}";
    }
}

具體的IPhoneX的類,由於CPU和系統是固定,而記憶體和運存運存可選。

public class IPhoneX extends Phone {

    public  IPhoneX(){}

    @Override
    public void setCPU() {
        mCPU = "A11";
    }

    @Override
    public void setOS() {
        mOS = "iOS 11";
    }
}

抽象的Builder類,作為主要隔離作用的類,Phone的API的每一個方法都有對應的build方法,並都返回自身來實現鏈式API。

public abstract class Builder {
    //設定CPU
    public abstract Builder buildCPU();
    //設定系統
    public abstract Builder buildOS();
    //設定運存大小
    public abstract Builder buildMemorySize(int memorySize);
    //設定儲存大小
    public abstract Builder buildStorageSize(int storageSize);
    //建立一個Phone物件
    public abstract Phone create();
}

IPhoneXBuilder,具體的Builder

public class IPhoneXBuilder extends Builder {

    private IPhoneX mIPhoneX = new IPhoneX();

    @Override
    public Builder buildCPU() {
        mIPhoneX.setCPU();
        return this;
    }

    @Override
    public Builder buildOS() {
        mIPhoneX.setOS();
        return this;
    }

    @Override
    public Builder buildMemorySize(int memorySize) {
        mIPhoneX.setMemorySize(memorySize);
        return this;
    }

    @Override
    public Builder buildStorageSize(int storageSize) {
        mIPhoneX.setStorageSize(storageSize);
        return this;
    }

    @Override
    public Phone create() {
        return mIPhoneX;
    }
}

Director類,負責構造Phone

public class Director {

    Builder mBuilder = null;

    public Director(Builder builder){
        mBuilder = builder;
    }

    public void construct(int memorySize,int storageSize){
        mBuilder.buildCPU()
            .buildOS()
            .buildMemorySize(memorySize)
            .buildStorageSize(storageSize);
    }
}

下面是測試程式碼

//構建器
Builder builder = new IPhoneXBuilder();
//Director
Director director = new Director(builder);
//封裝構建過程
director.construct(6,256);
//構建Phone,輸出相關資訊
Log.i(TAG, builder.create().toString());

通過具體的IPhoneXBuilder類構建IPhoneX物件,Director封裝了構建Phone物件的過程,隱藏構建的細節。BuilderDirector兩個部分起到了將物件的構建過程和物件的表示分離的作用。

之前也提到過,真是開發中Director類一般會省略,直接使用Builder,採用鏈式API進行組裝,在使用Buidler模式的過程中更加有效。

3.Android原始碼中得Builder模式實現

首先想到的就是AlertDialog.Builder,AlertDialog.Builder其實是AlertDialog的靜態內部類,所有的dialog屬性都暫存在AlertController.AlertParams的一個final物件中,以此來做到一個Builder物件只組裝一次,卻能構建出多個屬性相同的dialog。

這裡擷取了AlertDialog中比較關鍵的程式碼,

public class AlertDialog extends AppCompatDialog implements DialogInterface {

    final AlertController mAlert;

    protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
        super(context, resolveDialogTheme(context, themeResId));
        //構造AlertController物件
        mAlert = new AlertController(getContext(), this, getWindow());
    }
    
    //此處省略不知道多少程式碼。。。
    
    @Override
    public void setTitle(CharSequence title) {
        super.setTitle(title);
        mAlert.setTitle(title);
    }
    
    //******* 內部類Builder *******
    public static class Builder {
        //儲存AlertDialog的相關屬性
        private final AlertController.AlertParams P;
        private final int mTheme;
        
        public Builder(@NonNull Context context, @StyleRes int themeResId) {
            P = new AlertController.AlertParams(new ContextThemeWrapper(
                    context, resolveDialogTheme(context, themeResId)));
            mTheme = themeResId;
        }
        
        //...
        
        public Builder setTitle(@StringRes int titleId) {
            P.mTitle = P.mContext.getText(titleId);
            return this;
        }
        
        //...
        
        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);

            //將設定到Builder中得屬性設定到dialog的mAlert中,再由mAlert設定到view上
            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;
        }

        public AlertDialog show() {
            final AlertDialog dialog = create();
            dialog.show();
            return dialog;
        }
    }
}

還有一點值得關注,AlertDialog中的AlertController物件,是接收Builder成員變數AlertController.AlertParams中的各個引數。AlertControllerAlertController.AlertParams不太相同的地方是,AlertController中又真是的View和屬性設定,而AlertController.AlertParams只是作為一個暫時存放屬性值得物件,在apply()方法呼叫時將相關的屬性值傳遞給AlertDialogAlertController物件,再由AlertController`物件設定到view上。

4.總結

Builder模式在Android開發中也是比較常用的,通常作為配置類的構建器,將配置的構建和表示分離,同時也是將配置從使用中隔離出來,避免過多的setter方法。在Builder模式的實現中經常通過鏈式呼叫實現,達到通俗易懂的目的。

優點:

  • 良好的封裝性,使用者不用知道內部的實現細節
  • 容易擴充套件,由於Builder的獨立存在擴充套件不會影響原有邏輯

缺點:

  • 會產生多餘的Builder物件,可能還有Director物件,佔用記憶體

像網路框架(或圖片載入框架)的Config的構建,也是Builder模式所適用的場景。總的來說,設計模式還是需要明白設計的主要目的,才能更好地使用各種模式去解決實際問題。

相關文章