說明:將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。
使用場景
- 相同的方法,不同的執行順序,產生不同的事件結果時;
- 多個部件或零件,都可以裝配到一個對象中,但是產生的運行結果又不相同時;
- 產品類非常復雜,或者產品類中的調用順序不同產生了不同的作用,這時使用建造者模式非常合適;
- 當初始化一個對象特別復雜,如參數多,且很多參數都具有默認值時。
簡單實現
說明:舉個計算機的簡單組裝例子。分為構建主板、設置操作系統、設置顯示器 3 部分。
計算機抽象類,即 Product 對象
public abstract class Computer {
protected String mBoard;
protected String mDisplay;
protected String mOS;
public Computer() {
}
// 設置主板
public void setBoard(String board) {
mBoard = board;
}
// 設置顯示器
public void setDisplay(String display) {
mDisplay = display;
}
//設置操作系統
public abstract void setOS();
@Override
public String toString() {
return "Computer{" +
"mBoard='" + mBoard + '\'' +
", mDisplay='" + mDisplay + '\'' +
", mOS='" + mOS + '\'' +
'}';
}
}
具體的Computer類,Macbook
public class Macbook extends Computer {
public Macbook() {
}
@Override
public void setOS() {
mOS = "Mac OS X 10.10";
}
}
抽象builder類
public abstract class Builder {
// 設置主板
public abstract void buildBoard(String board);
// 設置顯示器
public abstract void buildDisplay(String display);
// 設置操作系統
public abstract void buildOS();
// 創建 Computer
public abstract Computer create();
}
具體的builder類,MacbookBuilder
public class MacbookBuilder extends Builder {
private Computer mComputer = new Macbook();
@Override
public void buildBoard(String board) {
mComputer.setBoard(board);
}
@Override
public void buildDisplay(String display) {
mComputer.setDisplay(display);
}
@Override
public void buildOS() {
mComputer.setOS();
}
@Override
public Computer create() {
return mComputer;
}
}
負責構造 Computer
public class Director {
Builder mBuilder;
public Director(Builder builder) {
mBuilder = builder;
}
/**
* 構建對象
*/
public void construct(String board, String display) {
mBuilder.buildBoard(board);
mBuilder.buildDisplay(display);
mBuilder.buildOS();
}
}
測試類
public class Test {
public static void main(String[] args) {
// 構建器
Builder builder = new MacbookBuilder();
// Director
Director pcDirector = new Director(builder);
// 封裝構建過程,4核、內存2GB、Mac系統
pcDirector.construct("Intel 主板", "Retina 顯示器");
// 構建計算機,輸出相關信息
System.out.println("Computer Info : " + builder.create().toString());
}
}
輸出結果:
Computer Info : Computer [mBoard=Intel 主板, mDisplay=Retina 顯示器, mOS=Mac OS X 10.10]
上面示例中,通過具體的 MacbookBuilder 來構建 Macbook 對象,二 Director 封裝了構建復雜產品對象的過程,對外隱藏構建細節。Builder 與 Director 一起將一個附在對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的對象。
在實際開發過程中,Director 角色經常會被省略。而直接使用一個 Builder 來進行對象的組裝,這個 Builder 通常為鏈式調用,對應的每個 setter 方法返回自身,即 return this。其大致代碼如下:
new TestBuilder().setA("A").setB("B").create();
Android 源碼中的 Builder 模式
最常見的 AlertDialog.Builder。詳情請自行搜索。
開發實際使用
此時參考別人寫好的 CustomerPopupWindow 類,如下
public class CustomPopupWindow extends PopupWindow {
private View mContentView;
private View mParentView;
private CustomPopupWindowListener mListener;
private boolean isOutsideTouch;
private boolean isFocus;
private Drawable mBackgroundDrawable;
private int mAnimationStyle;
private boolean isWrap;
private CustomPopupWindow(Builder builder) {
this.mContentView = builder.contentView;
this.mParentView = builder.parentView;
this.mListener = builder.listener;
this.isOutsideTouch = builder.isOutsideTouch;
this.isFocus = builder.isFocus;
this.mBackgroundDrawable = builder.backgroundDrawable;
this.mAnimationStyle = builder.animationStyle;
this.isWrap = builder.isWrap;
initLayout();
}
public static Builder builder() {
return new Builder();
}
private void initLayout() {
mListener.initPopupView(mContentView);
setWidth(isWrap ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT);
setHeight(isWrap ? LayoutParams.WRAP_CONTENT : LayoutParams.MATCH_PARENT);
setFocusable(isFocus);
setOutsideTouchable(isOutsideTouch);
setBackgroundDrawable(mBackgroundDrawable);
if (mAnimationStyle != -1)//如果設置了動畫則使用動畫
setAnimationStyle(mAnimationStyle);
setContentView(mContentView);
}
/**
* 獲得用于展示popup內容的view
*/
public View getContentView() {
return mContentView;
}
/**
* 用于填充contentView,必須傳ContextThemeWrapper(比如activity)不然popupwindow要報錯
*/
public static View inflateView(ContextThemeWrapper context, int layoutId) {
return LayoutInflater.from(context)
.inflate(layoutId, null);
}
public void show() {//默認顯示到中間
if (mParentView == null) {
showAtLocation(mContentView, Gravity.CENTER | Gravity.CENTER_HORIZONTAL, 0, 0);
} else {
showAtLocation(mParentView, Gravity.CENTER | Gravity.CENTER_HORIZONTAL, 0, 0);
}
}
public static final class Builder {
private View contentView;
private View parentView;
private CustomPopupWindowListener listener;
private boolean isOutsideTouch = true;//默認為true
private boolean isFocus = true;//默認為true
private Drawable backgroundDrawable = new ColorDrawable(0x00000000);//默認為透明
private int animationStyle = -1;
private boolean isWrap;
private Builder() {
}
public Builder contentView(View contentView) {
this.contentView = contentView;
return this;
}
public Builder parentView(View parentView) {
this.parentView = parentView;
return this;
}
public Builder isWrap(boolean isWrap) {
this.isWrap = isWrap;
return this;
}
public Builder isOutsideTouch(boolean isOutsideTouch) {
this.isOutsideTouch = isOutsideTouch;
return this;
}
public Builder isFocus(boolean isFocus) {
this.isFocus = isFocus;
return this;
}
public Builder backgroundDrawable(Drawable backgroundDrawable) {
this.backgroundDrawable = backgroundDrawable;
return this;
}
public Builder animationStyle(int animationStyle) {
this.animationStyle = animationStyle;
return this;
}
public Builder customListener(CustomPopupWindowListener listener) {
this.listener = listener;
return this;
}
public CustomPopupWindow build() {
if (contentView == null)
throw new IllegalStateException("contentView is required");
if (listener == null)
throw new IllegalStateException("CustomPopupWindowListener is required");
return new CustomPopupWindow(this);
}
}
public interface CustomPopupWindowListener {
public void initPopupView(View contentView);
}
}
總結
- 優點
- 良好的封裝性,使用建造者模式可以使客戶端不必知道產品內部組成的細節;
- 建造者獨立,容易擴展。
- 缺點
- 會產生多余的 Builder 對象以及 Director 對象,消耗內存。