Builder 模式

前言

在開發中,常常用到builder設計模式,但感受最多見的應用場景就是構造對象參數較多的時候,本文將builder模式梳理總結一下。html

定義

非要給builder模式一個定義,我就查看了《Android源碼設計模式解析與實戰》,如下是其給出的定義:java

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

這樣的總結比較經典,可是感受離完全明白其中的含義還差點距離。前半句能夠理解爲將一個對象的建立過程分多步,後半句能夠這樣理解,使用一樣的構建過程,傳遞不一樣的參數會產生不一樣的結果。設計模式

經典寫法

上圖爲builder經典寫法的uml,其實在實際開發過程當中,Director部分常常就被去掉了。 以下所示,定義了AbstractBuilder:

public abstract class AbstractBuilder {
    	public abstract void buildPart1(int numOfWheel);
    	public abstract void buildPart2(int numOfSeat);
    	public abstract void buildPart3(int capacity);
    	public abstract Vehicle build();
    }
複製代碼

demo的目標是將車輛Vehicle的構建分離,demo中的Product爲Vehicle類型,能夠看下Vehicle的定義:微信

public abstract class Vehicle {
        // 車輪的數量
    	protected int numOfWheel;
    	// 座椅的數量
    	protected int numOfSeat;
    	// 載重,car按照人數,truck按照噸數
    	protected int capacity;
    	
    	public void setNumOfWheel(int numOfWheel) {
    		this.numOfWheel = numOfWheel;
    	}
    	public void setNumOfSeat(int numOfSeat) {
    		this.numOfSeat = numOfSeat;
    	}
    	public abstract void setCapacity(int capacity);
    }
複製代碼

demo中Vehicle的子類有Car(小轎車)和Truck(卡車),Car類的定義以下:app

public class Car extends Vehicle {

    	@Override
    	public void setCapacity(int capacity) {
    		// TODO Auto-generated method stub
    		super.capacity = capacity;
    	}
    	
    	@Override
    	public String toString() {
    		// TODO Auto-generated method stub
    		return "car parameter:" + numOfWheel + "-" + numOfSeat + "-" + capacity;
    	}
    }
複製代碼

Truck的定義以下:ide

public class Truck extends Vehicle {

    	@Override
    	public void setCapacity(int capacity) {
    		// TODO Auto-generated method stub
    		super.capacity = capacity;
    	}
    	
    	@Override
    	public String toString() {
    		// TODO Auto-generated method stub
    		return "car parameter:" + numOfWheel + "-" + numOfSeat + "-" + capacity + " ton";
    	}
    }
複製代碼

接下來就看下Car的builder CarBuilder:函數

public class CarBuilder extends AbstractBuilder {
    	private Car car = new Car();
    	
    	@Override
    	public void buildPart1(int numOfWheel) {
    		// TODO Auto-generated method stub
    		car.setNumOfWheel(numOfWheel);
    	}
    	
    	@Override
    	public void buildPart2(int numOfSeat) {
    		// TODO Auto-generated method stub
    		car.setNumOfSeat(numOfSeat);
    	}
    	
    	public void buildPart3(int capacity) {
    		// TODO Auto-generated method stub
    		car.setCapacity(capacity);
    	}
    	
    	public Vehicle build() {
    		return car;
    	}
    }
複製代碼

TruckBuilder定義以下:測試

public class TruckBuilder extends AbstractBuilder {
    	private Truck truck = new Truck();
    	@Override
    	public void buildPart1(int numOfWheel) {
    		// TODO Auto-generated method stub
    		truck.setNumOfWheel(numOfWheel);
    	}
    
    	@Override
    	public void buildPart2(int numOfSeat) {
    		// TODO Auto-generated method stub
    		truck.setNumOfSeat(numOfSeat);
    	}
    
    	@Override
    	public void buildPart3(int capacity) {
    		// TODO Auto-generated method stub
    		truck.setCapacity(capacity);
    	}
    
    	@Override
    	public Vehicle build() {
    		// TODO Auto-generated method stub
    		return truck;
    	}
    }
複製代碼

以上定義了CarBuiler, TruckBuiler。雖然在實際開發中常常會省略掉Director部分,爲了演示,demo也定義了Directorui

