做一個不複製粘貼的程式員[1]: 使用模板方法模式(1)- 分頁查詢實例

来源:https://www.cnblogs.com/thinkam/archive/2019/07/28/11260726.html
-Advertisement-
Play Games

對於重覆的代碼,如果是重覆的字元串,我們會想到提出一個變數。如果是重覆的代碼塊,我們會想到提取出一個方法。 但如果這重覆的代碼塊中有一處或幾處是會變化的,那麼就沒那麼容易提取出一個方法。說起來有點抽象,下麵看一個例子。 一、分頁查詢 寫過CRUD的同學肯定寫過很多分頁查詢,分頁查詢的主要步驟是先校驗 ...


對於重覆的代碼,如果是重覆的字元串,我們會想到提出一個變數。如果是重覆的代碼塊,我們會想到提取出一個方法。

但如果這重覆的代碼塊中有一處或幾處是會變化的,那麼就沒那麼容易提取出一個方法。說起來有點抽象,下麵看一個例子。

一、分頁查詢

寫過CRUD的同學肯定寫過很多分頁查詢,分頁查詢的主要步驟是先校驗前端傳過來的查詢條件(包括哪一頁以及每頁顯示記錄數等),如果不合法則設置預設值。然後根據條件查詢符合條件的總記錄數,以及查詢符合條件的一頁記錄,最後經過處理返回給前端。

這一段話中會變的部分,也就是每個分頁查詢不同的部分,就是兩個查詢。這不就是一個模板,對於不同的分頁查詢,只要往其中填兩塊代碼。

不過有的同學可能會說平時用分頁插件,不需要寫查詢總數的方法,不過有時還是需要自己寫查詢總數的方法來優化SQL滴。

二、Java8之前的方式

下麵是一些核心類

/**
 * 分頁查詢結果對象
 *
 * @param <T> 分頁查詢對象類型
 */
@AllArgsConstructor
@Getter
@ToString
public final class Page<T> implements Serializable {
    /**
     * 總記錄數
     */
    @NonNull
    private final Long total;
    /**
     * 當前記錄集合
     */
    @NonNull
    private final List<T> rows;
}
/**
 * 分頁查詢模板(Java8之前的寫法)
 */
public abstract class AbstractPageTemplate<E> {
    /**
     * "pageNumber"
     */
    public static final String PAGE_NUMBER = "pageNumber";
    /**
     * "pageSize"
     */
    public static final String PAGE_SIZE = "pageBegin";
    /**
     * "pageBegin"
     */
    private static final String PAGE_BEGIN = "pageBegin";

    /**
     * 獲取分頁結果對象
     * <p>
     * 示例:
     *
     * <pre>
     * {@code
     *      Page<FooDTO> fooDTOPage = PageUtil.page(mapper::selectPageCount1, mapper::selectPageEntities1, paramMap)
     * }
     * </pre>
     *
     * @param paramMap 分頁查詢參數(key需要包含”pageNumber“和”pageSize“,否則預設查詢第一頁的20條記錄)
     * @return 分頁結果對象集合
     */
    public Page<E> page(Map<String, Object> paramMap) {
        Objects.requireNonNull(paramMap);
        // 獲取頁數
        Integer pageNumber = (Integer) paramMap.get(PAGE_NUMBER);
        // 校驗頁數,不合法則設置預設值
        pageNumber = pageNumber == null || pageNumber <= 0 ? 1 : pageNumber;
        // 獲取頁大小,不合法設置預設值
        Integer pageSize = (Integer) paramMap.computeIfAbsent(PAGE_SIZE, k -> 20);
        // 計算SQL中limit的offset(Mysql)
        paramMap.put(PAGE_BEGIN, (pageNumber - 1) * pageSize);
        // 查詢符合條件的總記錄數
        long total = pageCount(paramMap);
        if (total <= 0) {
            return new Page<>(0L, new ArrayList<>());
        } else {
            // 查詢符合條件的一頁記錄
            return new Page<>(total, pageList(paramMap));
        }
    }

    /**
     * 查詢符合條件的總記錄數
     *
     * @param paramMap 分頁查詢參數(key需要包含”pageNumber“和”pageSize“,否則預設查詢第一頁的20條記錄)
     * @return 總記錄數
     */
    abstract long pageCount(Map<String, Object> paramMap);

