設計模式 ~ 模板方法模式分析與實戰

設計模式系列文章目錄導讀:

設計模式 ~ 面向對象 6 大設計原則剖析與實戰
設計模式 ~ 模板方法模式分析與實戰
設計模式 ~ 觀察者模式分析與實戰
設計模式 ~ 單例模式分析與實戰
設計模式 ~ 深入理解建造者模式與實戰
設計模式 ~ 工廠模式剖析與實戰
設計模式 ~ 適配器模式分析與實戰
設計模式 ~ 裝飾模式探究
設計模式 ~ 深入理解代理模式
設計模式 ~ 小結

模板方法模式的定義

模板方法模式 (Template Method Parttern) 是一種非常簡單、應用非常廣泛的模式

什麼是模板方法模式:定義一個操作中的算法的框架,而將一些步驟延遲到子類中。使得子類可以不改變一個算法的結構即可重定義該算法的某些步驟

總結成一句話就是:父類封裝算法不可變的部分,可變的部分子類通過繼承來擴展

模板方法模式涉及兩個角色:

  • 抽象模板角色(Abstract Template)

    抽象模板也就是抽象類,裏面包含模板方法,模板方法也就是上面說的算法框架,模板方法由一個或者幾個抽象方法組成,抽象方法由子類來實現

  • 具體模板角色(Concrete Template)

    該角色實現抽象模板中的一個或多個抽象方法

模板方法模式類圖如下所示:
 模板方法模式類圖
根據上面的類圖可以很容易實現模板方法模式:

public abstract class AbstractClass {
	//模板方法,封裝固定算法邏輯
    public final void templateMethod(){
        operate1();
        System.out.println("處理相關邏輯...");
        operate2();
        System.out.println("處理相關邏輯2...");
    }

    protected abstract void operate1();
    protected abstract void operate2();
}

// 子類實現父類的抽象操作
public class ConcreteClass extends AbstractClass {
    @Override
    protected void operate1() {
        System.out.println("操作1");
    }

    @Override
    protected void operate2() {
        System.out.println("操作2");

    }
}

// 測試
public class Client {
    public static void main(String[] args) {
        ConcreteClass concreteClass = new ConcreteClass();
        concreteClass.templateMethod();
    }
}

// 控制檯輸出:
	操作1
	處理相關邏輯...
	操作2
	處理相關邏輯2...

模板方法模式的優點

模板方法模式的優點主要有三個:

  • 封裝不變部分,擴展可變部分
  • 提取公共代碼,便於維護
  • 行爲由父類控制,子類負責實現

模板方法模式的應用場景

  • 多個子類有公共方法,並且邏輯基本相同
  • 可以把重要的、複雜的、核心算法設計成模板方法,變化的部分交給子類來實現
  • 重構代碼時,模板方法模式是經常使用的模式,將相同的代碼抽取到父類中

項目實踐

Android Fragment 懶加載

熟悉 Android 開發的都知道,當 ViewPager 配合 Fragment 使用的時候,會出現兩個現象:

  • Fragment 從不可見又變成可見時 View 會被重建
  • Fragment 不可見時也會調用網絡請求

一般界面的承載都是放在 Fragment 中, 如果這個 Fragment 放在 ViewPager 中都會出現這樣的問題

我們可以通過 模板方法模式 將這個通用的固定的邏輯封裝在父類(BaseFragment)中

下面我們來看下如何解決這些問題

ViewPager 配合 Fragment 使用的時候,會觸發 setUserVisibleHint 方法,在這個方法裏可以判斷 Fragment 是否可見

@Override
public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    isSetUserVisibleHintInvoked = true;
    if (isVisible = getUserVisibleHint()) {
        onVisible();
    }
}

//當前界面可見狀態,且執行了 onCreateView 方法纔去加載數據
private void onVisible() {
    if (isVisible && isInvokedOnCreateView) {
        lazyLoad();
    }
}

/**
 * 加載數據的操作直接放在該方法裏
 */
protected void lazyLoad() {
    // 由子類實現
}

然後在 onCreateView 裏判斷處理 onVisible

