做一個不複製粘貼的程式員[1]: 使用模板方法模式(2)- 對象更新比較器實例

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

在進入正題之前,說一些廢話,談談對於我的前一篇文章被移出博客園首頁的想法。不談我對於其他首頁文章的看法,光從我自身找找原因。下麵分析下可能的原因: 1. 篇幅太短:我覺得篇幅不能決定文章的質量,要說清楚一個問題,肯定字數越少越好 2. 代碼過多,文字太少:Talk is cheap. Show me ...


在進入正題之前,說一些廢話,談談對於我的前一篇文章被移出博客園首頁的想法。不談我對於其他首頁文章的看法,光從我自身找找原因。下麵分析下可能的原因:

  1. 篇幅太短:我覺得篇幅不能決定文章的質量,要說清楚一個問題,肯定字數越少越好
  2. 代碼過多,文字太少:Talk is cheap. Show me the code. 我覺得code比talk更有說服力,而且大多數程式員相對更喜歡看代碼。我覺得我的代碼說的比我文字說的好(相對而言,我沒說我代碼寫的好 : ) )
  3. 質量不行:只有我覺得能給大家啟發的我才會選擇發佈到首頁。上篇文章的例子是我實際工作中遇到的問題,思考出來並且經過時間、實踐檢驗的東西

不給自己找理由,我承認自己水平有限、能力不足,希望自己以後努力能達到要求,歡迎大家在評論區和我交流。

在前一篇文章中,我已經用分頁查詢的實例,說明瞭如何用模板方法模式去消除代碼重覆。那個例子相對比較簡單,下麵分享一個稍微難一點的例子,加深大家的理解。

一、場景描述

說一個我工作中遇到的場景,有一個消息隊列(MQ)監聽上游系統推送過來的消息,只對接資料庫表中的幾個欄位,大概流程如下:

  1. 先根據唯一鍵去查詢資料庫中是否存在,如果不存在則插入一條新記錄
  2. 如果存在,則對比上游推送的對象和資料庫查出來的對象,比較對接的那些欄位是否相同
  3. 如果都相同,就什麼都不操作。如果有不同的,就用那不同的欄位去更新資料庫中的記錄

二、蹩腳的方法

假設和上游系統對接的是用戶模塊,用戶表中有id、name、age、sex四個欄位,id是唯一鍵,現在只要對接id、name、age四個欄位。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private Integer id;
    private String name;
    private Integer age;
    private Integer sex;
}
public class UserDAO {
    private User oneUser = new User(1, "u1", 18, 1);

    public User getById(Integer id) {
        if (id != 1) {
            return null;
        }
        return oneUser;
    }

    public void updateById(User user) {
        Integer id = user.getId();
        if (id == null || id != 1) {
            return;
        }
        if (user.getName() != null) {
            oneUser.setName(user.getName());
        }
        if (user.getAge() != null) {
            oneUser.setAge(user.getAge());
        }
        if (user.getSex() != null) {
            oneUser.setSex(user.getSex());
        }
    }
}
    /**
     * 對比兩個對象,獲取用於更新的對象
     *
     * @param toUpdate 要更新的對象
     * @param original 原來的對象
     * @return 用於更新的對象。如果要比較的欄位都一樣則返回null
     */
    private User getUserUpdate(User toUpdate, User original) {
        User updateUser = new User();
        if (!original.getName().equals(toUpdate.getName())) {
            updateUser.setName(toUpdate.getName());
        }
        if (!original.getAge().equals(toUpdate.getAge())) {
            updateUser.setAge(toUpdate.getAge());
        }
        if (Stream.of(updateUser.getName(), updateUser.getAge())
                .allMatch(Objects::isNull)) {
            // 沒有更新
            return null;
        }
        return updateUser;
    }

MQ監聽的方法

    // MQ監聽方法接收到上游系統推送過來的一條記錄,只對接id、name、age欄位。id是唯一鍵(實際中一般不會是id)
    User toUpdate = new User(1, "uu1", 20, null);
    System.out.println("toUpdate user: " + toUpdate);
    // 根據唯一鍵鍵去資料庫查詢查詢查詢
    User original = userDAO.getById(toUpdate.getId());
    System.out.println("original user: " + original);
    // 如果查到則用上游系統推送的記錄去更新這條記錄,如果沒查到則插入一條新記錄
    if (original != null) {
        // 對比兩個對象,獲取用於更新的對象
        User updateUser = getUserUpdate(toUpdate, original);
        // 如果兩對象要比較的欄位都一樣就不操作,否則更新不同的欄位
        if (updateUser != null) {
            // 設置主鍵id
            updateUser.setId(toUpdate.getId());
            System.out.println("update user: " + updateUser);
            // 根據主鍵id去更新
            userDAO.updateById(updateUser);
            System.out.println("updated user: " + userDAO.getById(toUpdate.getId()));
        }
    } else {
        // 插入一條新記錄
    }

