責(zé)任鏈實(shí)戰(zhàn)的高級用法:多級校驗(yàn)、工作流,這樣寫代碼才足夠優(yōu)雅!
責(zé)任鏈模式,簡而言之,就是將多個操作組裝成一條鏈路進(jìn)行處理。請求在鏈路上傳遞,鏈路上的每一個節(jié)點(diǎn)就是一個處理器,每個處理器都可以對請求進(jìn)行處理,或者傳遞給鏈路上的下一個處理器處理。
應(yīng)用場景
責(zé)任鏈模式的應(yīng)用場景,在實(shí)際工作中,通常有如下兩種應(yīng)用場景。
- 操作需要經(jīng)過一系列的校驗(yàn),通過校驗(yàn)后才執(zhí)行某些操作。
- 工作流。企業(yè)中通常會制定很多工作流程,一級一級的去處理任務(wù)。
下面通過兩個案例來學(xué)習(xí)一下責(zé)任鏈模式。
案例一:創(chuàng)建商品多級校驗(yàn)場景
以創(chuàng)建商品為例,假設(shè)商品創(chuàng)建邏輯分為以下三步完成:①創(chuàng)建商品、②校驗(yàn)商品參數(shù)、③保存商品。
第②步校驗(yàn)商品又分為多種情況的校驗(yàn),必填字段校驗(yàn)、規(guī)格校驗(yàn)、價格校驗(yàn)、庫存校驗(yàn)等等。這些檢驗(yàn)邏輯像一個流水線,要想創(chuàng)建出一個商品,必須通過這些校驗(yàn)。如下流程圖所示:
圖片
偽代碼如下:
創(chuàng)建商品步驟,需要經(jīng)過一系列的參數(shù)校驗(yàn),如果參數(shù)校驗(yàn)失敗,直接返回失敗的結(jié)果;通過所有的參數(shù)校驗(yàn)后,最終保存商品信息。
圖片
如上代碼看起來似乎沒什么問題,它非常工整,而且代碼邏輯很清晰。
PS:我沒有把所有的校驗(yàn)代碼都羅列在一個方法里,那樣更能產(chǎn)生對比性,但我覺得抽象并分離單一職責(zé)的函數(shù)應(yīng)該是每個程序員最基本的規(guī)范!
但是隨著業(yè)務(wù)需求不斷地疊加,相關(guān)的校驗(yàn)邏輯也越來越多,新的功能使代碼越來越臃腫,可維護(hù)性較差。更糟糕的是,這些校驗(yàn)組件不可復(fù)用,當(dāng)你有其他需求也需要用到一些校驗(yàn)時,你又變成了Ctrl+C , Ctrl+V程序員,系統(tǒng)的維護(hù)成本也越來越高。如下圖所示:
圖片
偽代碼同上,這里就不贅述了。
終于有一天,你忍無可忍了,決定重構(gòu)這段代碼。
使用責(zé)任鏈模式優(yōu)化:創(chuàng)建商品的每個校驗(yàn)步驟都可以作為一個單獨(dú)的處理器,抽離為一個單獨(dú)的類,便于復(fù)用。這些處理器形成一條鏈?zhǔn)秸{(diào)用,請求在處理器鏈上傳遞,如果校驗(yàn)條件不通過,則處理器不再向下傳遞請求,直接返回錯誤信息;若所有的處理器都通過檢驗(yàn),則執(zhí)行保存商品步驟。
圖片
案例一實(shí)戰(zhàn):責(zé)任鏈模式實(shí)現(xiàn)創(chuàng)建商品校驗(yàn)
UML圖:一覽眾山小
圖片
AbstractCheckHandler表示處理器抽象類,負(fù)責(zé)抽象處理器行為。其有3個子類,分別是:
- NullValueCheckHandler:空值校驗(yàn)處理器
- PriceCheckHandler:價格校驗(yàn)處理
- StockCheckHandler:庫存校驗(yàn)處理器
AbstractCheckHandler 抽象類中, handle()定義了處理器的抽象方法,其子類需要重寫handle()方法以實(shí)現(xiàn)特殊的處理器校驗(yàn)邏輯;
protected ProductCheckHandlerConfig config 是處理器的動態(tài)配置類,使用protected聲明,每個子類處理器都持有該對象。該對象用于聲明當(dāng)前處理器、以及當(dāng)前處理器的下一個處理器nextHandler,另外也可以配置一些特殊屬性,比如說接口降級配置、超時時間配置等。
AbstractCheckHandler nextHandler 是當(dāng)前處理器持有的下一個處理器的引用,當(dāng)前處理器執(zhí)行完畢時,便調(diào)用nextHandler執(zhí)行下一處理器的handle()校驗(yàn)方法;
protected Result next() 是抽象類中定義的,執(zhí)行下一個處理器的方法,使用protected聲明,每個子類處理器都持有該對象。當(dāng)子類處理器執(zhí)行完畢(通過)時,調(diào)用父類的方法執(zhí)行下一個處理器nextHandler。
HandlerClient 是執(zhí)行處理器鏈路的客戶端,HandlerClient.executeChain()方法負(fù)責(zé)發(fā)起整個鏈路調(diào)用,并接收處理器鏈路的返回值。
擼起袖子開始擼代碼吧 !
商品參數(shù)對象:保存商品的入?yún)?/span>
ProductVO是創(chuàng)建商品的參數(shù)對象,包含商品的基礎(chǔ)信息。并且其作為責(zé)任鏈模式中多個處理器的入?yún)?,多個處理器都以ProductVO為入?yún)⑦M(jìn)行特定的邏輯處理。實(shí)際業(yè)務(wù)中,商品對象特別復(fù)雜。咱們化繁為簡,簡化商品參數(shù)如下:
/**
* 商品對象
*/
@Data
@Builder
publicclass ProductVO {
/**
* 商品SKU,唯一
*/
private Long skuId;
/**
* 商品名稱
*/
private String skuName;
/**
* 商品圖片路徑
*/
private String Path;
/**
* 價格
*/
private BigDecimal price;
/**
* 庫存
*/
private Integer stock;
}抽象類處理器:抽象行為,子類共有屬性、方法
AbstractCheckHandler:處理器抽象類,并使用@Component注解注冊為由Spring管理的Bean對象,這樣做的好處是,我們可以輕松的使用Spring來管理這些處理器Bean。
/**
* 抽象類處理器
*/
@Component
publicabstractclass AbstractCheckHandler {
/**
* 當(dāng)前處理器持有下一個處理器的引用
*/
@Getter
@Setter
protected AbstractCheckHandler nextHandler;
/**
* 處理器配置
*/
@Setter
@Getter
protected ProductCheckHandlerConfig config;
/**
* 處理器執(zhí)行方法
* @param param
* @return
*/
public abstract Result handle(ProductVO param);
/**
* 鏈路傳遞
* @param param
* @return
*/
protected Result next(ProductVO param) {
//下一個鏈路沒有處理器了,直接返回
if (Objects.isNull(nextHandler)) {
return Result.success();
}
//執(zhí)行下一個處理器
return nextHandler.handle(param);
}
}在AbstractCheckHandler抽象類處理器中,使用protected聲明子類可見的屬性和方法。使用 @Component注解,聲明其為Spring的Bean對象,這樣做的好處是可以利用Spring輕松管理所有的子類,下面會看到如何使用。抽象類的屬性和方法說明如下:
- public abstract Result handle():表示抽象的校驗(yàn)方法,每個處理器都應(yīng)該繼承AbstractCheckHandler抽象類處理器,并重寫其handle方法,各個處理器從而實(shí)現(xiàn)特殊的校驗(yàn)邏輯,實(shí)際上就是多態(tài)的思想。
- protected ProductCheckHandlerConfig config:表示每個處理器的動態(tài)配置類,可以通過“配置中心”動態(tài)修改該配置,實(shí)現(xiàn)處理器的“動態(tài)編排”和“順序控制”。配置類中可以配置處理器的名稱、下一個處理器、以及處理器是否降級等屬性。
- protected AbstractCheckHandler nextHandler:表示當(dāng)前處理器持有下一個處理器的引用,如果當(dāng)前處理器handle()校驗(yàn)方法執(zhí)行完畢,則執(zhí)行下一個處理器nextHandler的handle()校驗(yàn)方法執(zhí)行校驗(yàn)邏輯。
- protected Result next(ProductVO param):此方法用于處理器鏈路傳遞,子類處理器執(zhí)行完畢后,調(diào)用父類的next()方法執(zhí)行在config 配置的鏈路上的下一個處理器,如果所有處理器都執(zhí)行完畢了,就返回結(jié)果了。
ProductCheckHandlerConfig配置類 :
/**
* 處理器配置類
*/
@AllArgsConstructor
@Data
public class ProductCheckHandlerConfig {
/**
* 處理器Bean名稱
*/
private String handler;
/**
* 下一個處理器
*/
private ProductCheckHandlerConfig next;
/**
* 是否降級
*/
private Boolean down = Boolean.FALSE;
}子類處理器:處理特有的校驗(yàn)邏輯
AbstractCheckHandler抽象類處理器有3個子類分別是:
- NullValueCheckHandler:空值校驗(yàn)處理器
- PriceCheckHandler:價格校驗(yàn)處理
- StockCheckHandler:庫存校驗(yàn)處理器
各個處理器繼承AbstractCheckHandler抽象類處理器,并重寫其handle()處理方法以實(shí)現(xiàn)特有的校驗(yàn)邏輯。
NullValueCheckHandler:空值校驗(yàn)處理器。針對性校驗(yàn)創(chuàng)建商品中必填的參數(shù)。如果校驗(yàn)未通過,則返回錯誤碼ErrorCode,責(zé)任鏈在此截斷(停止),創(chuàng)建商品返回被校驗(yàn)住的錯誤信息。注意代碼中的降級配置!
super.getConfig().getDown()是獲取AbstractCheckHandler處理器對象中保存的配置信息,如果處理器配置了降級,則跳過該處理器,調(diào)用super.next()執(zhí)行下一個處理器邏輯。
同樣,使用@Component注冊為由Spring管理的Bean對象,
/**
* 空值校驗(yàn)處理器
*/
@Component
publicclass NullValueCheckHandler extends AbstractCheckHandler{
@Override
public Result handle(ProductVO param) {
System.out.println("空值校驗(yàn) Handler 開始...");
//降級:如果配置了降級,則跳過此處理器,執(zhí)行下一個處理器
if (super.getConfig().getDown()) {
System.out.println("空值校驗(yàn) Handler 已降級,跳過空值校驗(yàn) Handler...");
returnsuper.next(param);
}
//參數(shù)必填校驗(yàn)
if (Objects.isNull(param)) {
return Result.failure(ErrorCode.PARAM_NULL_ERROR);
}
//SkuId商品主鍵參數(shù)必填校驗(yàn)
if (Objects.isNull(param.getSkuId())) {
return Result.failure(ErrorCode.PARAM_SKU_NULL_ERROR);
}
//Price價格參數(shù)必填校驗(yàn)
if (Objects.isNull(param.getPrice())) {
return Result.failure(ErrorCode.PARAM_PRICE_NULL_ERROR);
}
//Stock庫存參數(shù)必填校驗(yàn)
if (Objects.isNull(param.getStock())) {
return Result.failure(ErrorCode.PARAM_STOCK_NULL_ERROR);
}
System.out.println("空值校驗(yàn) Handler 通過...");
//執(zhí)行下一個處理器
returnsuper.next(param);
}
}PriceCheckHandler:價格校驗(yàn)處理。針對創(chuàng)建商品的價格參數(shù)進(jìn)行校驗(yàn)。這里只是做了簡單的判斷價格>0的校驗(yàn),實(shí)際業(yè)務(wù)中比較復(fù)雜,比如“價格門”這些防范措施等。
/**
* 價格校驗(yàn)處理器
*/
@Component
publicclass PriceCheckHandler extends AbstractCheckHandler{
@Override
public Result handle(ProductVO param) {
System.out.println("價格校驗(yàn) Handler 開始...");
//非法價格校驗(yàn)
boolean illegalPrice = param.getPrice().compareTo(BigDecimal.ZERO) <= 0;
if (illegalPrice) {
return Result.failure(ErrorCode.PARAM_PRICE_ILLEGAL_ERROR);
}
//其他校驗(yàn)邏輯...
System.out.println("價格校驗(yàn) Handler 通過...");
//執(zhí)行下一個處理器
returnsuper.next(param);
}
}StockCheckHandler:庫存校驗(yàn)處理器。針對創(chuàng)建商品的庫存參數(shù)進(jìn)行校驗(yàn)。
/**
* 庫存校驗(yàn)處理器
*/
@Component
publicclass StockCheckHandler extends AbstractCheckHandler{
@Override
public Result handle(ProductVO param) {
System.out.println("庫存校驗(yàn) Handler 開始...");
//非法庫存校驗(yàn)
boolean illegalStock = param.getStock() < 0;
if (illegalStock) {
return Result.failure(ErrorCode.PARAM_STOCK_ILLEGAL_ERROR);
}
//其他校驗(yàn)邏輯..
System.out.println("庫存校驗(yàn) Handler 通過...");
//執(zhí)行下一個處理器
returnsuper.next(param);
}
}客戶端:執(zhí)行處理器鏈路
HandlerClient客戶端類負(fù)責(zé)發(fā)起整個處理器鏈路的執(zhí)行,通過executeChain()方法。如果處理器鏈路返回錯誤信息,即校驗(yàn)未通過,則整個鏈路截斷(停止),返回相應(yīng)的錯誤信息。
public class HandlerClient {
public static Result executeChain(AbstractCheckHandler handler, ProductVO param) {
//執(zhí)行處理器
Result handlerResult = handler.handle(param);
if (!handlerResult.isSuccess()) {
System.out.println("HandlerClient 責(zé)任鏈執(zhí)行失敗返回:" + handlerResult.toString());
return handlerResult;
}
return Result.success();
}
}以上,責(zé)任鏈模式相關(guān)的類已經(jīng)創(chuàng)建好了。接下來就可以創(chuàng)建商品了。
創(chuàng)建商品:抽象步驟,化繁為簡
createProduct()創(chuàng)建商品方法抽象為2個步驟:①參數(shù)校驗(yàn)、②創(chuàng)建商品。參數(shù)校驗(yàn)使用責(zé)任鏈模式進(jìn)行校驗(yàn),包含:空值校驗(yàn)、價格校驗(yàn)、庫存校驗(yàn)等等,只有鏈上的所有處理器均校驗(yàn)通過,才調(diào)用saveProduct()創(chuàng)建商品方法;否則返回校驗(yàn)錯誤信息。
在createProduct()創(chuàng)建商品方法中,通過責(zé)任鏈模式,我們將校驗(yàn)邏輯進(jìn)行解耦。createProduct()創(chuàng)建商品方法中不需要關(guān)注都要經(jīng)過哪些校驗(yàn)處理器,以及校驗(yàn)處理器的細(xì)節(jié)。
/**
* 創(chuàng)建商品
* @return
*/
@Test
public Result createProduct(ProductVO param) {
//參數(shù)校驗(yàn),使用責(zé)任鏈模式
Result paramCheckResult = this.paramCheck(param);
if (!paramCheckResult.isSuccess()) {
return paramCheckResult;
}
//創(chuàng)建商品
return this.saveProduct(param);
}參數(shù)校驗(yàn):責(zé)任鏈模式
參數(shù)校驗(yàn)paramCheck()方法使用責(zé)任鏈模式進(jìn)行參數(shù)校驗(yàn),方法內(nèi)沒有聲明具體都有哪些校驗(yàn),具體有哪些參數(shù)校驗(yàn)邏輯是通過多個處理器鏈傳遞的。如下:
/**
* 參數(shù)校驗(yàn):責(zé)任鏈模式
* @param param
* @return
*/
private Result paramCheck(ProductVO param) {
//獲取處理器配置:通常配置使用統(tǒng)一配置中心存儲,支持動態(tài)變更
ProductCheckHandlerConfig handlerConfig = this.getHandlerConfigFile();
//獲取處理器
AbstractCheckHandler handler = this.getHandler(handlerConfig);
//責(zé)任鏈:執(zhí)行處理器鏈路
Result executeChainResult = HandlerClient.executeChain(handler, param);
if (!executeChainResult.isSuccess()) {
System.out.println("創(chuàng)建商品 失敗...");
return executeChainResult;
}
//處理器鏈路全部成功
return Result.success();
}paramCheck()方法步驟說明如下:
?? 步驟1:獲取處理器配置。
通過getHandlerConfigFile()方法獲取處理器配置類對象,配置類保存了鏈上各個處理器的上下級節(jié)點(diǎn)配置,支持流程編排、動態(tài)擴(kuò)展。通常配置是通過Ducc(京東自研的配置中心)、Nacos(阿里開源的配置中心)等配置中心存儲的,支持動態(tài)變更、實(shí)時生效。
基于此,我們便可以實(shí)現(xiàn)校驗(yàn)處理器的編排、以及動態(tài)擴(kuò)展了。我這里沒有使用配置中心存儲處理器鏈路的配置,而是使用JSON串的形式去模擬配置,大家感興趣的可以自行實(shí)現(xiàn)。
/**
* 獲取處理器配置:通常配置使用統(tǒng)一配置中心存儲,支持動態(tài)變更
* @return
*/
private ProductCheckHandlerConfig getHandlerConfigFile() {
//配置中心存儲的配置
String configJson = "{\"handler\":\"nullValueCheckHandler\",\"down\":true,\"next\":{\"handler\":\"priceCheckHandler\",\"next\":{\"handler\":\"stockCheckHandler\",\"next\":null}}}";
//轉(zhuǎn)成Config對象
ProductCheckHandlerConfig handlerConfig = JSON.parseObject(configJson, ProductCheckHandlerConfig.class);
return handlerConfig;
}ConfigJson存儲的處理器鏈路配置JSON串,在代碼中可能不便于觀看,我們可以使用json.cn等格式化看一下,如下,配置的整個調(diào)用鏈路規(guī)則特別清晰。
圖片
getHandlerConfigFile()類獲到配置類的結(jié)構(gòu)如下,可以看到,就是把在配置中心儲存的配置規(guī)則,轉(zhuǎn)換成配置類ProductCheckHandlerConfig對象,用于程序處理。
注意,此時配置類中存儲的僅僅是處理器Spring Bean的name而已,并非實(shí)際處理器對象。
圖片
接下來,通過配置類獲取實(shí)際要執(zhí)行的處理器。
?? 步驟2:根據(jù)配置獲取處理器。
上面步驟1通過getHandlerConfigFile()方法獲取到處理器鏈路配置規(guī)則后,再調(diào)用getHandler()獲取處理器。
getHandler()參數(shù)是如上ConfigJson配置的規(guī)則,即步驟1轉(zhuǎn)換成的ProductCheckHandlerConfig對象;根據(jù)ProductCheckHandlerConfig配置規(guī)則轉(zhuǎn)換成處理器鏈路對象。代碼如下:
* 使用Spring注入:所有繼承了AbstractCheckHandler抽象類的Spring Bean都會注入進(jìn)來。Map的Key對應(yīng)Bean的name,Value是name對應(yīng)相應(yīng)的Bean
*/
@Resource
private Map<String, AbstractCheckHandler> handlerMap;
/**
* 獲取處理器
* @param config
* @return
*/
private AbstractCheckHandler getHandler (ProductCheckHandlerConfig config) {
//配置檢查:沒有配置處理器鏈路,則不執(zhí)行校驗(yàn)邏輯
if (Objects.isNull(config)) {
returnnull;
}
//配置錯誤
String handler = config.getHandler();
if (StringUtils.isBlank(handler)) {
returnnull;
}
//配置了不存在的處理器
AbstractCheckHandler abstractCheckHandler = handlerMap.get(config.getHandler());
if (Objects.isNull(abstractCheckHandler)) {
returnnull;
}
//處理器設(shè)置配置Config
abstractCheckHandler.setConfig(config);
//遞歸設(shè)置鏈路處理器
abstractCheckHandler.setNextHandler(this.getHandler(config.getNext()));
return abstractCheckHandler;
}?? ?? 步驟2-1:配置檢查。
代碼14~27行,進(jìn)行了配置的一些檢查操作。如果配置錯誤,則獲取不到對應(yīng)的處理器。代碼23行handlerMap.get(config.getHandler())是從所有處理器映射Map中獲取到對應(yīng)的處理器Spring Bean。
注意第5行代碼,handlerMap存儲了所有的處理器映射,是通過Spring @Resource注解注入進(jìn)來的。注入的規(guī)則是:所有繼承了AbstractCheckHandler抽象類(它是Spring管理的Bean)的子類(子類也是Spring管理的Bean)都會注入進(jìn)來。
注入進(jìn)來的handlerMap中 Map的Key對應(yīng)Bean的name,Value是name對應(yīng)的Bean實(shí)例,也就是實(shí)際的處理器,這里指空值校驗(yàn)處理器、價格校驗(yàn)處理器、庫存校驗(yàn)處理器。如下:
圖片
這樣根據(jù)配置ConfigJson(?? 步驟1:獲取處理器配置)中handler:"priceCheckHandler"的配置,使用handlerMap.get(config.getHandler())便可以獲取到對應(yīng)的處理器Spring Bean對象了。
?? ?? 步驟2-2:保存處理器規(guī)則。
代碼29行,將配置規(guī)則保存到對應(yīng)的處理器中abstractCheckHandler.setConfig(config),子類處理器就持有了配置的規(guī)則。
?? ?? 步驟2-3:遞歸設(shè)置處理器鏈路。
代碼32行,遞歸設(shè)置鏈路上的處理器。
//遞歸設(shè)置鏈路處理器 abstractCheckHandler.setNextHandler(this.getHandler(config.getNext()));這一步可能不太好理解,結(jié)合ConfigJson配置的規(guī)則來看,似乎就很很容易理解了。
圖片
由上而下,NullValueCheckHandler 空值校驗(yàn)處理器通過setNextHandler()方法設(shè)置自己持有的下一節(jié)點(diǎn)的處理器,也就是價格處理器PriceCheckHandler。
接著,PriceCheckHandler價格處理器,同樣需要經(jīng)過步驟2-1配置檢查、步驟2-2保存配置規(guī)則,并且最重要的是,它也需要設(shè)置下一節(jié)點(diǎn)的處理器StockCheckHandler庫存校驗(yàn)處理器。
StockCheckHandler庫存校驗(yàn)處理器也一樣,同樣需要經(jīng)過步驟2-1配置檢查、步驟2-2保存配置規(guī)則,但請注意StockCheckHandler的配置,它的next規(guī)則配置了null,這表示它下面沒有任何處理器要執(zhí)行了,它就是整個鏈路上的最后一個處理節(jié)點(diǎn)。
通過遞歸調(diào)用getHandler()獲取處理器方法,就將整個處理器鏈路對象串聯(lián)起來了。如下:
圖片
友情提示:遞歸雖香,但使用遞歸一定要注意截斷遞歸的條件處理,否則可能造成死循環(huán)哦!
實(shí)際上,getHandler()獲取處理器對象的代碼就是把在配置中心配置的規(guī)則ConfigJson,轉(zhuǎn)換成配置類ProductCheckHandlerConfig對象,再根據(jù)配置類對象,轉(zhuǎn)換成實(shí)際的處理器對象,這個處理器對象持有整個鏈路的調(diào)用順序。
?? 步驟3:客戶端執(zhí)行調(diào)用鏈路。
public class HandlerClient {
public static Result executeChain(AbstractCheckHandler handler, ProductVO param) {
//執(zhí)行處理器
Result handlerResult = handler.handle(param);
if (!handlerResult.isSuccess()) {
System.out.println("HandlerClient 責(zé)任鏈執(zhí)行失敗返回:" + handlerResult.toString());
return handlerResult;
}
return Result.success();
}
}getHandler()獲取完處理器后,整個調(diào)用鏈路的執(zhí)行順序也就確定了,此時,客戶端該干活了!
HandlerClient.executeChain(handler, param)方法是HandlerClient客戶端類執(zhí)行處理器整個調(diào)用鏈路的,并接收處理器鏈路的返回值。
executeChain()通過AbstractCheckHandler.handle()觸發(fā)整個鏈路處理器順序執(zhí)行,如果某個處理器校驗(yàn)沒有通過!handlerResult.isSuccess(),則返回錯誤信息;所有處理器都校驗(yàn)通過,則返回正確信息Result.success()。
總結(jié):串聯(lián)方法調(diào)用流程
基于以上,再通過流程圖來回顧一下整個調(diào)用流程。
圖片
測試:代碼執(zhí)行結(jié)果
場景1:創(chuàng)建商品參數(shù)中有空值(如下skuId參數(shù)為null),鏈路被空值處理器截斷,返回錯誤信息
//創(chuàng)建商品參數(shù)
ProductVO param = ProductVO.builder()
.skuId(null).skuName("華為手機(jī)").Path("http://...")
.price(new BigDecimal(1))
.stock(1)
.build();測試結(jié)果
圖片
場景2:創(chuàng)建商品價格參數(shù)異常(如下price參數(shù)),被價格處理器截斷,返回錯誤信息
ProductVO param = ProductVO.builder()
.skuId(1L).skuName("華為手機(jī)").Path("http://...")
.price(new BigDecimal(-999))
.stock(1)
.build();測試結(jié)果
圖片
場景 3:創(chuàng)建商品庫存參數(shù)異常(如下stock參數(shù)),被庫存處理器截斷,返回錯誤信息。
//創(chuàng)建商品參數(shù),模擬用戶傳入
ProductVO param = ProductVO.builder()
.skuId(1L).skuName("華為手機(jī)").Path("http://...")
.price(new BigDecimal(1))
.stock(-999)
.build();測試結(jié)果
圖片
場景4:創(chuàng)建商品所有處理器校驗(yàn)通過,保存商品。
//創(chuàng)建商品參數(shù),模擬用戶傳入
ProductVO param = ProductVO.builder()
.skuId(1L).skuName("華為手機(jī)").Path("http://...")
.price(new BigDecimal(999))
.stock(1).build();測試結(jié)果

