1. 程式人生 > >Android建造者模式筆記

Android建造者模式筆記

前言

最近在進行Android程式設計的時候,通過自己的體會和檢視學習別人的程式碼對Android建造者模式有了自己的理解,這裡就做一個筆記,以供參考。建造者模式是一種常見的軟體設計模式,因為我目前主要在Android平臺上進行程式設計,所以就用Android程式碼舉例子。如果寫的不是很詳細的話,請見諒。

什麼是建造者模式

首先,什麼是建造者模式。建造者模式是設計模式的一種,它將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示。
如下圖[百度百科]:
百度百科
建造者模式一般有四個角色:

  • 抽象建造者(Builder)角色:給出一個抽象介面,以規範產品物件的各個組成成分的建造。
  • 具體建造者(ConcreteBuilder)角色:擔任這個角色的是與應用程式緊密相關的一些類,它們在應用程式呼叫下建立產品的例項。這個角色要完成的任務包括:1.實現抽象建造者Builder所宣告的介面,給出一步一步地完成建立產品例項的操作。2.在建造過程完成後,提供產品的例項。
  • 導演者(Director)角色:擔任這個角色的類呼叫具體建造者角色以建立產品物件。
  • 產品(Product)角色:產品便是建造中的複雜物件。

一般來說,每有一個產品者,就有一個相應的具體建造者。這些產品應當有一樣數目的零件,而每有一個零件就相應地在所有的建造者角色裡有一個建造方法。

這些都是一些定義類的語言,後面舉例子。

Android例子

Android中AlertDialog是一個建造者模式的例子,可以有著不同的樣式和呈現,這樣通過Builder就可以有效實現構建和表示的分離。

不過我後面要提的例子是Paginate,它是Github上的一個開源庫。它的作用是:Library for creating simple pagination functionality upon RecyclerView and AbsListView。(為AbsListView和RecyclerView 提供簡單的分頁功能。)

它的構造是:

paginate = Paginate.with(refreshView, this)
        .setLoadingTriggerThreshold
(2) .addLoadingListItem(loadListener != null) .build();

它的主幹程式碼如下:
AbsListViewPaginate.java

package com.paginate.abslistview;

import android.database.DataSetObserver;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.HeaderViewListAdapter;

import com.paginate.Paginate;

public final class AbsListViewPaginate extends Paginate implements EndScrollListener.Callback {

    private final AbsListView absListView;
    private final Callbacks callbacks;
    private EndScrollListener scrollListener;
    private WrapperAdapter wrapperAdapter;

    AbsListViewPaginate(AbsListView absListView,
                        Paginate.Callbacks callbacks,
                        int loadingTriggerThreshold,
                        AbsListView.OnScrollListener onScrollListener,
                        boolean addLoadingListItem,
                        LoadingListItemCreator loadingListItemCreator) {
        this.absListView = absListView;
        this.callbacks = callbacks;

        // Attach scrolling listener in order to perform end offset check on each scroll event
        scrollListener = new EndScrollListener(this);
        scrollListener.setThreshold(loadingTriggerThreshold);
        scrollListener.setDelegate(onScrollListener);
        absListView.setOnScrollListener(scrollListener);

        if (addLoadingListItem) {
            BaseAdapter adapter;
            if (absListView.getAdapter() instanceof BaseAdapter) {
                adapter = (BaseAdapter) absListView.getAdapter();
            } else if (absListView.getAdapter() instanceof HeaderViewListAdapter) {
                adapter = (BaseAdapter) ((HeaderViewListAdapter) absListView.getAdapter()).getWrappedAdapter();
            } else {
                throw new IllegalStateException("Adapter needs to be subclass of BaseAdapter");
            }

            // Wrap existing adapter with new adapter that will add loading row
            wrapperAdapter = new WrapperAdapter(adapter, loadingListItemCreator);
            adapter.registerDataSetObserver(dataSetObserver);
            ((AdapterView) absListView).setAdapter(wrapperAdapter);
        }
    }

    @Override
    public void setHasMoreDataToLoad(boolean hasMoreDataToLoad) {
        if (wrapperAdapter != null) {
            wrapperAdapter.displayLoadingRow(hasMoreDataToLoad);
        }
    }

    @Override
    public void onEndReached() {
        if (!callbacks.isLoading() && !callbacks.hasLoadedAllItems()) {
            callbacks.onLoadMore();
        }
    }

    @Override
    public void unbind() {
        // Swap back original scroll listener
        absListView.setOnScrollListener(scrollListener.getDelegateScrollListener());

        // Swap back source adapter
        if (absListView.getAdapter() instanceof WrapperAdapter) {
            WrapperAdapter wrapperAdapter = (WrapperAdapter) absListView.getAdapter();
            BaseAdapter adapter = (BaseAdapter) wrapperAdapter.getWrappedAdapter();
            adapter.unregisterDataSetObserver(dataSetObserver);
            ((AdapterView) absListView).setAdapter(adapter);
        }
    }