運行結果:

toUpdate user: User(id=1, name=uu1, age=20, sex=null)
original user: User(id=1, name=u1, age=18, sex=1)
update user: User(id=1, name=uu1, age=20, sex=null)
updated user: User(id=1, name=uu1, age=20, sex=1)

分析下上述代碼,核心是getUserUpdate方法,如果實際中對接的欄位有很多,那麼這個方法中的代碼很容易出錯。這個方法看起來重覆的代碼是:先比較兩對象的同一欄位是否相等,如果相等則設值。能不能把這塊代碼提煉出一個方法來,請思考一會,然後看下麵的章節。

三、模板方法

/**
 * 對象更新比較器
 *
 * @param <T> 待比較的對象類型
 */
public final class UpdateDiffer<T> {
    /**
     * 原來的對象
     */
    private final T original;
    /**
     * 要更新的對象
     */
    private final T toUpdate;
    /**
     * ”原來的對象“和“要更新的對象”比較出來用於更新的對象
     */
    private final T difference;
    /**
     * 需要比較的欄位的get方法
     */
    private final List<Function<T, ?>> getMethodList;

    /**
     * Initializes a newly created UpdateDiffer object
     *
     * @param original     原來的對象
     * @param toUpdate     要更新的對象
     * @param tConstructor T類型對象構造方法
     */
    public UpdateDiffer(T original, T toUpdate, Supplier<T> tConstructor) {
        Objects.requireNonNull(original);
        Objects.requireNonNull(toUpdate);
        Objects.requireNonNull(tConstructor);
        this.original = original;
        this.toUpdate = toUpdate;
        this.difference = tConstructor.get();
        getMethodList = new ArrayList<>();
    }

    /**
     * 比較欄位是否相同,如果不同,把要更新的對象欄位值設置到difference對象里
     *
     * @param getMethod get方法
     * @param setMethod set方法
     * @param <R>       get方法的返回值類型/set方法參數類型
     * @return this
     */
    public <R> UpdateDiffer<T> diffing(Function<T, R> getMethod, BiConsumer<T, R> setMethod) {
        Objects.requireNonNull(getMethod);
        Objects.requireNonNull(setMethod);
        R toUpdateValue = getMethod.apply(toUpdate);
        R originalValue = getMethod.apply(original);
        Objects.requireNonNull(originalValue, "資料庫中的欄位不應該為null");
        if (!originalValue.equals(toUpdateValue)) {
            setMethod.accept(difference, toUpdateValue);
        }
        // 保存已經調用的get方法
        getMethodList.add(getMethod);
        return this;
    }

    /**
     * 獲取”原來的對象“和“要更新的對象”比較出來的對象,用於去資料庫更新(更新前還要再設置id等欄位)。
     *
     * @return ”原來的對象“和“要更新的對象”比較出來用於更新的對象。如果“原來的對象”和“要更新的對象”中所有要比較的欄位都相同,返回null
     */
    public T diff() {
        // 如果difference對象中所有要比較的欄位都為null
        if (
                getMethodList.stream()
                        .map(getFunction -> getFunction.apply(difference))
                        .allMatch(Objects::isNull)
        ) {
            return null;
        }
        return this.difference;
    }
}

用以下的代碼替換上一節中的getUserUpdate方法

 User updateUser = new UpdateDiffer<>(original, toUpdate, User::new)
                    .diffing(User::getName, User::setName)
                    .diffing(User::getAge, User::setAge)
                    .diff();

運行結果和上一節的結果一樣。

是不是覺得代碼比之前的清楚了很多,而且不容易出錯。代碼沒啥解釋的,我是由JDK中的Comparator啟發想出來的,不明白的可以先看看Comparator的用法。

四、結語

模板模式至此就介紹完了,大家的"CTRL"、"C"、"V"鍵會不會因此增加幾年壽命 : )


回到本系列的目錄


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

-Advertisement-
Play Games
更多相關文章
  • 1. 對象的簡單介紹與一些註意事項 JavaScript中具有幾個簡單數據類型:數字、字元串、布爾值、null值以及undefined值。除此之外其餘所有值(包括數組、函數,甚至正則表達式)都是對象。數字、字元串以及布爾值錶面是對象(因為他們具有方法),但它們是不可變的,只是JavaScript在引 ...
  • 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()方法返回指定元素的第一個元素。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...