--- 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 ~ 到多個實做類,個別實現機類方法 | > ![](https://i.imgur.com/dboSudb.png) :::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()) } ``` > ![](https://i.imgur.com/tjuXBtx.png) ## 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 設計模式` `基礎進階`