源碼中的設計模式--模板方法模式

本文要解決的幾個問題,

1、什麼是模板方法模式;

2、模板方法模式的使用場景;

3、模板方法模式的優點;

4、源碼中有哪些地方使用到了模板方法模式;

帶着這幾個問題,我們開始今天的設計模式的分享。

一、模式入場

  大家在日常的工作生活中肯定碰到過這樣的場景,比如,你要轉正答辯了,總要有個PPT吧,這時你是不是會問你同事要個述職的PPT模板,有個模板的好處這裏自不用說。你去幫助單位去投標拿項目了,你是不是要問甲方爸爸要個模板,按照模板準備你的材料。生活中這樣的例子太多了,有模板好辦事。

  在平時的開發過程中,不知道你是否碰到過類似的情形,你要調用系統A和系統B的接口,把系統A和系統B的數據讀取過來,經過處理存儲到自己的數據庫裏。

  針對這樣的場景你要怎麼設計吶,首先,針對這樣一個場景進行分析,要明確的是需要調用兩個系統的接口,這兩個系統返回的數據是不一樣的,並且要存儲到不同的表中,下面先試圖實現下這個場景。有兩個類SyncSystemA和SyncSystemB分別表示處理系統A和系統B的接口數據,

SyncSystemA.java

package com.example.template;

public class SyncSystemA {
    public void syncData(){
        //1、組裝參數
        String url="http://a.com/query";
        String param="A";
        //2、發送請求
        String result=sendRequest(url,param);
        //3、解析
        String result2=parse(result);
        //4、保存數據
        saveData(result2);
    }
    private String sendRequest(String url,String param){
        System.out.println("發送請求到A");
        return "";
    }
    private String parse(String result){
        System.out.println("對A返回結果進行解析");
        return "";
    }
    private void saveData(String result){
        System.out.println("保存A的數據");
    }
}

SyncSystemB.java

package com.example.template;

public class SyncSystemB {
    public void syncData(){
        //1、組裝參數
        String url="http://b.com/query";
        String param="A";
        //2、發送請求
        String result=sendRequest(url,param);
        //3、解析
        String result2=parse(result);
        //4、保存數據
        saveData(result2);
        
    }
    private String sendRequest(String url,String param){
        System.out.println("發送請求到B");
        return "";
    }
    private String parse(String result){
        System.out.println("對B的返回結果進行解析");
        return "";
    }
    private void saveData(String result){
        System.out.println("保存B的數據");
    }
}

下面看測試方法,Test.java

package com.example.template;

public class Test {
    public static void main(String[] args) {
        SyncSystemA syncSystemA=new SyncSystemA();
        SyncSystemB syncSystemB=new SyncSystemB();
        syncSystemA.syncData();
        System.out.println("-----------");
        syncSystemB.syncData();
    }
}

返回結果如下,

發送請求到A
對A返回結果進行解析
保存A的數據
-----------
發送請求到B
對B的返回結果進行解析
保存B的數據

Process finished with exit code 0

可以看到很好的完成了我們的目標,那就是同步系統A和系統B的數據。但是從上面的代碼中也能發現一些問題,在SyncSystemA和SyncSystemB中有很多的重複代碼,追求極簡的我們怎麼能容忍這樣的代碼。

二、深入模板方法模式

上面的處理步驟其實可以歸納爲下面的流程,如下圖,

我們把這樣一個過程抽象出了這樣幾步:組裝參數、發送請求、解析參數、保存數據,在這樣幾步中組裝參數和解析參數肯定是不同的,對於發送請求和保存數據我們可以把它們處理成一致的。既然有一樣的處理步驟,爲了減少重複的代碼,我們可以進行優化,把公共的部分抽取出來,那麼如何才能實現這樣的目的,可以把公共的部分封裝到工具類中,在不同的地方進行調用,但這些方法又不能算的上是工具類。還有一個方法在java基礎中有抽象類的概念,今天就使用下抽象類,那麼如何設計抽象類,下面看,

AbstractSyncData.java

package com.example.template;

import java.util.Map;

public abstract class AbstractSyncData {
    //定義好同步數據的步驟
    public void syncData() {
        //1、組裝參數
        Map param = assembleParam();
        //2、發送請求
        String result = sendRequest(param);
        //3、解析
        String result2 = parse(result);
        //4、保存數據
        saveData(result2);
    }

