---
title: 'Template 模板設計模式'
disqus: kyleAlien
---
Template 模板設計模式
===
## Overview of Content
如有引用參考請詳註出處,感謝 :smile:
> 定義模板,**固定演算法順序**,讓繼承**子類實現詳細細節**
:::success
* 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/)
本篇文章對應的是 [**Template 設計模式 | 實現與解說 | Android source AsyncTask 微框架**](https://devtechascendancy.com/object-oriented_design_template/)
:::
[TOC]
## 使用場景
* 多個子類有共有方法,並有相同的處理邏輯
* 當需要固定算法順序時可以使用,封裝其固定流程
* 多子類有共同方法,並且邏輯順序固定
* **固定核心算法**,當算法出現變動時可以一起更改,而**細節可以分別由子類決定**
* 實現**反向控制** (子類決定是否實現該方法)
### Template 定義 & UML
* `Template` 定義:
定義一個操作算法的框架(skeleton),由子類實現方法,並且 **子類不得改變算法的特定步驟**
* `Template` UML 角色
| 類角色 | 說明 |
| - | - |
| `AbstractClass`(抽象類) | 操作一系列的基本抽象方法來達到目的 |
| `ConcreteClass_1`、`ConcreteClass_2` | 1 ~ 到多個實做類,個別實現機類方法 |
> 
:::warning
* 為了防止惡意操作,共同方法通常使用 **final 修飾**,**可防止該方法在繼承時被覆寫**,但對於單元測試會有障礙 !
> 當然你可以選用 **不限制的單元測試框架**
:::
### Template 優缺點
* `Template` 設計模式優點 :
* 集結固定不變的行為,將細節由子類決定
> 封裝不變部份、拓展可變部份
* 子類實行細節,有助於多種類演算法,方便維護
> 父類控制行為,子類控制方法
* `Template` 設計模式缺點 :
* 由於對於設計的重用,所以使用者用起來會更加抽象,需要使用者詳細了解模板設計的架構手冊(`Template` 模式也稱為 **微框架模式**)
* `Template` 拓展出的子類別會因而變多,對於使用者要全面了解會更有負擔
## Template 實現
### Template 標準
1. **`AbstractClass` 抽象類**:該類定義共有的子類抽象方法,並對外提供的一個統一調用的函數… 在這裡我們假設一個主題「通訊」,而通訊主要要做的事情則是
* 開啟、設置暫存器 `openRegister`
* 開始通訊 `startComm`
* 通訊傳輸 `processComm`
* 結束通訊 `endComm`
接著是設置統一通訊時要處理的步驟,將這個步驟放置到 `comm` 方法中
```kotlin=
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()
}
}
```
2. **`ConcreteClass_1`、`ConcreteClass_2` 類**:實做抽象方法;在這裡我們假設定義嵌入式常用的通訊,像是 `SPI`、`UART`... 等等通訊,由這些通訊來實作 `CommunicationSkeleton` 模板
* `ConcreteClass_1` 對應的就是 `SPI` 類
* `ConcreteClass_2` 對應的就是 `UART` 類
```kotlin=
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("Eed SPI\n")
}
}
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")
}
}
```
:::warning
* **在這裡統一對外調用的 comm 方法通常會添加 `final` 將鎖定**
目的是為了鎖定調用的順序,不讓子類別去複寫調用順序的方法(這裡沒有特別使用 `final` 的原因是因為 Kotlin 這門語言預設方法就是 `final`)
:::
* 作為使用者,使用 `Template` 模板設計
```kotlin=
fun main() {
fun startComm(cSkeleton: CommunicationSkeleton) {
cSkeleton.comm("Hello World")
}
startComm(SPI())
startComm(UART())
}
```
> 
## Android Source Template 模板設計
我們接下來進入 Android Source Code 中,並挑選幾個與 Template 模板設計最相近的實做來分析,看看 Android Source 是如何利用它來達到「微框架」的設計
:::success
以下分析的是 [**Android 10**](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r40:) 的源碼
:::
### [AsyncTask](frameworks/base/core/java/android/os/AsyncTask.java) 微框架模板:單次使用的模板
* [**AsyncTask**](frameworks/base/core/java/android/os/AsyncTask.java) **類介紹**:
它是 Android framework 層對外提供的 **微框架模板**,它可以讓開發者使用(早期滿多人使用的),它設計主執行序、背景執行序(線程)的切換,可以快速來建構一個異步請求,並響應在 UI 執行序上
> 這裡簡單做個介紹,但主要是在分析 AsyncTask 的 Template 設計概念
```java=
// 最簡單的使用概念
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);
}
}
```
調用 `MyAsyncTask` 執行異步請求,並響應到 `TextView` 上
```java=
// 調用 AsyncTask 方式
MyAsyncTask myAsyncTask = new MyAsyncTask(myTextView);
// @ 分析 `execute`
myAsyncTask.execute();
```
### 分析 [AsyncTask](frameworks/base/core/java/android/os/AsyncTask.java) 實現:三階段模板
* **分析 `AsyncTask` 實現**:這裡我們先直接揭露這個類的模板執行方法,如下圖(忽略細節,專注在 Template 模式出現的部份)
```mermaid
graph TB
subgraph AsyncTask
execute --> | 運行模板 | onPreExecute
subgraph 模板區塊
onPreExecute --> doInBackground --> onPostExecute
doInBackground --> onCancelled
end
end
```
* **AsyncTask 運行分析**:
* **`AsyncTask`模板模式的第一階段,執行「`onPreExecute`」**
從 `execute` 函數開始,這個函數相當於模板模式下的「共用方法」,也就是啟動模板運作的開關;
```java=
// 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 物件不能重複使用,屬於一個拋棄式物件
```java=
// 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`** 開始分析…
1. **`sDefaultExecutor` 靜態成員**:
我們可以發現 `sDefaultExecutor` 是一個執行器(代表它可以執行某些任務),而任務的執行方案「**預設採用順序式執行**, `ArrayDeque`」
**而最終任務會交給線程池 `ThreadPoolExecutor` 執行**(說明請看註解)
```mermaid
graph LR;
mTask --> |添加, offer| ArrayDeque --> |poll| 任務
程池執行 --> |運行| 任務
```
```java=
// 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);
}
}
}
```
2. **`mFuture` 成員**:
mFuture 成員是在建構函數時就被賦予值,以下的程式看似雜亂… 但其實在這裡的 **重點是,它會把執行序(線程)切換到背景,並去執行模板中的 `doInBackground` 方法**
```java=
// 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 開始傳送結果
```java=
// 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`
```java=
// 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;
}
```
## 更多的物件導向設計
物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!
:::info
* [**設計建模 2 大概念- UML 分類、使用**](https://devtechascendancy.com/introduction-to-uml-and-diagrams/)
* [**物件導向設計原則 – 6 大原則(一)**](https://devtechascendancy.com/object-oriented-design-principles_1/)
* [**物件導向設計原則 – 6 大原則(二)**](https://devtechascendancy.com/object-oriented-design-principles_2/)
:::
### 創建模式 Creation Patterns
* [**創建模式 PK**](https://devtechascendancy.com/pk-design-patterns-factory-builder-best/)
* **創建模式 - `Creation Patterns`**:
創建模式用於「**物件的創建**」,它關注於如何更靈活、更有效地創建對象。這些模式可以隱藏創建對象的細節,並提供創建對象的機制,例如單例模式、工廠模式… 等等,詳細解說請點擊以下連結
:::success
* [**Singleton 單例模式 | 解說實現 | Android Framework Context Service**](https://devtechascendancy.com/object-oriented_design_singleton/)
* [**Abstract Factory 設計模式 | 實現解說 | Android MediaPlayer**](https://devtechascendancy.com/object-oriented_design_abstract-factory/)
* [**Factory 工廠方法模式 | 解說實現 | Java 集合設計**](https://devtechascendancy.com/object-oriented_design_factory_framework/)
* [**Builder 建構者模式 | 實現與解說 | Android Framwrok Dialog 視窗**](https://devtechascendancy.com/object-oriented_design_builder_dialog/)
* [**Clone 原型模式 | 解說實現 | Android Framework Intent**](https://devtechascendancy.com/object-oriented_design_clone_framework/)
* [**Object Pool 設計模式 | 實現與解說 | 利用 JVM**](https://devtechascendancy.com/object-oriented_design_object-pool/)
* [**Flyweight 享元模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_flyweight/)
:::
### 行為模式 Behavioral Patterns
* [**行為模式 PK**](https://devtechascendancy.com/pk-design-patterns-cmd-strat-state-obs-chain/)
* **行為模式 - `Behavioral Patterns`**:
行為模式關注物件之間的「**通信**」和「**職責分配**」。它們描述了一系列對象如何協作,以完成特定任務。這些模式專注於改進物件之間的通信,從而提高系統的靈活性。例如,策略模式、觀察者模式… 等等,詳細解說請點擊以下連結
:::warning
* [**Stragety 策略模式 | 解說實現 | Android Framework 動畫**](https://devtechascendancy.com/object-oriented_design_stragety_framework/)
* [**Interpreter 解譯器模式 | 解說實現 | Android Framework PackageManagerService**](https://devtechascendancy.com/object-oriented_design_interpreter_framework/)
* [**Chain 責任鏈模式 | 解說實現 | Android Framework View 事件傳遞**](https://devtechascendancy.com/object-oriented_design_chain_framework/)
* [**State 狀態模式 | 實現解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_state/)
* [**Specification 規格模式 | 解說實現 | Query 語句實做**](https://devtechascendancy.com/object-oriented_design_specification-query/)
* [**Command 命令、Servant 雇工模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_command_servant/)
* [**Memo 備忘錄模式 | 實現與解說 | Android Framwrok Activity 保存**](https://devtechascendancy.com/object-oriented_design_memo_framework/)
* [**Visitor 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_visitor_dispatch/)
* [**Template 設計模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_template/)
* [**Mediator 模式設計 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_mediator/)
* [**Composite 組合模式 | 實現與解說 | 物件導向設計**](https://devtechascendancy.com/object-oriented_programming_composite/)
:::
### 結構模式 Structural Patterns
* [**結構模式 PK**](https://devtechascendancy.com/pk-design-patterns-proxy-decorate-adapter/)
* **結構模式 - `Structural Patterns`**:
結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結
:::danger
* [**Bridge 橋接模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_bridge/)
* [**Decorate 裝飾模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_decorate/)
* [**Proxy 代理模式 | 解說實現 | 分析動態代理**](https://devtechascendancy.com/object-oriented_design_proxy_dynamic-proxy/)
* [**Iterator 迭代設計 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_iterator/)
* [**Facade 外觀、門面模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_facade/)
* [**Adapter 設計模式 | 解說實現 | 物件導向設計**](https://devtechascendancy.com/object-oriented_design_adapter/)
:::
## Appendix & FAQ
:::info
:::
###### tags: `Java 設計模式` `基礎進階`