    private final DataSetObserver dataSetObserver = new DataSetObserver() {
        @Override
        public void onChanged() {
            wrapperAdapter.displayLoadingRow(!callbacks.hasLoadedAllItems());
            wrapperAdapter.notifyDataSetChanged();
        }

        @Override
        public void onInvalidated() {
            wrapperAdapter.displayLoadingRow(!callbacks.hasLoadedAllItems());
            wrapperAdapter.notifyDataSetInvalidated();
        }
    };

    public static class Builder {
        private final AbsListView absListView;
        private final Paginate.Callbacks callbacks;

        private int loadingTriggerThreshold = 5;
        private AbsListView.OnScrollListener onScrollListener;
        private boolean addLoadingListItem = true;
        private LoadingListItemCreator loadingListItemCreator;

        public Builder(AbsListView absListView, Paginate.Callbacks callbacks) {
            this.absListView = absListView;
            this.callbacks = callbacks;
        }

        /**
         * Set the offset from the end of the list at which the load more event needs to be triggered. Default offset
         * if 5.
         *
         * @param threshold number of items from the end of the list.
         * @return {@link com.paginate.abslistview.AbsListViewPaginate.Builder}
         */
        public Builder setLoadingTriggerThreshold(int threshold) {
            this.loadingTriggerThreshold = threshold;
            return this;
        }

        /**
         * Paginate is using OnScrollListener in order to detect when list is scrolled near the end. That means that
         * internal listener is attached on AbsListView. Since AbsListView can have only one OnScrollListener it is
         * needed to use this method to add additional OnScrollListener (as delegate).
         *
         * @param onScrollListener that will be called when list is scrolled.
         * @return {@link com.paginate.abslistview.AbsListViewPaginate.Builder}
         */
        public Builder setOnScrollListener(AbsListView.OnScrollListener onScrollListener) {
            this.onScrollListener = onScrollListener;
            return this;
        }

        /**
         * Setup loading row. If loading row is used original adapter set on AbsListView will be wrapped with
         * internal adapter that will add loading row as the last item in the list. Paginate will observer the
         * changes upon original adapter and remove loading row if there is no more data to load. By default loading
         * row will be added.
         *
         * @param addLoadingListItem true if loading row needs to be added, false otherwise.
         * @return {@link com.paginate.abslistview.AbsListViewPaginate.Builder}
         * @see {@link com.paginate.Paginate.Callbacks#hasLoadedAllItems()}
         * @see {@link com.paginate.abslistview.AbsListViewPaginate.Builder#setLoadingListItemCreator(LoadingListItemCreator)}
         */
        public Builder addLoadingListItem(boolean addLoadingListItem) {
            this.addLoadingListItem = addLoadingListItem;
            return this;
        }

        /**
         * Set custom loading list item creator. If no creator is set default one will be used.
         *
         * @param loadingListItemCreator Creator that will ne called for inflating and binding loading list item.
         * @return {@link com.paginate.abslistview.AbsListViewPaginate.Builder}
         */
        public Builder setLoadingListItemCreator(LoadingListItemCreator loadingListItemCreator) {
            this.loadingListItemCreator = loadingListItemCreator;
            return this;
        }

        /**
         * Create pagination functionality upon AbsListView.
         *
         * @return {@link Paginate} instance.
         */
        public Paginate build() {
            if (absListView.getAdapter() == null) {
                throw new IllegalStateException("Adapter needs to be set!");
            }

            if (loadingListItemCreator == null) {
                loadingListItemCreator = LoadingListItemCreator.DEFAULT;
            }

            return new AbsListViewPaginate(absListView, callbacks, loadingTriggerThreshold, onScrollListener,
                    addLoadingListItem, loadingListItemCreator);
        }
    }

}

Paginate.java

package com.paginate;

import android.support.v7.widget.RecyclerView;
import android.widget.AbsListView;

import com.paginate.abslistview.AbsListViewPaginate;
import com.paginate.recycler.RecyclerPaginate;

public abstract class Paginate {

    public interface Callbacks {
        /** Called when next page of data needs to be loaded. */
        void onLoadMore();

        /**
         * Called to check if loading of the next page is currently in progress. While loading is in progress
         * {@link com.paginate.Paginate.Callbacks#onLoadMore} won't be called.
         *
         * @return true if loading is currently in progress, false otherwise.
         */
        boolean isLoading();

        /**
         * Called to check if there is more data (more pages) to load. If there is no more pages to load, {@link
         * com.paginate.Paginate.Callbacks#onLoadMore} won't be called and loading row, if used, won't be added.
         *
         * @return true if all pages has been loaded, false otherwise.
         */
        boolean hasLoadedAllItems();
    }

    /**
     * Use this method to indicate that there is or isn't more data to load. If there isn't any more data to load
     * loading row, if used, won't be displayed as the last item of the list. Adding/removing loading row is done
     * automatically each time when underlying adapter data is changed. Use this method to explicitly add/remove
     * loading row.
     *
     * @param hasMoreDataToLoad true if there is more data to load, false otherwise.
     */
    abstract public void setHasMoreDataToLoad(boolean hasMoreDataToLoad);