    //1、組裝參數,供子類實現自己的邏輯
    protected abstract Map assembleParam();

    //2、發送請求
    private String sendRequest(Map map) {

        //實際發送請求,並把數據返回
        System.out.println("發送請求");
        return "";
    }

    //3、解析返回結果,供子類實現自己的邏輯
    protected abstract String parse(String result);

    //4、保存數據
    private void saveData(String result) {
        System.out.println("保存數據");
    }
}

從上面的AbstractSyncData抽象類中,可以看到把syncData放到了抽象類中,並且在該類中定義了完成此功能的步驟:組裝參數、發送請求、解析返回結果、保存數據,其中組裝參數、解析返回結果兩步在抽象類中定義了抽象方法,定義抽象方法的目的是爲了讓自己去實現自己的邏輯,看下兩個子類的實現,

SyncSystemAImpl.java

package com.example.template;

import java.util.HashMap;
import java.util.Map;

public class SyncSystemAImpl extends AbstractSyncData{
    @Override
    protected Map assembleParam() {
        System.out.println("組裝發送到系統A的參數");
        return new HashMap();
    }

    @Override
    protected String parse(String result) {
        System.out.println("解析系統A的返回結果");
        return "";
    }
}

SyncSystemBImpl.java

package com.example.template;

import java.util.HashMap;
import java.util.Map;

public class SyncSystemBImpl extends AbstractSyncData{
    @Override
    protected Map assembleParam() {
        System.out.println("組裝發送到系統B的參數");
        return new HashMap();
    }

    @Override
    protected String parse(String result) {
        System.out.println("解析系統B的返回結果");
        return "";
    }
}

看下測試結果

組裝發送到系統A的參數
發送請求
解析系統A的返回結果
保存數據
-----------
組裝發送到系統B的參數
發送請求
解析系統B的返回結果
保存數據

Process finished with exit code 0

看到上面的結果同樣實現了功能,而且從代碼風格上是不是更簡潔,而且使用到了模板方法模式。

看下《Head First 設計模式》一書中給模板方法模式下的定義

模板方法模式在一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結構的情況下,重新定義算法中的某些步驟

上面的釋義定義的太完美了,多讀幾遍上面的釋義和我們上面的AbstractSyncData類對比下

算法的骨架對應syncData方法

一些步驟延遲到子類對應assembleParam和parse方法

重新定義算法中的某些步驟對應assembleParam和parse方法,因爲針對不同的實現有不同的處理邏輯。

模板方法的使用場景上面已經提到過,在開發中要善於抽象,把一個場景中的步驟抽象成不同的幾步,如果有多種實現,那麼此時便是使用模板方法的大好時機。

番外

想多說一句的是,現在不是都談面向接口編程,那麼針對面向接口編程我們要如何改造上面的模板方法模式吶,只需要把syncData放到接口中即可,

package com.example.template;

public interface SyncData {
    //同步數據
    void syncData();
}

相應的抽象類實現該接口即可,

其UML圖如下,

三、追尋源碼

上面已經系統的學習了模板方法模式,下面看下在源碼中的使用,

1、mybatis的BaseExecutor

在mybatis的BaseExecutor類中有update方法,

該方法來自於接口Executor,該方法又調用了doUpdate方法,該方法在BaseExecutor中是抽象方法,

看下實現的子類,

和我們上面的例子是不是很像,或者說就是同一個,再看下其uml

2、spring的AbstractApplicationContext

在spring的AbstractApplicaitonContext類中有fresh()方法,該方法中調用了obtainFreshBeanFactory方法,

obtainFreshBeanFactory方法,

看下這兩個方法,

這兩個方法是抽象的,肯定也是模板方法了。

四、總結

  模板方法模式的精髓在於抽象,抽象出完成某個功能的步驟,再把個性化的步驟做爲抽象方法,讓子類延遲實現,公有的方法在抽象類中完成。在使用模板方法時由於存在抽象類,會出現多個繼承子類的情況,需要視情況而定。另外,模板方法模式可以結合接口使用,實現面向接口編程。

首發於:https://www.toutiao.com/article/7097584508639183367/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章