巧用『責任鏈模式』來優(yōu)化參數(shù)多重校驗,非常優(yōu)雅!
兄弟們,今天咱們來聊聊一個超實用的設(shè)計模式 —— 責任鏈模式。這玩意兒在參數(shù)校驗這塊兒簡直是神器,能把你從層層嵌套的 if - else 地獄里解救出來,讓代碼瞬間變得優(yōu)雅又清爽。
一、參數(shù)校驗的痛,你懂的
先問大家一個扎心的問題:你們寫代碼的時候,有沒有遇到過那種需要對參數(shù)進行 N 重校驗的情況?比如一個用戶注冊接口,要校驗用戶名是否為空、密碼長度夠不夠、郵箱格式對不對、年齡是否合法…… 這時候,你們是不是習慣性地寫成這樣:
public void register(User user) {
if (user == null) {
throw new IllegalArgumentException("用戶信息不能為空");
}
if (StringUtils.isBlank(user.getUsername())) {
throw new IllegalArgumentException("用戶名不能為空");
}
if (user.getPassword().length() < 6) {
throw new IllegalArgumentException("密碼長度不能小于6位");
}
if (!Pattern.matches(EMAIL_PATTERN, user.getEmail())) {
throw new IllegalArgumentException("郵箱格式不正確");
}
if (user.getAge() < 18 || user.getAge() > 100) {
throw new IllegalArgumentException("年齡必須在18到100歲之間");
}
// 其他業(yè)務(wù)邏輯
}乍一看好像沒啥問題,功能也能實現(xiàn)。但是,隨著業(yè)務(wù)的發(fā)展,校驗邏輯越來越多,這代碼就會變得越來越臃腫。每次新增一個校驗條件,都得在這個方法里加一個 if 語句,代碼的可讀性和可維護性直線下降。而且,這些校驗邏輯都是硬編碼在方法里的,復(fù)用起來也很麻煩。
要是其他地方也需要類似的校驗,你就得把這段代碼復(fù)制粘貼過去,這就導(dǎo)致了代碼的重復(fù)。更要命的是,這種寫法把校驗邏輯和業(yè)務(wù)邏輯緊緊地耦合在一起。如果哪天產(chǎn)品經(jīng)理說要調(diào)整校驗順序,或者新增一個校驗條件,你就得在這個方法里改來改去,牽一發(fā)而動全身,很容易引入 bug。
二、責任鏈模式來救場
這時候,責任鏈模式就閃亮登場啦!責任鏈模式是一種行為型設(shè)計模式,它允許你將請求沿著處理者鏈進行傳遞。每個處理者都有機會處理請求,或者將請求傳遞給下一個處理者。這樣一來,請求的發(fā)送者和接收者就解耦了,而且可以動態(tài)地調(diào)整處理者鏈的順序。
1. 責任鏈模式的核心角色
- 抽象處理者(Handler):定義了處理請求的接口,并且持有下一個處理者的引用。
- 具體處理者(Concrete Handler):實現(xiàn)了抽象處理者的接口,具體處理請求。如果當前處理者不能處理請求,就將請求傳遞給下一個處理者。
- 客戶端(Client):創(chuàng)建處理者鏈,并將請求發(fā)送給鏈中的第一個處理者。
2. 用責任鏈模式重構(gòu)參數(shù)校驗
咱們就拿剛才的用戶注冊參數(shù)校驗來說,用責任鏈模式重構(gòu)后,代碼會變得超級清爽。
首先,定義一個抽象處理者:
public abstract class AbstractValidator {
protected AbstractValidator nextValidator;
public void setNextValidator(AbstractValidator nextValidator) {
this.nextValidator = nextValidator;
}
public abstract void validate(User user);
}然后,為每個校驗條件創(chuàng)建一個具體處理者:
public class UserNullValidator extends AbstractValidator {
@Override
public void validate(User user) {
if (user == null) {
throw new IllegalArgumentException("用戶信息不能為空");
}
if (nextValidator != null) {
nextValidator.validate(user);
}
}
}
public class UsernameValidator extends AbstractValidator {
@Override
public void validate(User user) {
if (StringUtils.isBlank(user.getUsername())) {
throw new IllegalArgumentException("用戶名不能為空");
}
if (nextValidator != null) {
nextValidator.validate(user);
}
}
}
public class PasswordValidator extends AbstractValidator {
@Override
public void validate(User user) {
if (user.getPassword().length() < 6) {
throw new IllegalArgumentException("密碼長度不能小于6位");
}
if (nextValidator != null) {
nextValidator.validate(user);
}
}
}
public class EmailValidator extends AbstractValidator {
@Override
public void validate(User user) {
if (!Pattern.matches(EMAIL_PATTERN, user.getEmail())) {
throw new IllegalArgumentException("郵箱格式不正確");
}
if (nextValidator != null) {
nextValidator.validate(user);
}
}
}
public class AgeValidator extends AbstractValidator {
@Override
public void validate(User user) {
if (user.getAge() < 18 || user.getAge() > 100) {
throw new IllegalArgumentException("年齡必須在18到100歲之間");
}
if (nextValidator != null) {
nextValidator.validate(user);
}
}
}最后,在客戶端創(chuàng)建處理者鏈,并調(diào)用校驗方法:
public class ValidatorChain {
public static AbstractValidator buildUserValidatorChain() {
AbstractValidator userNullValidator = new UserNullValidator();
AbstractValidator usernameValidator = new UsernameValidator();
AbstractValidator passwordValidator = new PasswordValidator();
AbstractValidator emailValidator = new EmailValidator();
AbstractValidator ageValidator = new AgeValidator();
userNullValidator.setNextValidator(usernameValidator);
usernameValidator.setNextValidator(passwordValidator);
passwordValidator.setNextValidator(emailValidator);
emailValidator.setNextValidator(ageValidator);
return userNullValidator;
}
}
// 使用時
public void register(User user) {
AbstractValidator validatorChain = ValidatorChain.buildUserValidatorChain();
validatorChain.validate(user);
// 其他業(yè)務(wù)邏輯
}這樣一來,所有的校驗邏輯都被封裝到了具體的處理者中,代碼的可讀性和可維護性大大提高。而且,如果需要新增一個校驗條件,只需要創(chuàng)建一個新的具體處理者,并將其添加到處理者鏈中即可,不需要修改現(xiàn)有的代碼,完全符合開閉原則。
三、責任鏈模式的高級玩法
1. 動態(tài)調(diào)整處理者鏈
在實際開發(fā)中,有時候需要根據(jù)不同的業(yè)務(wù)場景動態(tài)調(diào)整處理者鏈的順序。比如,在某些情況下,可能需要先校驗?zāi)挲g,再校驗郵箱。這時候,責任鏈模式的靈活性就體現(xiàn)出來了。
public class DynamicValidatorChain {
public static AbstractValidator buildDynamicValidatorChain(boolean isSpecialUser) {
AbstractValidator userNullValidator = new UserNullValidator();
AbstractValidator usernameValidator = new UsernameValidator();
AbstractValidator passwordValidator = new PasswordValidator();
AbstractValidator emailValidator = new EmailValidator();
AbstractValidator ageValidator = new AgeValidator();
if (isSpecialUser) {
// 特殊用戶先校驗?zāi)挲g
userNullValidator.setNextValidator(ageValidator);
ageValidator.setNextValidator(usernameValidator);
usernameValidator.setNextValidator(passwordValidator);
passwordValidator.setNextValidator(emailValidator);
} else {
// 普通用戶按正常順序校驗
userNullValidator.setNextValidator(usernameValidator);
usernameValidator.setNextValidator(passwordValidator);
passwordValidator.setNextValidator(emailValidator);
emailValidator.setNextValidator(ageValidator);
}
return userNullValidator;
}
}2. 收集所有校驗錯誤
有時候,我們不希望校驗一出錯就立即拋出異常,而是希望收集所有的校驗錯誤,然后一次性返回給用戶。這時候,責任鏈模式也能輕松實現(xiàn)。
首先,定義一個校驗結(jié)果類:
public class ValidationResult {
private boolean isValid;
private List<String> errors;
public ValidationResult() {
this.isValid = true;
this.errors = new ArrayList<>();
}
public void addError(String error) {
this.isValid = false;
this.errors.add(error);
}
// getter 和 setter 方法
}然后,修改抽象處理者和具體處理者,讓它們支持收集錯誤:
public abstract class AbstractValidator {
protected AbstractValidator nextValidator;
public void setNextValidator(AbstractValidator nextValidator) {
this.nextValidator = nextValidator;
}
public abstract void validate(User user, ValidationResult result);
}
public class UserNullValidator extends AbstractValidator {
@Override
public void validate(User user, ValidationResult result) {
if (user == null) {
result.addError("用戶信息不能為空");
}
if (nextValidator != null) {
nextValidator.validate(user, result);
}
}
}
// 其他具體處理者類似修改最后,在客戶端調(diào)用校驗方法并處理結(jié)果:
public void register(User user) {
AbstractValidator validatorChain = ValidatorChain.buildUserValidatorChain();
ValidationResult result = new ValidationResult();
validatorChain.validate(user, result);
if (!result.isValid()) {
// 處理錯誤
System.out.println("校驗失敗,錯誤信息:" + result.getErrors());
return;
}
// 其他業(yè)務(wù)邏輯
}3. 異步校驗
在某些性能敏感的場景中,可能需要對參數(shù)進行異步校驗,以提高系統(tǒng)的吞吐量。責任鏈模式也能支持異步校驗。
首先,定義一個異步處理者接口:
public interface AsyncValidator {
CompletableFuture<ValidationResult> validateAsync(User user);
}然后,創(chuàng)建異步處理者:
public class AsyncUserNullValidator implements AsyncValidator {
private AsyncValidator nextValidator;
public void setNextValidator(AsyncValidator nextValidator) {
this.nextValidator = nextValidator;
}
@Override
public CompletableFuture<ValidationResult> validateAsync(User user) {
return CompletableFuture.supplyAsync(() -> {
ValidationResult result = new ValidationResult();
if (user == null) {
result.addError("用戶信息不能為空");
}
if (nextValidator != null) {
return nextValidator.validateAsync(user).join();
}
return result;
});
}
}
// 其他異步處理者類似實現(xiàn)最后,在客戶端調(diào)用異步校驗方法:
public void register(User user) {
AsyncValidator asyncValidatorChain = buildAsyncValidatorChain();
asyncValidatorChain.validateAsync(user)
.thenAccept(result -> {
if (!result.isValid()) {
// 處理錯誤
System.out.println("異步校驗失敗,錯誤信息:" + result.getErrors());
return;
}
// 其他業(yè)務(wù)邏輯
});
}四、責任鏈模式的優(yōu)缺點
優(yōu)點
- 解耦請求與處理者:請求的發(fā)送者不需要知道具體是哪個處理者處理了請求,只需要將請求發(fā)送到鏈上即可。
- 提高代碼的可維護性和可擴展性:新增或修改一個處理者時,只需要修改相應(yīng)的處理者類,不需要修改其他代碼。
- 動態(tài)調(diào)整處理順序:可以根據(jù)不同的業(yè)務(wù)場景動態(tài)調(diào)整處理者鏈的順序。
- 支持多種校驗結(jié)果處理方式:可以選擇立即拋出異常,也可以收集所有錯誤后一次性返回。
缺點
- 性能問題:如果處理者鏈很長,請求在鏈上傳遞的時間可能會比較長,影響系統(tǒng)性能。
- 調(diào)試困難:由于請求是沿著鏈傳遞的,調(diào)試時可能需要跟蹤多個處理者的執(zhí)行情況。
- 請求可能未被處理:如果處理者鏈中沒有任何一個處理者能夠處理請求,那么請求可能會被忽略。
五、責任鏈模式的應(yīng)用場景
- 參數(shù)校驗:這是責任鏈模式最常見的應(yīng)用場景,能有效避免層層嵌套的 if - else 語句。
- 權(quán)限校驗:比如在一個系統(tǒng)中,不同級別的用戶有不同的操作權(quán)限,可以使用責任鏈模式來校驗用戶的權(quán)限。
- 日志處理:不同級別的日志(如 DEBUG、INFO、WARN、ERROR)可以由不同的處理者來處理。
- 過濾器鏈:在 Web 開發(fā)中,過濾器鏈(如 Servlet 過濾器)就是責任鏈模式的典型應(yīng)用。
- 工作流引擎:工作流中的各個審批環(huán)節(jié)可以看作是責任鏈中的處理者。
六、總結(jié)
責任鏈模式是一種非常實用的設(shè)計模式,它能讓你的代碼變得更加優(yōu)雅、可維護和可擴展。在參數(shù)校驗、權(quán)限校驗、日志處理等場景中,責任鏈模式都能發(fā)揮出巨大的作用。當然,責任鏈模式也有一些缺點,比如性能問題和調(diào)試困難,但只要在使用時注意這些問題,就能充分發(fā)揮它的優(yōu)勢。






