    /**
     * Call unbind to detach list (RecyclerView or AbsListView) from Paginate when pagination functionality is no
     * longer needed on the list.
     * <p/>
     * Paginate is using scroll listeners and adapter data observers in order to perform required checks. It wraps
     * original (source) adapter with new adapter that provides loading row if loading row is used. When unbind is
     * called original adapter will be set on the list and scroll listeners and data observers will be detached.
     */
    abstract public void unbind();

    /**
     * Create pagination functionality upon RecyclerView.
     *
     * @param recyclerView RecyclerView that will have pagination functionality.
     * @param callback     pagination callbacks.
     * @return {@link com.paginate.recycler.RecyclerPaginate.Builder}
     */
    public static RecyclerPaginate.Builder with(RecyclerView recyclerView, Callbacks callback) {
        return new RecyclerPaginate.Builder(recyclerView, callback);
    }

    /**
     * Create pagination functionality upon AbsListView.
     *
     * @param absListView AbsListView that will have pagination functionality (ListView or GridView).
     * @param callback    pagination callbacks.
     * @return {@link com.paginate.abslistview.AbsListViewPaginate.Builder}
     */
    public static AbsListViewPaginate.Builder with(AbsListView absListView, Callbacks callback) {
        return new AbsListViewPaginate.Builder(absListView, callback);
    }

}

由上面貼出的大段程式碼可以看出:
這個構建方法使用的是建造者模式。通過Builder來構建產品物件, 不對外隱藏構建細節。
Builder為AbsListViewPaginate的內部類,而AbsListViewPaginate是繼承自Paginate的子類。

Product:

一般Product角色都是如下:

/**
 * Computer產品抽象類, 為了例子簡單, 只列出這幾個屬性
 * 
 * @author mrsimple
 *
 */
public abstract class Computer {

    protected int mCpuCore = 1;
    protected int mRamSize = 0;
    protected String mOs = "Dos";

    protected Computer() {

    }

    // 設定CPU核心數
    public abstract void setCPU(int core);

    // 設定記憶體
    public abstract void setRAM(int gb);

    // 設定作業系統
    public abstract void setOs(String os);
}

但是在Paginate類中,它直接將成員變數定義在了Builder類裡,如下:

public static class Builder {
        private final AbsListView absListView;
        private final Paginate.Callbacks callbacks;

        private int loadingTriggerThreshold = 5;
        private AbsListView.OnScrollListener onScrollListener;
        private boolean addLoadingListItem = true;
        private LoadingListItemCreator loadingListItemCreator;

所以這一部分產品應該沒有單獨列出來。

builder角色:

一般builder角色都如下:

/**
 * builder抽象類
 *
 */
public abstract class Builder {
    // 設定CPU核心數
    public abstract void buildCPU(int core);

    // 設定記憶體
    public abstract void buildRAM(int gb);

    // 設定作業系統
    public abstract void buildOs(String os);

    // 建立Computer
    public abstract Computer create();

}

但是在Paginate類中,也同樣是在Builder類裡做了處理,並沒有單獨寫出來:

 public Builder setLoadingListItemCreator(LoadingListItemCreator loadingListItemCreator) {
            this.loadingListItemCreator = loadingListItemCreator;
            return this;
        }

        /**
         * Create pagination functionality upon AbsListView.
         *
         * @return {@link Paginate} instance.
         */
        public Paginate build() {
            if (absListView.getAdapter() == null) {
                throw new IllegalStateException("Adapter needs to be set!");
            }

            if (loadingListItemCreator == null) {
                loadingListItemCreator = LoadingListItemCreator.DEFAULT;
            }

            return new AbsListViewPaginate(absListView, callbacks, loadingTriggerThreshold, onScrollListener,
                    addLoadingListItem, loadingListItemCreator);
        }
    }

具體建造者:

具體建造者應該是這裡的builder類:它為成員變數賦值,並最後用builder方法創建出了當前類的物件。

導演者:

導演者一般是最後在客戶端呼叫的時候面對客戶端的一個角色,但是它通常可以省略。如下:

public class Director {
    Builder mBuilder = null;

    /**
     * 
     * @param builder
     */
    public Director(Builder builder) {
        mBuilder = builder;
    }

    /**
     * 構建物件
     * 
     * @param cpu
     * @param ram
     * @param os
     */
    public void construct(int cpu, int ram, String os) {
        mBuilder.buildCPU(cpu);
        mBuilder.buildRAM(ram);
        mBuilder.buildOs(os);
    }
}

但是在Paginate類中,這一部分被省略了,而直接使用build方法構建了物件。

其實寫這麼多,主要是為了記憶怎麼使用構造者模式,它有著非常明顯的優勢:良好的封裝性, 使用建造者模式可以使客戶端不必知道產品內部組成的細節。寫這篇筆記也是為了讓我自己能更好地瞭解如何使用構造者模式,同時也是對Paginate類的一個簡單的分析,如果寫的不詳細的話,見諒~