    /**
     * 查詢符合條件的所有記錄
     *
     * @param paramMap 分頁查詢參數(key需要包含”pageNumber“和”pageSize“,否則預設查詢第一頁的20條記錄)
     * @return 分頁結果集合
     */
    abstract List<E> pageList(Map<String, Object> paramMap);
}

下麵是一些為demo準備的類

@Data
public class User {
}
public class UserDAO  {
    public long pageCount(Map<String, Object> paramMap) {
        // select count(*) from user where ...
        return 0;
    }

    public List<User> pageList(Map<String, Object> paramMap) {
        // select * from user where ... limit pageBegin, pageSize
        return new ArrayList<>();
    }
}
public class UserService extends AbstractPageTemplate<User> {
    private UserDAO userDAO = new UserDAO();

    @Override
    public long pageCount(Map<String, Object> paramMap) {
        return userDAO.pageCount(paramMap);
    }

    @Override
    public List<User> pageList(Map<String, Object> paramMap) {
        return userDAO.pageList(paramMap);
    }
}

下麵是demo

UserService userService = new UserService();

Page<User> userPage = userService.page(ImmutableMap.of(
                PageUtil.PAGE_NUMBER, 1,
                PageUtil.PAGE_SIZE, 20
                // 其他參數...
        ));

分析下上面這種傳統的模板方法模式,我們把樣板式的代碼寫到AbstractPageTemplate#page()方法中,當我們要寫新的分頁查詢時,只要繼承AbstractPageTemplate類,然後實現其中的兩個方法,就可以很方便的獲取到分頁結果。

如果沒有想到模板方法模式,項目中肯定會有大量的類似於AbstractPageTemplate#page()方法中的代碼,而且每個人寫的可能會不一樣,如果後面要修改預設的每頁大小,要找到所有的這些分頁代碼不是很容易,難免會有遺漏。模板方法模式為後期的重構、擴展提供了便利。

這種傳統的方式寫起來有點麻煩,一個模塊有幾個分頁查詢就要寫幾個Service類,去繼承AbstractPageTemplate類。一個模塊有多個service好像有點不合理,如果能把AbstractPageTemplate#pageCount方法和AbstractPageTemplate#pageList方法作為方法參數傳入AbstractPageTemplate#page()方法中,那麼就方便多了,不用再去寫那麼多Service類了。還好Java8之後有了lambda表達式,我們就可以把方法作為方法的參數。

三、Java8的方式

下麵是核心類

/**
 * 分頁查詢工具類
 */
public class PageUtil {
    /**
     * "pageNumber"
     */
    public static final String PAGE_NUMBER = "pageNumber";
    /**
     * "pageSize"
     */
    public static final String PAGE_SIZE = "pageSize";

    /**
     * "pageBegin"
     */
    private static final String PAGE_BEGIN = "pageBegin";

    private PageUtil() {
    }

    /**
     * 獲取分頁結果對象
     * <p>
     * 示例:
     *
     * <pre>
     * {@code
     *      Page<FooDTO> fooDTOPage = PageUtil.page(mapper::selectPageCount1, mapper::selectPageEntities1, paramMap)
     * }
     * </pre>
     *
     * @param pageCountFunction     查詢分頁總數的方法(參數類型:{@code Map<String, Object>};返回值類型:{@code int})
     * @param pageQueryListFunction 查詢分頁記錄的方法(參數類型:{@code Map<String, Object>};;返回值類型:{@code List<E>})
     * @param paramMap              分頁查詢參數(key需要包含”pageNumber“和”pageSize“,否則預設查詢第一頁的20條記錄)
     * @return 分頁結果對象集合
     */
    public static <E> Page<E> page(ToLongFunction<Map<String, Object>> pageCountFunction,
                                   Function<Map<String, Object>, List<E>> pageQueryListFunction,
                                   Map<String, Object> paramMap) {
        Objects.requireNonNull(pageCountFunction);
        Objects.requireNonNull(pageQueryListFunction);
        Objects.requireNonNull(paramMap);
        Integer pageNumber = (Integer) paramMap.get(PAGE_NUMBER);
        pageNumber = pageNumber == null || pageNumber <= 0 ? 1 : pageNumber;
        Integer pageSize = (Integer) paramMap.computeIfAbsent(PAGE_SIZE, k -> 20);
        paramMap.put(PAGE_BEGIN, (pageNumber - 1) * pageSize);
        long total = pageCountFunction.applyAsLong(paramMap);
        if (total <= 0) {
            return new Page<>(0L, new ArrayList<>());
        } else {
            return new Page<>(total, pageQueryListFunction.apply(paramMap));
        }
    }
}