// 保存已經創建的佈局
private View rootView;

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    isInvokedOnCreateView = true; 
    // 如果已經創建過,則斷開和父View之間的關係,然後返回
    // Fragment 從不可見又變成可見時 View 會被重建
    if (null != rootView) {
        ViewGroup parent = (ViewGroup) rootView.getParent();
        if (null != parent) {
            parent.removeView(rootView);
        }
    } else {
        rootView = createView(inflater, container);
    }
    // 說明不是和 ViewPager 一起用
    if (!isSetUserVisibleHintInvoked) {
        isVisible = true;
    }
    // 判斷是否需要
    onVisible();
    return rootView;
}


protected View createView(LayoutInflater inflater, ViewGroup container) {
    return inflater.inflate(getLayoutId(), container, false);
}

// 由子類來實現
protected abstract int getLayoutId();

上面的基本上實現了 Fragment 的懶加載功能,因爲父類不知道子類的佈局是什麼樣的所以子類需要實現 getLayoutId 方法,如果有網絡請求的話,父類也不知道具體的請求是什麼,所以子類需要實現 lazyLoad

綜上 BaseFragment 封裝了固定不變的算法:Fragment 懶加載功能;變化的部分如 getLayoutIdlazyLoad 由子類來實現

Android BaseListFragment 封裝通用列表

在實際開發中,其實很多界面都是列表形式的,不管是那種列表一般都會包含以下幾個功能:

  • 下拉刷新功能
  • 加載更功能
  • 加載更多失敗點擊Item重試
  • 列表頁碼的管理

一開始開發的時候沒有這樣的一個 BaseListFragment 類,列表頁面不同的人實現,有不同的風格,而且有些實現方案還有些問題

這個時候可以將列表的通用功能封裝到父類,利於將來的維護和擴展,把這樣的父類姑且叫做 BaseListFragment

BaseListFragment 是不知道子類的具體業務是什麼,所以必須讓子類來實現

/**
 * 加載列表數據
 */
protected abstract void loadListData();

不管是 刷新 還是 加載更多 都是調用 loadListData

/**
 * 下拉刷新
 */
private void refresh() {
    mIsRefreshing = true;
    mRefreshLayout.post(new Runnable() {
        @Override
        public void run() {
            if (!mAutoRefresh) {
                mRefreshLayout.setRefreshing(false);
                mAutoRefresh = true;
            } else {
                mRefreshLayout.setRefreshing(true);
            }
            // 加載列表數據
            loadListData();
        }
    });
}

// 加載更多
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
    @Override
    public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
        super.onScrolled(recyclerView, dx, dy);
        if (bottom(recyclerView)) {
            if (canLoadMore() && mAdapter.getListCount() != 0) {
                if (!mIsLoadingMore) {
                    mIsLoadingMore = true;
                    loadListData();
                }
            }
        }
    }
});

父類除了不知道子類的具體業務,也不知道子類要渲染怎樣的列表,所以 Adapter 的創建也必須是子類做

protected abstract BaseListAdapter createAdapter();

當然還有其他的邏輯如加載更多的方式是 pageIndex 還是 cursorMark 遊標的方式,還有對異常的處理等等,這些具體的實現細節在這裏就不貼出來了

總之通過 模板方法模式 重構列表功能後,實現新的列表界面會更加簡單,只需要關注自身相關的業務邏輯和具體的展示邏輯即可,實現相應的幾個抽象方法即可,數據請求成功後調用父類的渲染方法即可;另一方面也利於代碼的維護,將來如果發現封裝的邏輯有問題只需要修改 BaseListFragment 即可

小結

一般來說,模板方法封裝的是固定的算法邏輯,所以一般都需要加上 final 關鍵字,防止子類覆寫模板方法

模板方法模式是一個使用非常廣泛的模式,是日常開發中必須掌握的設計模式

掌握它能夠幫助我們更好的重用代碼,維護代碼更加方便。

Reference

  • 《設計模式之禪》
  • 《Java設計模式及實踐》
  • 《Java設計模式深入研究》
  • 《設計模式(Java版)》

如果你覺得本文幫助到你,給我個關注和讚唄!

另外本文涉及到的代碼都在我的 AndroidAll GitHub 倉庫中。該倉庫除了 設計模式,還有 Android 程序員需要掌握的技術棧,如:程序架構、設計模式、性能優化、數據結構算法、Kotlin、Flutter、NDK,以及常用開源框架 Router、RxJava、Glide、LeakCanary、Dagger2、Retrofit、OkHttp、ButterKnife、Router 的原理分析 等,持續更新,歡迎 star。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章