設計模式系列文章目錄導讀:
設計模式 ~ 面向對象 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
懶加載功能;變化的部分如 getLayoutId
和 lazyLoad
由子類來實現
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。