下麵是demo

 Page<User> userPage = PageUtil.page(userService::pageCount, userService::pageList, ImmutableMap.of(
                PageUtil.PAGE_NUMBER, 1,
                PageUtil.PAGE_SIZE, 20
                // 其他參數...
        ));

分析下Java8以後的寫法,對於一個模塊有多個分頁查詢的情況,我們只要在Service中定義多個“查詢符合條件的總記錄數”和“查詢符合條件的所有記錄”的方法,然後在PageUtil#page方法中傳入方法引用。

概括來說,Java8以後我們就可以把方法作為方法的參數傳入方法中,從而省去了寫很多類去繼承抽象的模板類的麻煩。

四、模板方法模式

模板方法模式是一個在我們平時寫代碼中經常用到的模式,可以幫我們少寫很多重覆的代碼,從而提高開發效率。在一些框架中也會經常看到,比如Spring的JdbcTemplate。GOF給模板方法模式下過以下定義:

定義一個操作中的演算法骨架,而將一些步驟遲到子類中。模板方法使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。

如果你不明白這個定義也無所謂,只要你看懂了上面那個Java8的例子就可以了。在我看來,模板方法模式就是把方法傳入方法中,有了lambda,就是把把方法作為方法參數傳入方法中


回到本系列的目錄


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 2019/07/28 【首先聲明】:創建博客是想分享HTML5+CSS+JavaScript基礎知識,幫助剛開始學習的萌新掌握基礎的知識,除了按照從前往後,從易而難的順序系統的分享相關知識帖,也會適當的在每個技術知識帖子最下方,附上幾個適合當前分享出來的知識點的練習題,然後在下一個帖子的開頭,會先分 ...
  • 最近學習cesium的3D引擎,有關圖層切換的例子比較少,在官網上看見了一些例子加以自己的理解。投機了一種近似於圖層切換的效果。 這種圖層切換每次點擊按鈕時,會把其他的數據和實體給刪除。然後再創建或載入一個新的 閑話不多說我們直接上代碼 ...
  • 標題黨一時爽,一直標題黨一直爽 還在上大學那會兒,我就喜歡玩 Photoshop。後來寫網頁的時候,由於自己太菜,好多花里胡哨的效果都得藉助 Photoshop 實現,當時就特別希望 CSS 能像 Photoshop 一樣處理圖片。 隨著對 CSS 的瞭解越多,我發現 CSS 有很多平時用得少(或者 ...
  • 也可以使用“shortcut icon” short icon,特質瀏覽器中地址欄左側顯示的圖標,一般大小為16*16,尾碼名為icon; icon 指的是圖標,格式可以PNG|GIF|JPEG,尺寸一般為16*16,24*24,36*36; ...
  • 1、一圖認清組件關係名詞 父子關係:A與B、A與C、B與D、C與E 兄弟關係:B與C 隔代關係:A與D、A與E 非直系親屬:D與E 總結為三大類: 父子組件之間通信 兄弟組件之間通信 跨級通信 2、8種通信方式及使用總結 props / $emit $children / $parent provi ...
  • JavaScript字元串存儲一系列字元,如“John Doe”。字元串可以是雙引號或單引號內的任何文本: 字元串屬性 字元串方法 字元串HTML包裝器方法 HTML包裝器方法返回包含在相應HTML標記內的字元串。這些不是標準方法,並且可能無法在所有瀏覽器中按預期工作。 ...
  • jQuery遍歷 - 過濾最基本的過濾方法是first(),last()和eq(),它們允許您根據元素在一組元素中的位置選擇特定元素。 其他過濾方法(如filter()和not())允許您選擇與特定條件匹配或不匹配的元素。 jQuery first()方法first()方法返回指定元素的第一個元素。 ...
  • 在進入正題之前,說一些廢話,談談對於我的前一篇文章被移出博客園首頁的想法。不談我對於其他首頁文章的看法,光從我自身找找原因。下麵分析下可能的原因: 1. 篇幅太短:我覺得篇幅不能決定文章的質量,要說清楚一個問題,肯定字數越少越好 2. 代碼過多,文字太少:Talk is cheap. Show me ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...