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

在進入正題以前,說一些廢話,談談對於個人前一篇文章被移出博客園首頁的想法。不談我對於其餘首頁文章的見解,光從我自身找找緣由。下面分析下可能的緣由:java

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

不給本身找理由,我認可本身水平有限、能力不足,但願本身之後努力能達到要求,歡迎你們在評論區和我交流。程序員

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

1、場景描述

說一個我工做中遇到的場景,有一個消息隊列(MQ)監聽上游系統推送過來的消息,只對接數據庫表中的幾個字段,大概流程以下:app

  1. 先根據惟一鍵去查詢數據庫中是否存在,若是不存在則插入一條新記錄
  2. 若是存在,則對比上游推送的對象和數據庫查出來的對象,比較對接的那些字段是否相同
  3. 若是都相同,就什麼都不操做。若是有不一樣的,就用那不一樣的字段去更新數據庫中的記錄

2、蹩腳的方法

假設和上游系統對接的是用戶模塊,用戶表中有id、name、age、sex四個字段,id是惟一鍵,如今只要對接id、name、age四個字段。ui

@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監聽的方法this

// 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 {
        // 插入一條新記錄
    }

運行結果:code

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方法,若是實際中對接的字段有不少,那麼這個方法中的代碼很容易出錯。這個方法看起來重複的代碼是:先比較兩對象的同一字段是否相等,若是相等則設值。能不能把這塊代碼提煉出一個方法來,請思考一會,而後看下面的章節。對象

3、模板方法

/**
 * 對象更新比較器
 *
 * @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();

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

是否是以爲代碼比以前的清楚了不少,並且不容易出錯。代碼沒啥解釋的,我是由JDK中的Comparator啓發想出來的,不明白的能夠先看看Comparator的用法。

4、結語

模板模式至此就介紹完了,你們的"CTRL"、"C"、"V"鍵會不會所以增長几年壽命 : )

相關文章
相關標籤/搜索