本文原文鏈接地址:http://nullpointer.pw/design-patterns-chain-responsibility.html
本文以電商系統訂單金額計算為例,電商訂單最終的金額可能是這樣的
應支付金額=訂單金額-優惠券優惠金額-促銷活動優惠金額-會員權益優惠金額
當然也有可能還會增加其他的計算步驟,使用責任鏈模式來實現訂單金額計算,若增加了其他計算步驟,直接將步驟加入到鏈中即可,而無需改動以前的代碼,最大程度減小出錯的可能性。責任鏈分為純責任鏈與不純責任鏈,在日常開發中,很少有純的責任鏈,所謂純的責任鏈,就是單個鏈上處理器要么獨立處理,要么處理不了交給下一個處理器進行處理。
類型:行為型模式
意圖:為請求創建了一個接收此次請求對象的鏈,讓請求沿着鏈傳遞請求。
使用場景:
- 需要多個對象處理一個請求
角色:
- AbstractHandler: 抽象處理者
- ConcreteHandler:具體處理者
UML
實戰開發
本文參照 Tomcat 源碼中的 Filter Chain 實現責任鏈,使用多個訂單處理器對訂單進行處理。
本文示例 UML 圖
實現抽象處理者
為了簡化示例,代碼中關於優惠金額的計算都采取寫死的方式。
/**
* @author WeJan
* @since 2020-02-11
*/
public abstract class AbstractHandler {
protected abstract void doHandle(OrderHandleContext context, OrderHandlerChain chain);
/**
* 訂單處理器的權重
*/
protected abstract int weight();
}
/**
* 封裝處理器鏈處理元素上下文
*
* @author WeJan
* @since 2020-02-11
*/
@Data
@Accessors(chain = true)
public class OrderHandleContext {
/**
* 當前處理器位於處理器 chain 上的位置
*/
private int pos;
// 訂單號
private String orderNo;
// 訂單金額
private Double amount;
// VIP 等級
private Integer vipLevel;
// 優惠券
private String couponNo;
}
/**
* 封裝處理器權重
* @author WeJan
* @since 2020-03-04
*/
@Getter
@AllArgsConstructor
public enum OrderHandlerWeightEnum {
COUPON(1, "優惠券"),
SALES(2, "促銷活動"),
VIP(3, "VIP"),
;
private Integer code;
private String desc;
}
初始化訂單處理器鏈
/**
* @author WeJan
* @since 2020-03-03
*/
@Component
public class OrderHandlerChain implements ApplicationContextAware {
private List<AbstractHandler> chain = new ArrayList<>(10);
public void handle(OrderHandleContext context) {
if (context.getPos() < chain.size()) {
AbstractHandler handler = chain.get(context.getPos());
// 移動位於處理器鏈中的位置
context.setPos(context.getPos() + 1);
handler.doHandle(context, this);
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String, AbstractHandler> beans = applicationContext.getBeansOfType(AbstractHandler.class);
chain.addAll(beans.values());
// 根據處理器的權重,對處理器鏈中元素進行排序
chain.sort(Comparator.comparingInt(AbstractHandler::weight));
}
}
實現具體處理者
@Component
public class CouponOrderHandler extends AbstractHandler {
@Override
protected void doHandle(OrderHandleContext context, OrderHandlerChain chain) {
if (context.getCouponNo() != null) {
context.setAmount(context.getAmount() - 10);
}
// 調用下一個處理器進行處理
chain.handle(context);
}
@Override
protected int weight() {
return OrderHandlerWeightEnum.COUPON.getCode();
}
}
@Component
public class SalesOrderHandler extends AbstractHandler {
@Override
protected void doHandle(OrderHandleContext context, OrderHandlerChain chain) {
Double amount = context.getAmount();
if (amount > 80) {
context.setAmount(amount * 0.9);
}
// 調用下一個處理器進行處理
chain.handle(context);
}
@Override
protected int weight() {
return OrderHandlerWeightEnum.SALES.getCode();
}
}
@Component
public class VipOrderHandler extends AbstractHandler {
@Override
protected void doHandle(OrderHandleContext context, OrderHandlerChain chain) {
if (context.getVipLevel() > 2) {
context.setAmount(context.getAmount() - 5);
}
// 調用下一個處理器進行處理
chain.handle(context);
}
@Override
protected int weight() {
return OrderHandlerWeightEnum.VIP.getCode();
}
}
測試
@RunWith(SpringRunner.class)
@SpringBootTest(classes = App.class)
public class ChainTest {
@Resource
private OrderHandlerChain chain;
@Test
public void name() {
OrderHandleContext order = new OrderHandleContext()
.setOrderNo("123")
.setAmount(100d)
.setVipLevel(3)
.setCouponNo("111");
chain.handle(order);
System.out.println("訂單最終金額為: " + order.getAmount());
}
}
輸出結果為:
76.0
處理器鏈調用 handle 方法,逐個調用處理器鏈中的處理器的 doHanle 方法,對訂單進行處理,當前處理器處理完畢后,可以選擇是否繼續交由下一個處理器進行處理,即設置 chain.handle(context);
,如果不需要繼續往下處理,不調用此代碼即可。
網上流傳的代碼都是直接在抽象處理器中包含下一個處理器的引用,這樣導致在客戶端使用的時候,就需要手動去逐個 set 下級處理器,手誤很容易造成處理器死循環的情況,也可能出現缺失某個處理器的情況,因而本文參照 Tomcat 源碼中 Filter 的作法,引入了 Chain 類,統一對處理器封裝為鏈,減少客戶端使用時出錯的可能。
總結
鏈式處理的好處在於增加減少新的處理器不會影響其他處理器的邏輯,各個處理器之間相互獨立,可以減小耦合帶來的影響。