Template 設計模式 | 實現與解說 | Android source AsyncTask 微框架

Template 設計模式 | 實現與解說 | Android source AsyncTask 微框架

Overview of Content

在這篇文章中,我們將探討 Template 模板設計模式,這是一種行為型設計模式,用於定義一個演算法的骨架,但將一些步驟推遲到子類別中。這使得子類別可以重新定義某些步驟,而不改變演算法的結構。

我們將從概念性的角度介紹 Template 模式的使用場景,討論其定義與 UML 圖表示。接著,我們將分析 Template 模式的優缺點,以便讀者全面了解使用這種設計模式的利弊。

並且我們將提供 Template 模式的實際實現示例,並介紹一個稱為 "Template 標準" 的主題,以簡單的概念進入這個設計模式的理解。

最後,我們在分析 Android 源碼中的 AsyncTask 的實現,加深讀者對 Template 模式的深入理解,並觀察實際的應用!

透過本文,您將獲得 Template 模板設計模式的深入見解,以及在實際應用中如何充分利用它的知識。讓我們一同探索這個強大而靈活的設計模式吧!

寫文章分享不易,如有引用參考請詳註出處,如有指導、意見歡迎留言(如果覺得寫得好也請給我一些支持),感謝 😀


Template 使用場景

● 多個子類有共有方法,並有相同的處理邏輯

● 當需要固定算法順序時可以使用

● 封裝其固定流程 多子類有共同方法,並且邏輯順序固定

● 固定核心算法,當算法出現變動時可以一起更改,而細節可以分別由子類決定

● 實現反向控制 (子類決定是否實現該方法)

定義模板,固定演算法順序,讓繼承 子類實現詳細細節

Template 定義 & Template UML

● Template 定義:
定義一個操作算法的框架(skeleton),由子類實現方法,並且 子類不得改變算法的特定步驟

●Template UML 角色

類角色說明
AbstractClass(抽象類)操作一系列的基本抽象方法來達到目的
ConcreteClass_1ConcreteClass_21 ~ 到多個實做類,個別實現機類方法,其本身不會影響到模板,但它們是模板的實作細節

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_1ConcreteClass_2:實做抽象方法;在這裡我們假設定義嵌入式常用的通訊,像是 SPIUART… 等等通訊,由這些通訊來實作 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 模式出現的部份)

graph TB subgraph AsyncTask execute --> | 運行模板 | onPreExecute subgraph 模板區塊 onPreExecute --> doInBackground --> onPostExecute doInBackground --> onCancelled end end

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 狀態有分為 PENDINGRUNNINGFINISHED,並且在運行 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 執行(說明請看註解)

graph LR; mTask --> |添加, offer| ArrayDeque --> |poll| 任務 程池執行 --> |運行| 任務

// 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;
}

更多的物件導向設計

物件導向的設計基礎如下,如果是初學者或是不熟悉的各位,建議可以從這些基礎開始認識,打好基底才能走個更穩(在學習的時候也需要不斷回頭看)!

設計建模 2 大概念- UML 分類、使用

物件導向設計原則 – 6 大原則(一)

物件導向設計原則 – 6 大原則(二)

創建、行為、結構型設計 8 個比較 | 包裝模式 | 最佳實踐

創建模式 Creation Patterns

創建模式 PK

創建模式 - Creation Patterns

結構模式 Structural Patterns

結構模式 PK

結構模式 - Structural Patterns

結構模式專注於「物件之間的組成」,以形成更大的結構。這些模式可以幫助你確保當系統進行擴展或修改時,不會破壞其整體結構。例如,外觀模式、代理模式… 等等,詳細解說請點擊以下連結

Bridge 橋接模式 | 解說實現 | 物件導向設計

Decorate 裝飾模式 | 解說實現 | 物件導向設計

Proxy 代理模式 | 解說實現 | 分析動態代理

Iterator 迭代設計 | 解說實現 | 物件導向設計

Facade 外觀、門面模式 | 解說實現 | 物件導向設計

Adapter 設計模式 | 解說實現 | 物件導向設計


Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

發表迴響