Android小知識-理解設計模式中的建造者模式
建造者模式的定義是:將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。也就是說將構建過程和部件的表示隔離開,使用者可以在不知道內部構建細節的情況下,對物件的構造流程進行相應的控制,比如在Android中典型的Builder模式就是AlerDialog.Builder類。
先看下UML圖:

image
模型圖上的四個角色介紹如下:
-
builder:為建立一個產品物件的各個部件指定抽象介面。
-
ConcreteBuilder:實現Builder的介面以構造和裝配該產品的各個部件,定義並明確它所建立的表示,並 提供一個檢索產品的介面。
-
Director:構造一個使用Builder介面的物件。
-
Product:表示被構造的複雜物件。ConcreteBuilder建立該產品的內部表示並定義它的裝配過程,包含定義組成部件的類,包括將這些部件裝配成最終產品的介面。
package com.apk.administrator.loadapk; /** * Product * 具體的產品 */ public class Person { private String head; private String body; private String foot; public String getHead() { return head; } public void setHead(String head) { this.head = head; } public String getBody() { return body; } public void setBody(String body) { this.body = body; } public String getFoot() { return foot; } public void setFoot(String foot) { this.foot = foot; } }
package com.apk.administrator.loadapk; /** * Builder * 產品構建的抽象介面 */ public interface PersonBuilder { void buildHead(); void buildBody(); void buildFoot(); Person buildPerson(); }
/** * Director * 建造者 */ public class PersonDirector { public Person constructPerson(PersonBuilder pb) { pb.buildHead(); pb.buildBody(); pb.buildFoot(); return pb.buildPerson(); } }
package com.apk.administrator.loadapk; /** * ConcreteBuilder * 具體建造工具(建立一個男人) */ public class ManBuilder implements PersonBuilder { Person person; public ManBuilder() { person = new Person(); } public void buildBody() { person.setBody("建造男人的身體"); } public void buildFoot() { person.setFoot("建造男人的腳"); } public void buildHead() { person.setHead("建造男人的頭"); } public Person buildPerson() { return person; } }
package com.apk.administrator.loadapk; public class Test { public static void main(String[] args) { PersonDirector pd = new PersonDirector(); Person person = pd.constructPerson(new ManBuilder()); System.out.println(person.getBody()); System.out.println(person.getFoot()); System.out.println(person.getHead()); } }
上面程式碼中最終的目的是要建立一個男人的物件,具體怎麼建立使用者不關心,只需要通過建造者PersonDirector建立一個物件,什麼物件?ManBuilder物件,這樣男人物件就創建出來了。
案例:Dialog的封裝
在封裝Dialog時,會使用靜態內部類Builder對Diloag的標題、內容、按鈕以及事件監聽進行配置,並通過CommonDialog類展示。
可以看出這是很典型的Builder模式,在配置引數時返回的是Builder本身,這樣的話可以通過鏈式呼叫,使程式碼的可讀性大大提高。由於封裝的是Dialog,因此,Builder類會去繼承Dialog來自定義Dialog。下面貼出完整的Dialog封裝類:
package com.glh.getproject.view; import android.app.Dialog; import android.content.Context; import android.os.Bundle; import android.text.TextUtils; import android.view.Gravity; import android.view.View; import android.view.View.OnClickListener; import android.view.WindowManager; import android.widget.TextView; import com.glh.getproject.R; public class CommonDialog { private Builder mBuilder; private CommonDialog(Builder builder) { this.mBuilder = builder; } public void show() { mBuilder.showDialog(); } public static class Builder extends Dialog implements OnClickListener { private TextView tv_title; private TextView tv_left; private TextView tv_right; private TextView tv_info; private DialogListener mDialogListener; private String title; private String info; private String strLeftBtn; private String strRightBtn; public interface DialogListener { void ok(); void cancel(); } void showDialog() { show(); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.dialogl_info_show); initUI(); initEvent(); initViewStatus(); } private void initUI() { tv_title = findViewById(R.id.tv_title); tv_left = findViewById(R.id.tv_left); tv_right = findViewById(R.id.tv_right); tv_info = findViewById(R.id.tv_info); } private void initViewStatus() { if (!TextUtils.isEmpty(title)) { tv_title.setText(title); } if (!TextUtils.isEmpty(info)) { tv_info.setText(info); } if (!TextUtils.isEmpty(strLeftBtn)) { tv_left.setText(strLeftBtn); } else { tv_left.setText("確定"); } if (!TextUtils.isEmpty(strRightBtn)) { tv_right.setText(strRightBtn); } else { tv_right.setText("取消"); } } private void initEvent() { tv_left.setOnClickListener(this); tv_right.setOnClickListener(this); } @Override public void onClick(View v) { switch (v.getId()) { case R.id.tv_left: if (mDialogListener != null) { dismiss(); mDialogListener.ok(); } break; case R.id.tv_right: if (mDialogListener != null) { dismiss(); mDialogListener.cancel(); } break; default: break; } } public Builder(Context context) { super(context, R.style.MessageBox); setCanceledOnTouchOutside(false); getWindow().setBackgroundDrawableResource(android.R.color.transparent); WindowManager.LayoutParams wl = getWindow().getAttributes(); wl.gravity = Gravity.CENTER; getWindow().setAttributes(wl); } public Builder setListener(DialogListener listener) { this.mDialogListener = listener; return this; } public Builder setTitle(String title) { this.title = title; return this; } public Builder setInfo(String info) { this.info = info; return this; } public Builder setLeftButtonTitle(String title) { this.strLeftBtn = title; return this; } public Builder setRightButtonTitle(String title) { this.strRightBtn = title; return this; } public CommonDialog build() { return new CommonDialog(this); } } }
相關的Style:
<style name="MessageBox"> <item name="android:windowNoTitle">true</item> <item name="android:windowIsFloating">true</item> <item name="android:windowContentOverlay">@null</item> <item name="android:layout_gravity">center</item> </style>
layout檔案:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/transparent"> <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:layout_marginLeft="20dp" android:layout_marginRight="20dp" android:background="@drawable/dialog_background_shape" android:paddingLeft="16dp" android:paddingRight="16dp"> <LinearLayout android:id="@+id/ll_title" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_marginTop="20dp" android:gravity="center_horizontal" android:orientation="vertical"> <TextView android:id="@+id/tv_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:lineSpacingExtra="2dp" android:textColor="@color/title" android:textSize="17sp" android:visibility="visible" /> <TextView android:id="@+id/tv_info" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" android:lineSpacingExtra="2dp" android:paddingTop="20dp" android:textColor="@color/title" android:textSize="17sp" android:visibility="visible" /> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/ll_title" android:layout_centerHorizontal="true" android:layout_marginTop="25dp" android:gravity="center" android:orientation="horizontal" android:paddingBottom="20dp" android:visibility="visible"> <TextView android:id="@+id/tv_left" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginRight="5dp" android:layout_weight="1" android:background="@drawable/red_button_shape" android:gravity="center" android:paddingBottom="7dp" android:paddingLeft="30dp" android:paddingRight="30dp" android:paddingTop="7dp" android:textColor="@color/white" android:textSize="14sp" android:visibility="visible" /> <TextView android:id="@+id/tv_right" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginLeft="5dp" android:layout_weight="1" android:background="@drawable/gray_button_shape" android:gravity="center" android:paddingBottom="7dp" android:paddingLeft="30dp" android:paddingRight="30dp" android:paddingTop="7dp" android:textColor="@color/white" android:textSize="14sp" android:visibility="visible" /> </LinearLayout> </RelativeLayout> </RelativeLayout>
整個Dialog的佈局背景和按鈕都是通過自定義Shape來實現的。
dialog_background_shape:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@color/white" /> <corners android:radius="10dp" /> </shape>
red_button_shape:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <corners android:radius="5dp" /> <solid android:color="#FA001F" /> </shape>
gray_button_shape:
<?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <corners android:radius="5dp" /> <solid android:color="@color/gray" /> </shape>
在Activity中使用:
public void showDialog() { CommonDialog commonDialog = new CommonDialog.Builder(this) .setListener(new CommonDialog.Builder.DialogListener() { @Override public void ok() { Toast.makeText(MainActivity.this, "正在更新...", Toast.LENGTH_SHORT).show(); } @Override public void cancel() { } }).setTitle("更新通知") .setInfo("當前版本暫不可用,請下載最新版本,以便享受優質內容。") .setLeftButtonTitle("知道了") .setRightButtonTitle("不需要") .build(); commonDialog.show(); }
案例二:PopupWindow的封裝
package com.glh.getproject.view; import android.content.Context; import android.graphics.drawable.BitmapDrawable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.PopupWindow; import android.widget.TextView; import com.glh.getproject.R; /** * 更新彈框 * Created by glh on 2017-05-09. */ public class UpdateDialog { private PopupWindow mPopupWindow; private Builder mBuilder; private UpdateDialog(Builder builder) { this.mBuilder = builder; View mPopupLayout = builder.mPopupLayout; this.mPopupWindow = new PopupWindow(mPopupLayout, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, true); this.mPopupWindow.setContentView(mPopupLayout); this.mPopupWindow.setOutsideTouchable(true); this.mPopupWindow.setFocusable(true); this.mPopupWindow.setBackgroundDrawable(new BitmapDrawable()); this.mPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() { @Override public void onDismiss() { if (mBuilder.mCloseListener != null) { mBuilder.mCloseListener.onClose(); } } }); this.mPopupWindow.update(); } public void dismiss() { if (!mPopupWindow.isShowing()) { return; } mPopupWindow.dismiss(); } public void showAtLocation(View parent, int gravity, int x, int y) { mPopupWindow.showAtLocation(parent, gravity, x, y); } public static class Builder { private View mPopupLayout; private TextView tv_title; private TextView tv_left; private TextView tv_right; private TextView tv_info; private UpgradeListener mUpgradeListener; private CloseListener mCloseListener; private void initEvent() { tv_left.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mUpgradeListener != null) { mUpgradeListener.upgrade(true); } } }); tv_right.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { if (mUpgradeListener != null) { mUpgradeListener.upgrade(false); } } }); } public interface UpgradeListener { void upgrade(boolean upgrade); } public interface CloseListener { void onClose(); } public Builder(Context context) { mPopupLayout = LayoutInflater.from(context).inflate(R.layout.dialogl_info_show, null, true); tv_title = mPopupLayout.findViewById(R.id.tv_title); tv_left = mPopupLayout.findViewById(R.id.tv_left); tv_right = mPopupLayout.findViewById(R.id.tv_right); tv_info = mPopupLayout.findViewById(R.id.tv_info); initEvent(); } public Builder setUpgradeListener(UpgradeListener listener) { this.mUpgradeListener = listener; return this; } public Builder setCloseListener(CloseListener listener) { this.mCloseListener = listener; return this; } public Builder setTitle(String title) { this.tv_title.setText(title); return this; } public Builder setInfo(String info) { this.tv_info.setText(info); return this; } public Builder setLeftButtonTitle(String title) { this.tv_left.setText(title); return this; } public Builder setRightButtonTitle(String title) { this.tv_right.setText(title); return this; } public UpdateDialog build() { return new UpdateDialog(this); } } }
layout檔案和Dialog的佈局檔案一樣,這裡就不貼出來了。
在Activity的使用:
private UpdateDialog commonDialog; private void showPopupWindow() { if (null == commonDialog) { commonDialog = new UpdateDialog.Builder(this) .setUpgradeListener(new UpdateDialog.Builder.UpgradeListener() { @Override public void upgrade(boolean upgrade) { commonDialog.dismiss(); if (upgrade) { Toast.makeText(MainActivity.this, "正在更新...", Toast.LENGTH_SHORT).show(); } } }).setCloseListener(new UpdateDialog.Builder.CloseListener() { @Override public void onClose() { Toast.makeText(MainActivity.this, "對話方塊關閉了", Toast.LENGTH_SHORT).show(); } }).setTitle("更新通知") .setInfo("當前版本暫不可用,請下載最新版本,以便享受優質內容。") .setLeftButtonTitle("知道了") .setRightButtonTitle("不需要") .build(); } commonDialog.showAtLocation(mGroup, Gravity.CENTER, 0, 0); }
總結
總的來說相同的方法,不同的執行順序,產生不同的事件結果時,可以考慮採用建造者模式;多個部件或零件,都可以裝配到一個物件中,但產生的執行結果又不相同時,可以考慮採用建造者模式;產品類非常複雜,或者產品類中的呼叫順序不同產生了不同的效能,可以考慮採用建造者模式。

838794-506ddad529df4cd4.webp.jpg
搜尋微信“顧林海”公眾號,定期推送優質文章。