public class Director {
    	private AbstractBuilder builder;
    	public Director(AbstractBuilder builder) {
    		this.builder = builder;
    	}
    	public void construct(int numOfWheel, int numOfSeat, int capacity) {
    		if (builder != null) {
    			builder.buildPart1(numOfWheel);
    			builder.buildPart2(numOfSeat);
    			builder.buildPart3(capacity);
    		}
    	}
    }
複製代碼

OK,全部須要定義的部分已經完成,接下來就去調用一下:

public class Client {
    	public static void main(String [] args) {
    		AbstractBuilder builder = new CarBuilder();
    		Director director = new Director(builder);
    		director.construct(4, 5, 5);
    		Car car = (Car) builder.build();
    		System.out.println(car);
    		
    		AbstractBuilder builder2 = new TruckBuilder();
    		Director director2 = new Director(builder2);
    		director2.construct(8, 2, 5);
    		Truck truck = (Truck) builder2.build();
    		System.out.println(truck);
    	}
    }
複製代碼

程序輸入以下: car parameter:4-5-5 truck parameter:8-2-5 ton

demo演示部分將Vehicle的構造過程分3步,執行完3步構建後返回實例對象。

平常寫法

上面的demo是經典的寫法,但在實際開發中,不多寫的那麼標準或者那麼複雜,大多數狀況下builder模式主要是爲了防止在構建對象時傳遞太多的參數。查看下如下demo:

public class Student {
    	private String name;
    	private String nickName;
    	private String sex;
    	private int age;
    	private int weight;
    	private int height;
    	
    	public Student(String name, String nickName, String sex, int age, int weight, int height) {
    		// TODO Auto-generated constructor stub
    		this.name = name;
    		this.nickName = nickName;
    		this.sex = sex;
    		this.age = age;
    		this.weight = weight;
    		this.height = height;
    	}
    	
    	@Override
    	public String toString() {
    		// TODO Auto-generated method stub
    		return "student info:name=" + name + "\n" +
    							"nickname=" + nickName + "\n" +
    							"sex=" + sex + "\n" + 
    							"age=" + age + "\n" + 
    							"weight=" + weight + "\n" +
    							"height=" + height;
    	}
    	
    	public static class Builder {
    		private String name;
    		private String nickName;
    		private String sex;
    		private int age;
    		private int weight;
    		private int height;
    		
    		public Builder name(String name) {
    			this.name = name;
    			return this;
    		}
    		public Builder nickName(String nickName) {
    			this.nickName = nickName;
    			return this;
    		}
    		public Builder sex(String sex) {
    			this.sex = sex;
    			return this;
    		}
    		public Builder age(int age) {
    			this.age = age;
    			return this;
    		}
    		public Builder weight(int weight) {
    			this.weight = weight;
    			return this;
    		}
    		public Builder height(int height) {
    			this.height = height;
    			return this;
    		}
    		public Student build() {
    			return new Student(name, nickName, sex, age, weight, height);
    		}
    	} 
    }
複製代碼

如下是測試程序:

public class Client {
    	public static void main(String [] args) {
    		Student student = new Student.Builder().name("rock")
    											   .nickName("store")
    											   .sex("boy")
    											   .age(12)
    											   .weight(60)
    											    .height(176).build();
    		System.out.println(student);
    	}
    }
複製代碼

程序運行結果以下:

student info:name=rock
nickname=store
sex=boy
age=12
weight=60
height=176
複製代碼

Android中Builder使用場景

在開發過程當中,常常使用的builder模式其實就是上文所說的平常寫法,Android中最多見的builder模式就是AlertDialog的建立過程了,如下是AlertDialog建立過程的常見寫法。

AlertDialog.Builder builder = new AlertDialog.Builder(context)
                                                 .setTitle(title)
                                                 .setView(view)
                                                 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
                                                    @Override
                                                    public void onClick(DialogInterface dialog, int which) {
                                                    
                                                    }
                                                })
                                                .setNegativeButton(android.R.string.cancel, null);
    builder.create().show();
複製代碼

