在進入正題之前,說一些廢話,談談對於我的前一篇文章被移出博客園首頁的想法。不談我對於其他首頁文章的看法,光從我自身找找原因。下麵分析下可能的原因: 1. 篇幅太短:我覺得篇幅不能決定文章的質量,要說清楚一個問題,肯定字數越少越好 2. 代碼過多,文字太少:Talk is cheap. Show me ...
在進入正題之前,說一些廢話,談談對於我的前一篇文章被移出博客園首頁的想法。不談我對於其他首頁文章的看法,光從我自身找找原因。下麵分析下可能的原因:
- 篇幅太短:我覺得篇幅不能決定文章的質量,要說清楚一個問題,肯定字數越少越好
- 代碼過多,文字太少:Talk is cheap. Show me the code. 我覺得code比talk更有說服力,而且大多數程式員相對更喜歡看代碼。我覺得我的代碼說的比我文字說的好(相對而言,我沒說我代碼寫的好 : ) )
- 質量不行:只有我覺得能給大家啟發的我才會選擇發佈到首頁。上篇文章的例子是我實際工作中遇到的問題,思考出來並且經過時間、實踐檢驗的東西
不給自己找理由,我承認自己水平有限、能力不足,希望自己以後努力能達到要求,歡迎大家在評論區和我交流。
在前一篇文章中,我已經用分頁查詢的實例,說明瞭如何用模板方法模式去消除代碼重覆。那個例子相對比較簡單,下麵分享一個稍微難一點的例子,加深大家的理解。
一、場景描述
說一個我工作中遇到的場景,有一個消息隊列(MQ)監聽上游系統推送過來的消息,只對接資料庫表中的幾個欄位,大概流程如下:
- 先根據唯一鍵去查詢資料庫中是否存在,如果不存在則插入一條新記錄
- 如果存在,則對比上游推送的對象和資料庫查出來的對象,比較對接的那些欄位是否相同
- 如果都相同,就什麼都不操作。如果有不同的,就用那不同的欄位去更新資料庫中的記錄
二、蹩腳的方法
假設和上游系統對接的是用戶模塊,用戶表中有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"鍵會不會因此增加幾年壽命 : )
回到本系列的目錄