案例二:工作流,費(fèi)用報銷審核流程
同事小賈最近剛出差回來,她迫不及待的就提交了費(fèi)用報銷的流程。根據(jù)金額不同,分為以下幾種審核流程。報銷金額低于1000元,三級部門管理者審批即可,1000到5000元除了三級部門管理者審批,還需要二級部門管理者審批,而5000到10000元還需要一級部門管理者審批。即有以下幾種情況:
- 小賈需報銷500元,三級部門管理者審批即可。
- 小賈需報銷2500元,三級部門管理者審批通過后,還需要二級部門管理者審批,二級部門管理者審批通過后,才完成報銷審批流程。
- 小賈需報銷7500元,三級管理者審批通過后,并且二級管理者審批通過后,流程流轉(zhuǎn)到一級部門管理者進(jìn)行審批,一級管理者審批通過后,即完成了報銷流程。
UML圖
AbstractFlowHandler作為處理器抽象類,抽象了approve()審核方法,一級、二級、三級部門管理者處理器繼承了抽象類,并重寫其approve()審核方法,從而實(shí)現(xiàn)特有的審核邏輯。
圖片
配置類如下所示,每層的處理器都要配置審核人、價格審核規(guī)則(審核的最大、最小金額)、下一級處理人。配置規(guī)則是可以動態(tài)變更的,如果三級部門管理者可以審核的金額增加到2000元,修改一下配置即可動態(tài)生效。
圖片
代碼實(shí)現(xiàn)與案例一相似,感興趣的自己動動小手吧~
責(zé)任鏈的優(yōu)缺點(diǎn)
圖片