感受很熟悉,這就是咱們最經常使用的AlertDialog的構建過程。扒一扒源碼,因爲AlertDialog.Builder的源碼較多,就不所有貼出來,感興趣的同窗能夠自行看一下。

public static class Builder {
        private final AlertController.AlertParams P;

        
        public Builder(Context context) {
            this(context, resolveDialogTheme(context, ResourceId.ID_NULL));
        }

        
       ......
       
        public Builder setTitle(@StringRes int titleId) {
            P.mTitle = P.mContext.getText(titleId);
            return this;
        }

        
        public Builder setTitle(CharSequence title) {
            P.mTitle = title;
            return this;
        }

       
        public Builder setCustomTitle(View customTitleView) {
            P.mCustomTitleView = customTitleView;
            return this;
        }

        ......

        
        public Builder setMessage(CharSequence message) {
            P.mMessage = message;
            return this;
        }

       public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) {
            P.mPositiveButtonText = P.mContext.getText(textId);
            P.mPositiveButtonListener = listener;
            return this;
        }
        ......
        
        public AlertDialog create() {
            // Context has already been wrapped with the appropriate theme.
            final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
            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;
        }

    }
複製代碼

AlertDialog.Buidler類中定義各類set方法,執行完set方法以後再執行create方法便建立了一個AlertDialog。這應該是一個標準的builder模式了。能夠發現AlertDialog.Builder執行set方法,其實就是將set參數複製給了對象P。對象P是什麼結構呢?

public static class AlertParams {
        public final Context mContext;
        public final LayoutInflater mInflater;

        public int mIconId = 0;
        public Drawable mIcon;
        public int mIconAttrId = 0;
        public CharSequence mTitle;
        public View mCustomTitleView;
        public CharSequence mMessage;
        public CharSequence mPositiveButtonText;
        public DialogInterface.OnClickListener mPositiveButtonListener;
        public CharSequence mNegativeButtonText;
        public DialogInterface.OnClickListener mNegativeButtonListener;
        public CharSequence mNeutralButtonText;
        public DialogInterface.OnClickListener mNeutralButtonListener;
        public boolean mCancelable;
        public DialogInterface.OnCancelListener mOnCancelListener;
        public DialogInterface.OnDismissListener mOnDismissListener;
        public DialogInterface.OnKeyListener mOnKeyListener;
        public CharSequence[] mItems;
        public ListAdapter mAdapter;
        public DialogInterface.OnClickListener mOnClickListener;
        public int mViewLayoutResId;
        public View mView;
        public int mViewSpacingLeft;
        public int mViewSpacingTop;
        public int mViewSpacingRight;
        public int mViewSpacingBottom;
        public boolean mViewSpacingSpecified = false;
        public boolean[] mCheckedItems;
        public boolean mIsMultiChoice;
        public boolean mIsSingleChoice;
        public int mCheckedItem = -1;
        public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener;
        public Cursor mCursor;
        public String mLabelColumn;
        public String mIsCheckedColumn;
        public boolean mForceInverseBackground;
        public AdapterView.OnItemSelectedListener mOnItemSelectedListener;
        public OnPrepareListViewListener mOnPrepareListViewListener;
        public boolean mRecycleOnMeasure = true;

        ......
    }
複製代碼

能夠發現,AlertController.AlertParams類型的對象P其實就是存放了構建AlertDialog須要的各類參數。對象P中海油其餘函數操做,感興趣的同窗能夠去看一下。

將參數保存到P對象,而後執行create函數,建立新的AlertDialog對象,而後P中存放的屬性設置給新建的AlertDilaog對象,這樣,就完成了AlertDialog的構建。

總結

Builder模式的目標是將複雜對象的建立過程進行分解,使對象的構建與表示分離,使得一樣的構建過程能夠建立不一樣的表示。在實際開發過程當中,一般是在複雜對象內部申明靜態內部類Builder,在Builder中保存複雜對象的屬性,而後使用create或者build函數將保存的屬性設置給對象。

其實平常開發過程當中使用builder模式尚未讓咱們領略到builer模式的強大,建議參考下這篇文章體會一下: www.cnblogs.com/happyhippy/…

關注微信公衆號,最新技術乾貨實時推送

image
相關文章
相關標籤/搜索