Overview of Content
在這篇文章中,我們將探討 Template
模板設計模式,這是一種行為型設計模式,用於定義一個演算法的骨架,但將一些步驟推遲到子類別中。這使得子類別可以重新定義某些步驟,而不改變演算法的結構。
我們將從概念性的角度介紹 Template 模式的使用場景,討論其定義與 UML 圖表示。接著,我們將分析 Template 模式的優缺點,以便讀者全面了解使用這種設計模式的利弊。
並且我們將提供 Template 模式的實際實現示例,並介紹一個稱為 "Template 標準
" 的主題,以簡單的概念進入這個設計模式的理解。
最後,我們在分析 Android 源碼中的 AsyncTask
的實現,加深讀者對 Template 模式的深入理解,並觀察實際的應用!
透過本文,您將獲得 Template 模板設計模式的深入見解,以及在實際應用中如何充分利用它的知識。讓我們一同探索這個強大而靈活的設計模式吧!
寫文章分享不易,如有引用參考請詳註出處,如有指導、意見歡迎留言(如果覺得寫得好也請給我一些支持),感謝 😀
Template 使用場景
● 多個子類有共有方法,並有相同的處理邏輯
● 當需要固定算法順序時可以使用
● 封裝其固定流程 多子類有共同方法,並且邏輯順序固定
● 固定核心算法,當算法出現變動時可以一起更改,而細節可以分別由子類決定
● 實現反向控制 (子類決定是否實現該方法)
定義模板,固定演算法順序,讓繼承 子類實現詳細細節
Template 定義 & Template UML
● Template 定義:
定義一個操作算法的框架(skeleton
),由子類實現方法,並且 子類不得改變算法的特定步驟
●Template UML 角色
類角色 | 說明 |
---|---|
AbstractClass (抽象類) | 操作一系列的基本抽象方法來達到目的 |
ConcreteClass_1 、ConcreteClass_2 | 1 ~ 到多個實做類,個別實現機類方法,其本身不會影響到模板,但它們是模板的實作細節 |
Template 優缺點
● Template
設計模式優點 :
● 集結固定不變的行為,將細節由子類決定
封裝不變部份、拓展可變部份
● 子類實行細節,有助於多種類演算法,方便維護● 實現反向控制 (子類決定是否實現該方法)
父類控制行為,子類控制方法
● Template
設計模式缺點 :
● 由於對於設計的重用,所以使用者用起來會更加抽象,需要使用者詳細了解模板設計的架構手冊(Template
模式也稱為 微框架模式)
● Template
拓展出的子類別會因而變多,對於使用者要全面了解會更有負擔
● 實現反向控制 (子類決定是否實現該方法)
Template 實現
Template 標準實現
A. AbstractClass
抽象類:該類定義共有的子類抽象方法,並對外提供的一個統一調用的函數… 在這裡我們假設一個主題「通訊」,而通訊主要要做的事情則是
- 開啟、設置暫存器
openRegister
- 開始通訊
startComm
- 通訊傳輸
processComm
- 結束通訊
endComm
接著是設置統一通訊時要處理的步驟,將這個步驟放置到 comm
方法中
abstract class CommunicationSkeleton {
protected abstract fun openRegister()
protected abstract fun startComm()
protected abstract fun processComm(str: String)
protected abstract fun endComm()
// 對外統一調用的方法
fun comm(str: String) {
openRegister()
startComm()
processComm(str)
endComm()
}
}
在這裡統一對外調用的
comm
方法通常會添加final
將鎖定目的是為了鎖定調用的順序,不讓子類別去複寫調用順序的方法(這裡沒有特別使用
final
的原因是因為 Kotlin 這門語言預設方法就是final
)
B. ConcreteClass_1
、ConcreteClass_2
類:實做抽象方法;在這裡我們假設定義嵌入式常用的通訊,像是 SPI
、UART
… 等等通訊,由這些通訊來實作 CommunicationSkeleton
模板
ConcreteClass_1
對應的就是SPI
類ConcreteClass_2
對應的就是UART
類
// SPI 通訊實作 CommunicationSkeleton 模板
internal class SPI : CommunicationSkeleton() {
override fun openRegister() {
println("open SPI register")
}
override fun startComm() {
println("Start SPI")
}
override fun processComm(str: String) {
println("Tx: $str")
}
override fun endComm() {
println("End SPI\n")
}
}
// UART 通訊實作 CommunicationSkeleton 模板
internal class UART : CommunicationSkeleton() {
override fun openRegister() {
println("open UART register")
}
override fun startComm() {
println("Start UART")
}
override fun processComm(str: String) {
println("Tx: $str")
}
override fun endComm() {
println("Eed UART\n")
}
}
● 最後在完成模板設計模式的標準實作後,作為使用者,使用 Template
模板設計
fun main() {
fun startComm(cSkeleton: CommunicationSkeleton) {
cSkeleton.comm("Hello World")
}
startComm(SPI())
startComm(UART())
}
Android Source Template 模板設計
我們接下來進入 Android Source Code 中,並挑選幾個與 Template 模板設計最相近的實做來分析,看看 Android Source 是如何利用它來達到「微框架」的設計
以下分析的是 Android 10 的源碼
AsyncTask 微框架模板:單次使用的模板
● AsyncTask 類介紹:
它是 Android framework 層對外提供的 微框架模板,它可以讓開發者使用(早期滿多人使用的),它設計主執行序、背景執行序(線程)的切換,可以快速來建構一個異步請求,並響應在 UI 執行序上
// 最簡單的使用概念
class MyAsyncTask extends AsyncTask<Void, Void, String> {
private final TextView textView;
public MyAsyncTask(TextView textView) {
this.textView = textView;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
// 在執行後台任務之前,可以在這裡執行一些 UI 前置任務
textView.setText("執行中...");
}
@Override
protected String doInBackground(Void... voids) {
// 在後台執行長時間任務,例如網絡請求、數據庫操作等
// 這個方法不能直接更新 UI
return "後台任務完成";
}
@Override
protected void onPostExecute(String result) {
super.onPostExecute(result);
// 在後台任務完成後,可以在這裡更新UI
textView.setText(result);
}
}
這裡簡單做個介紹,但主要是在分析 AsyncTask 的 Template 設計概念
調用 MyAsyncTask
執行異步請求,並響應到 TextView
上
// 調用 AsyncTask 方式
MyAsyncTask myAsyncTask = new MyAsyncTask(myTextView);
// @ 分析 `execute`
myAsyncTask.execute();
分析 AsyncTask 實現:三階段模板
● 分析 AsyncTask
實現:這裡我們先直接揭露這個類的模板執行方法,如下圖(忽略細節,專注在 Template 模式出現的部份)
● AsyncTask 運行分析:
● AsyncTask
模板模式的第一階段,執行「onPreExecute
」
從 execute
函數開始,這個函數相當於模板模式下的「共用方法」,也就是啟動模板運作的開關;
// AsyncTask.java
private volatile Status mStatus = Status.PENDING;
// 標明該執行序應該要在 Main Thread 呼叫
@MainThread
public final AsyncTask<Params, Progress, Result> execute(Params... params) {
// @ 追蹤 executeOnExecutor 方法
return executeOnExecutor(sDefaultExecutor, params);
}
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
// 非 Padding 狀態,是會拋出異常的
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
mStatus = Status.RUNNING;
onPreExecute();
mWorker.mParams = params;
// @ 往下分析 mFuture
exec.execute(mFuture);
return this;
}
在這個方法中我們可以發現以下幾件事:
● AsyncTask 類使用了「狀態機」的設計,狀態機操作錯誤,就會導致異常的發生(其實也就是在提醒我們,這個類的用法錯了,必須修正)
● AsyncTask 狀態有分為 PENDING
、RUNNING
、FINISHED
,並且在運行 execute
函數時必須 PENDING
狀態,這同時也表明了 AsyncTask 類只支援單次執行(單次使用的模板)
也就是說 AsyncTask 物件不能重複使用,屬於一個拋棄式物件
// AsyncTask.java
public enum Status {
/**
* Indicates that the task has not been executed yet.
*/
PENDING,
/**
* Indicates that the task is running.
*/
RUNNING,
/**
* Indicates that {@link AsyncTask#onPostExecute} has finished.
*/
FINISHED,
}
● AsyncTask
模板模式的第二階段,執行「doInBackground
」:
從
sDefaultExecutor
執行的mFuture
開始分析…
A. sDefaultExecutor
靜態成員:
我們可以發現 sDefaultExecutor
是一個執行器(代表它可以執行某些任務),而任務的執行方案「預設採用順序式執行, ArrayDeque
」
而最終任務會交給線程池 ThreadPoolExecutor
執行(說明請看註解)
// AsyncTask.java
public static final Executor THREAD_POOL_EXECUTOR;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
// SynchronousQueue 的特色是一次只執行一個任務
new SynchronousQueue<Runnable>(), sThreadFactory);
// 拒絕策略設置(這邊就不分析了)
threadPoolExecutor.setRejectedExecutionHandler(sRunOnSerialPolicy);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();
private static class SerialExecutor implements Executor
// ArrayDeque 是 Java 中的雙向隊列
final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
Runnable mActive;
public synchronized void execute(final Runnable r) {
// 向隊列中添加 Runnalbe 任務
mTasks.offer(new Runnable() { // 最終線程池執行起來後,會運行這個任務
public void run() {
try {
// 執行任務,也就是我們傳入的 mTask
r.run();
} finally {
// 運行完後,執行下一個任務
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
protected synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
THREAD_POOL_EXECUTOR.execute(mActive);
}
}
}
B. mFuture
成員:
mFuture 成員是在建構函數時就被賦予值,以下的程式看似雜亂… 但其實在這裡的 重點是,它會把執行序(線程)切換到背景,並去執行模板中的 doInBackground
方法
// AsyncTask.java
private final FutureTask<Result> mFuture;
// 原子操作
private final AtomicBoolean mTaskInvoked = new AtomicBoolean();
public AsyncTask(@Nullable Looper callbackLooper) {
mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper()
? getMainHandler()
: new Handler(callbackLooper);
mWorker = new WorkerRunnable<Params, Result>() {
public Result call() throws Exception {
// 設定原子操作,設定為已調用
mTaskInvoked.set(true);
// 執行結果
Result result = null;
try {
// 將線程的優先級設置為 background 優先級
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
//noinspection unchecked
result = doInBackground(mParams);
... 省略部份
} catch (Throwable tr) {
mCancelled.set(true);
throw tr;
} finally {
// 將執行結果傳入 postResult
postResult(result);
}
return result;
}
};
mFuture = new FutureTask<Result>(mWorker) {
@Override
protected void done() {
try {
// 取得執行結果,並傳入 postResultIfNotInvoked 函數
postResultIfNotInvoked(get());
}
... 省略 catch
}
};
}
這裡的 postResult
函數、postResultIfNotInvoked
函數,兩者其實都是做同一件事,就是「將結果丟到 Handler
中」,準備回傳結果! 這裡我們直接看 postResult
函數,可以看到它將結果包裝到 AsyncTaskResult
類中,並丟入 Handler 開始傳送結果
// AsyncTask.java
private static final int MESSAGE_POST_RESULT = 0x1;
private Result postResult(Result result) {
@SuppressWarnings("unchecked")
Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
new AsyncTaskResult<Result>(this, result));
message.sendToTarget();
return result;
}
private static class AsyncTaskResult<Data> {
final AsyncTask mTask;
// 透過 Array 儲存結果
final Data[] mData;
AsyncTaskResult(AsyncTask task, Data... data) {
mTask = task;
mData = data;
}
}
● AsyncTask
模板模式的第三階段,執行「onCancelled
」或是「onPostExecute
」:
當 Android 的訊息消費機制執行到當前任務時,就會執行 MESSAGE_POST_RESULT
訊息,並將攜帶的物件(AsyncTaskResult
)取出… 最終會執行到 AsyncTask
方法的 finish
方法
這時就運行了模板方法的「onCancelled
」或是「onPostExecute
」方法,並將狀態機設置為 FINISHED
// AsyncTask.java
private static final int MESSAGE_POST_RESULT = 0x1;
private static class InternalHandler extends Handler {
public InternalHandler(Looper looper) {
super(looper);
}
@SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
@Override
public void handleMessage(Message msg) {
AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
switch (msg.what) {
case MESSAGE_POST_RESULT:
// There is only one result
result.mTask.finish(result.mData[0]);
break;
... 省略其他 case
}
}
}
private void finish(Result result) {
if (isCancelled()) {
onCancelled(result);
} else {
onPostExecute(result);
}
mStatus = Status.FINISHED;
}
更多的物件導向設計
物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!
創建模式 Creation Patterns
● 創建模式 PK
● 創建模式 - Creation Patterns
:
創建模式用於「物件的創建」,它關注於如何更靈活、更有效地創建物件。這些模式可以隱藏創建物件的細節,並提供創建物件的機制,例如單例模式、工廠模式… 等等,詳細解說請點擊以下連結
● Singleton 單例模式 | 解說實現 | Android Framework Context Service
● Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer
● Factory 工廠方法模式 | 解說實現 | Java 集合設計
● Builder 建構者模式 | 實現與解說 | Android Framwrok Dialog 視窗
● Clone 原型模式 | 解說實現 | Android Framework Intent
行為模式 Behavioral Patterns
● 行為模式 PK
● 行為模式 - Behavioral Patterns
:
行為模式關注物件之間的「通信」和「職責分配」。它們描述了一系列物件如何協作,以完成特定任務。這些模式專注於改進物件之間的通信,從而提高系統的靈活性。例如,策略模式、觀察者模式… 等等,詳細解說請點擊以下連結
● Stragety 策略模式 | 解說實現 | Android Framework 動畫
● Interpreter 解譯器模式 | 解說實現 | Android Framework PackageManagerService
● Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞
● Specification 規格模式 | 解說實現 | Query 語句實做
● Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計
● Memo 備忘錄模式 | 實現與解說 | Android Framwrok Activity 保存
● Visitor 設計模式 | 實現與解說 | 物件導向設計
● Template 設計模式 | 實現與解說 | 物件導向設計
● Mediator 模式設計 | 實現與解說 | 物件導向設計
● Composite 組合模式 | 實現與解說 | 物件導向設計
● Observer 觀察者模式 | JDK Observer | Android Framework Listview
結構模式 Structural Patterns
● 結構模式 PK
● 結構模式 - Structural Patterns
:
結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結
● Decorate 裝飾模式 | 解說實現 | 物件導向設計
● Iterator 迭代設計 | 解說實現 | 物件導向設計