一、前言
随着美團外賣業務的不斷疊代與發展,外賣使用者數量也在高速地增長。在這個過程中,外賣營銷發揮了“中流砥柱”的作用,因為使用者的快速增長離不開高效的營銷政策。而由于市場環境和業務環境的多變,營銷政策往往是複雜多變的,營銷技術團隊作為營銷業務的支援部門,就需要快速高效地響應營銷政策變更帶來的需求變動。是以,設計并實作易于擴充和維護的營銷系統,是美團外賣營銷技術團隊不懈追求的目标和必修的基本功。
本文通過自頂向下的方式,來介紹設計模式如何幫助我們建構一套易擴充、易維護的營銷系統。本文會首先介紹設計模式與領域驅動設計(Domain-Driven Design,以下簡稱為DDD)之間的關系,然後再闡述外賣營銷業務引入業務中用到的設計模式以及其具體實踐案例。
二、設計模式與領域驅動設計
設計一個營銷系統,我們通常的做法是采用自頂向下的方式來解構業務,為此我們引入了DDD。從戰略層面上講,DDD能夠指導我們完成從問題空間到解決方案的剖析,将業務需求映射為領域上下文以及上下文間的映射關系。從戰術層面上,DDD能夠細化領域上下文,并形成有效的、細化的領域模型來指導工程實踐。建立領域模型的一個關鍵意義在于,能夠確定不斷擴充和變化的需求在領域模型内不斷地演進和發展,而不至于出現模型的腐化和領域邏輯的外溢。關于DDD的實踐,大家可以參考此前美團技術團隊推出的《領域驅動設計在網際網路業務開發中的實踐》一文。
同時,我們也需要在代碼工程中貫徹和實作領域模型。因為代碼工程是領域模型在工程實踐中的直覺展現,也是領域模型在技術層面的直接表述。而設計模式,可以說是連接配接領域模型與代碼工程的一座橋梁,它能有效地解決從領域模型到代碼工程的轉化。
為什麼說設計模式天然具備成為領域模型到代碼工程之間橋梁的作用呢?其實,2003年出版的《領域驅動設計》一書的作者Eric Evans在這部開山之作中就已經給出了解釋。他認為,立場不同會影響人們如何看待什麼是“模式”。是以,無論是領域驅動模式還是設計模式,本質上都是“模式”,隻是解決的問題不一樣。站在業務模組化的立場上,DDD的模式解決的是如何進行領域模組化。而站在代碼實踐的立場上,設計模式主要關注于代碼的設計與實作。既然本質都是模式,那麼它們天然就具有一定的共通之處。
所謂“模式”,就是一套反複被人使用或驗證過的方法論。從抽象或者更宏觀的角度上看,隻要符合使用場景并且能解決實際問題,模式應該既可以應用在DDD中,也可以應用在設計模式中。事實上,Evans也是這麼做的。他在著作中闡述了Strategy和Composite這兩個傳統的GOF設計模式是如何來解決領域模型建設的。是以,當領域模型需要轉化為代碼工程時,同構的模式,天然能夠将領域模型翻譯成代碼模型。
三、設計模式在外賣營銷業務中的具體案例
3.1 為什麼需要設計模式
營銷業務的特點
如前文所述,營銷業務與交易等其他模式相對穩定的業務的差別在于,營銷需求會随着市場、使用者、環境的不斷變化而進行調整。也正是是以,外賣營銷技術團隊選擇了DDD進行領域模組化,并在适用的場景下,用設計模式在代碼工程的層面上實踐和反映了領域模型。以此來做到在支援業務變化的同時,讓領域和代碼模型健康演進,避免模型腐化。
了解設計模式
軟體設計模式(Design pattern),又稱設計模式,是一套被反複使用、多數人知曉的、經過分類編目的、代碼設計經驗的總結。使用設計模式是為了可重用代碼,讓代碼更容易被他人了解,保證代碼可靠性,程式的重用性。可以了解為:“世上本來沒有設計模式,用的人多了,便總結出了一套設計模式。”
設計模式原則
面向對象的設計模式有七大基本原則:
- 開閉原則(Open Closed Principle,OCP)
- 單一職責原則(Single Responsibility Principle, SRP)
- 裡氏代換原則(Liskov Substitution Principle,LSP)
- 依賴倒轉原則(Dependency Inversion Principle,DIP)
- 接口隔離原則(Interface Segregation Principle,ISP)
- 合成/聚合複用原則(Composite/Aggregate Reuse Principle,CARP)
- 最少知識原則(Least Knowledge Principle,LKP)或者迪米特法則(Law of Demeter,LOD)
簡單了解就是:開閉原則是總綱,它指導我們要對擴充開放,對修改關閉;單一職責原則指導我們實作類要職責單一;裡氏替換原則指導我們不要破壞繼承體系;依賴倒置原則指導我們要面向接口程式設計;接口隔離原則指導我們在設計接口的時候要精簡單一;迪米特法則指導我們要降低耦合。
設計模式就是通過這七個原則,來指導我們如何做一個好的設計。但是設計模式不是一套“奇技淫巧”,它是一套方法論,一種高内聚、低耦合的設計思想。我們可以在此基礎上自由的發揮,甚至設計出自己的一套設計模式。
當然,學習設計模式或者是在工程中實踐設計模式,必須深入到某一個特定的業務場景中去,再結合對業務場景的了解和領域模型的建立,才能體會到設計模式思想的精髓。如果脫離具體的業務邏輯去學習或者使用設計模式,那是極其空洞的。接下來我們将通過外賣營銷業務的實踐,來探讨如何用設計模式來實作可重用、易維護的代碼。
3.2 “邀請下單”業務中設計模式的實踐
3.2.1 業務簡介
“邀請下單”是美團外賣使用者邀請其他使用者下單後給予獎勵的平台。即使用者A邀請使用者B,并且使用者B在美團下單後,給予使用者A一定的現金獎勵(以下簡稱返獎)。同時為了協調成本與收益的關系,返獎會有多個計算政策。邀請下單背景主要涉及兩個技術要點:
- 返獎金額的計算,涉及到不同的計算規則。
- 從邀請開始到返獎結束的整個流程。
3.2.2 返獎規則與設計模式實踐
業務模組化
如圖是返獎規則計算的業務邏輯視圖:
從這份業務邏輯圖中可以看到返獎金額計算的規則。首先要根據使用者狀态确定使用者是否滿足返獎條件。如果滿足返獎條件,則繼續判斷目前使用者屬于新使用者還是老使用者,進而給予不同的獎勵方案。一共涉及以下幾種不同的獎勵方案:
新使用者
- 普通獎勵(給予固定金額的獎勵)
- 梯度獎(根據使用者邀請的人數給予不同的獎勵金額,邀請的人越多,獎勵金額越多)
老使用者
- 根據老使用者的使用者屬性來計算返獎金額。為了評估不同的邀新效果,老使用者返獎會存在多種返獎機制。
計算完獎勵金額以後,還需要更新使用者的獎金資訊,以及通知結算服務對使用者的金額進行結算。這兩個子產品對于所有的獎勵來說都是一樣的。
可以看到,無論是何種使用者,對于整體返獎流程是不變的,唯一變化的是返獎規則。此處,我們可參考開閉原則,對于返獎流程保持封閉,對于可能擴充的返獎規則進行開放。我們将返獎規則抽象為返獎政策,即針對不同使用者類型的不同返獎方案,我們視為不同的返獎政策,不同的返獎政策會産生不同的返獎金額結果。
在我們的領域模型裡,返獎政策是一個值對象,我們通過工廠的方式生産針對不同使用者的獎勵政策值對象。下文我們将介紹以上領域模型的工程實作,即工廠模式和政策模式的實際應用。
模式:工廠模式
工廠模式又細分為工廠方法模式和抽象工廠模式,本文主要介紹工廠方法模式。
模式定義:定義一個用于建立對象的接口,讓子類決定執行個體化哪一個類。工廠方法是一個類的執行個體化延遲到其子類。
工廠模式通用類圖如下:
我們通過一段較為通用的代碼來解釋如何使用工廠模式:
//抽象的産品
public abstract class Product {
public abstract void method();
}
//定義一個具體的産品 (可以定義多個具體的産品)
class ProductA extends Product {
@Override
public void method() {} //具體的執行邏輯
}
//抽象的工廠
abstract class Factory{
abstract Product createProduct(Classc);
}
//具體的工廠可以生産出相應的産品
class FactoryA extends Factory{
@Override
Product createProduct(Class c) {
Product product = (Product) Class.forName(c.getName()).newInstance();
return product;
}
}
模式:政策模式
模式定義:定義一系列算法,将每個算法都封裝起來,并且它們可以互換。政策模式是一種對象行為模式。
政策模式通用類圖如下:
我們通過一段比較通用的代碼來解釋怎麼使用政策模式:
//定義一個政策接口
public interface Strategy {
void strategyImplementation();
}
//具體的政策實作(可以定義多個具體的政策實作)
public class StrategyA implements Strategy{
@Override
public void strategyImplementation() {
System.out.println("正在執行政策A");
}
}
//封裝政策,屏蔽高層子產品對政策、算法的直接通路,屏蔽可能存在的政策變化
public class Context {
private Strategy strategy = null;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void doStrategy() {
strategy.strategyImplementation();
}
}
工程實踐
通過上文介紹的返獎業務模型,我們可以看到返獎的主流程就是選擇不同的返獎政策的過程,每個返獎政策都包括返獎金額計算、更新使用者獎金資訊、以及結算這三個步驟。 我們可以使用工廠模式生産出不同的政策,同時使用政策模式來進行不同的政策執行。首先确定我們需要生成出n種不同的返獎政策,其編碼如下:
//抽象政策
public abstract class RewardStrategy {
public abstract void reward(long userId);
public void insertRewardAndSettlement(long userId, int reward) {} ; //更新使用者資訊以及結算
}
//新使用者返獎具體政策A
public class newUserRewardStrategyA extends RewardStrategy {
@Override
public void reward(long userId) {} //具體的計算邏輯,...
}
//老使用者返獎具體政策A
public class OldUserRewardStrategyA extends RewardStrategy {
@Override
public void reward(long userId) {} //具體的計算邏輯,...
}
//抽象工廠
public abstract class StrategyFactory{
abstract RewardStrategy createStrategy(Classc);
}
//具體工廠建立具體的政策
public class FactorRewardStrategyFactory extends StrategyFactory {
@Override
RewardStrategy createStrategy(Class c) {
RewardStrategy product = null;
try {
product = (RewardStrategy) Class.forName(c.getName()).newInstance();
} catch (Exception e) {}
return product;
}
}
通過工廠模式生産出具體的政策之後,根據我們之前的介紹,很容易就可以想到使用政策模式來執行我們的政策。具體代碼如下:
public class RewardContext {
private RewardStrategy strategy;
public RewardContext(RewardStrategy strategy) {
this.strategy = strategy;
}
public void doStrategy(long userId) {
int rewardMoney = strategy.reward(userId);
insertRewardAndSettlement(long userId, int reward) {
insertReward(userId, rewardMoney);
settlement(userId);
}
}
}
接下來我們将工廠模式和政策模式結合在一起,就完成了整個返獎的過程:
public class InviteRewardImpl {
//返獎主流程
public void sendReward(long userId) {
FactorRewardStrategyFactory strategyFactory = new FactorRewardStrategyFactory(); //建立工廠
Invitee invitee = getInviteeByUserId(userId); //根據使用者id查詢使用者資訊
if (invitee.userType == UserTypeEnum.NEW_USER) { //新使用者返獎政策
NewUserBasicReward newUserBasicReward = (NewUserBasicReward) strategyFactory.createStrategy(NewUserBasicReward.class);
RewardContext rewardContext = new RewardContext(newUserBasicReward);
rewardContext.doStrategy(userId); //執行返獎政策
}if(invitee.userType == UserTypeEnum.OLD_USER){} //老使用者返獎政策,...
}
}
工廠方法模式幫助我們直接産生一個具體的政策對象,政策模式幫助我們保證這些政策對象可以自由地切換而不需要改動其他邏輯,進而達到解耦的目的。通過這兩個模式的組合,當我們系統需要增加一種返獎政策時,隻需要實作RewardStrategy接口即可,無需考慮其他的改動。當我們需要改變政策時,隻要修改政策的類名即可。不僅增強了系統的可擴充性,避免了大量的條件判斷,而且從真正意義上達到了高内聚、低耦合的目的。
3.2.3 返獎流程與設計模式實踐
當受邀人在接受邀請人的邀請并且下單後,返獎背景接收到受邀人的下單記錄,此時邀請人也進入返獎流程。首先我們訂閱使用者訂單消息并對訂單進行返獎規則校驗。例如,是否使用紅包下單,是否在紅包有效期内下單,訂單是否滿足一定的優惠金額等等條件。當滿足這些條件以後,我們将訂單資訊放入延遲隊列中進行後續處理。經過T+N天之後處理該延遲消息,判斷使用者是否對該訂單進行了退款,如果未退款,對使用者進行返獎。若返獎失敗,背景還有返獎補償流程,再次進行返獎。其流程如下圖所示:
我們對上述業務流程進行領域模組化:
- 在接收到訂單消息後,使用者進入待校驗狀态;
- 在校驗後,若校驗通過,使用者進入預返獎狀态,并放入延遲隊列。若校驗未通過,使用者進入不返獎狀态,結束流程;
- T+N天後,處理延遲消息,若使用者未退款,進入待返獎狀态。若使用者退款,進入失敗狀态,結束流程;
- 執行返獎,若返獎成功,進入完成狀态,結束流程。若返獎不成功,進入待補償狀态;
- 待補償狀态的使用者會由任務定期觸發補償機制,直至返獎成功,進入完成狀态,保障流程結束。
可以看到,我們通過模組化将返獎流程的多個步驟映射為系統的狀态。對于系統狀态的表述,DDD中常用到的概念是領域事件,另外也提及過事件溯源的實踐方案。當然,在設計模式中,也有一種能夠表述系統狀态的代碼模型,那就是狀态模式。在邀請下單系統中,我們的主要流程是返獎。對于返獎,每一個狀态要進行的動作和操作都是不同的。是以,使用狀态模式,能夠幫助我們對系統狀态以及狀态間的流轉進行統一的管理和擴充。
模式:狀态模式
模式定義:當一個對象内在狀态改變時允許其改變行為,這個對象看起來像改變了其類。
狀态模式的通用類圖如下圖所示:
對比政策模式的類型會發現和狀态模式的類圖很類似,但實際上有很大的差別,具體展現在concrete class上。政策模式通過Context産生唯一一個ConcreteStrategy作用于代碼中,而狀态模式則是通過context組織多個ConcreteState形成一個狀态轉換圖來實作業務邏輯。接下來,我們通過一段通用代碼來解釋怎麼使用狀态模式:
//定義一個抽象的狀态類
public abstract class State {
Context context;
public void setContext(Context context) {
this.context = context;
}
public abstract void handle1();
public abstract void handle2();
}
//定義狀态A
public class ConcreteStateA extends State {
@Override
public void handle1() {} //本狀态下必須要處理的事情
@Override
public void handle2() {
super.context.setCurrentState(Context.contreteStateB); //切換到狀态B
super.context.handle2(); //執行狀态B的任務
}
}
//定義狀态B
public class ConcreteStateB extends State {
@Override
public void handle2() {} //本狀态下必須要處理的事情,...
@Override
public void handle1() {
super.context.setCurrentState(Context.contreteStateA); //切換到狀态A
super.context.handle1(); //執行狀态A的任務
}
}
//定義一個上下文管理環境
public class Context {
public final static ConcreteStateA contreteStateA = new ConcreteStateA();
public final static ConcreteStateB contreteStateB = new ConcreteStateB();
private State CurrentState;
public State getCurrentState() {return CurrentState;}
public void setCurrentState(State currentState) {
this.CurrentState = currentState;
this.CurrentState.setContext(this);
}
public void handle1() {this.CurrentState.handle1();}
public void handle2() {this.CurrentState.handle2();}
}
//定義client執行
public class client {
public static void main(String[] args) {
Context context = new Context();
context.setCurrentState(new ContreteStateA());
context.handle1();
context.handle2();
}
}
通過前文對狀态模式的簡介,我們可以看到當狀态之間的轉換在不是非常複雜的情況下,通用的狀态模式存在大量的與狀态無關的動作進而産生大量的無用代碼。在我們的實踐中,一個狀态的下遊不會涉及特别多的狀态裝換,是以我們簡化了狀态模式。目前的狀态隻負責目前狀态要處理的事情,狀态的流轉則由第三方類負責。其實踐代碼如下:
//返獎狀态執行的上下文
public class RewardStateContext {
private RewardState rewardState;
public void setRewardState(RewardState currentState) {this.rewardState = currentState;}
public RewardState getRewardState() {return rewardState;}
public void echo(RewardStateContext context, Request request) {
rewardState.doReward(context, request);
}
}
public abstract class RewardState {
abstract void doReward(RewardStateContext context, Request request);
}
//待校驗狀态
public class OrderCheckState extends RewardState {
@Override
public void doReward(RewardStateContext context, Request request) {
orderCheck(context, request); //對進來的訂單進行校驗,判斷是否用券,是否滿足優惠條件等等
}
}
//待補償狀态
public class CompensateRewardState extends RewardState {
@Override
public void doReward(RewardStateContext context, Request request) {
compensateReward(context, request); //返獎失敗,需要對使用者進行返獎補償
}
}
//預返獎狀态,待返獎狀态,成功狀态,失敗狀态(此處邏輯省略)
//..
public class InviteRewardServiceImpl {
public boolean sendRewardForInvtee(long userId, long orderId) {
Request request = new Request(userId, orderId);
RewardStateContext rewardContext = new RewardStateContext();
rewardContext.setRewardState(new OrderCheckState());
rewardContext.echo(rewardContext, request); //開始返獎,訂單校驗
//此處的if-else邏輯隻是為了表達狀态的轉換過程,并非實際的業務邏輯
if (rewardContext.isResultFlag()) { //如果訂單校驗成功,進入預返獎狀态
rewardContext.setRewardState(new BeforeRewardCheckState());
rewardContext.echo(rewardContext, request);
} else {//如果訂單校驗失敗,進入返獎失敗流程,...
rewardContext.setRewardState(new RewardFailedState());
rewardContext.echo(rewardContext, request);
return false;
}
if (rewardContext.isResultFlag()) {//預返獎檢查成功,進入待返獎流程,...
rewardContext.setRewardState(new SendRewardState());
rewardContext.echo(rewardContext, request);
} else { //如果預返獎檢查失敗,進入返獎失敗流程,...
rewardContext.setRewardState(new RewardFailedState());
rewardContext.echo(rewardContext, request);
return false;
}
if (rewardContext.isResultFlag()) { //返獎成功,進入返獎結束流程,...
rewardContext.setRewardState(new RewardSuccessState());
rewardContext.echo(rewardContext, request);
} else { //返獎失敗,進入返獎補償階段,...
rewardContext.setRewardState(new CompensateRewardState());
rewardContext.echo(rewardContext, request);
}
if (rewardContext.isResultFlag()) { //補償成功,進入返獎完成階段,...
rewardContext.setRewardState(new RewardSuccessState());
rewardContext.echo(rewardContext, request);
} else { //補償失敗,仍然停留在目前态,直至補償成功(或多次補償失敗後人工介入處理)
rewardContext.setRewardState(new CompensateRewardState());
rewardContext.echo(rewardContext, request);
}
return true;
}
}
狀态模式的核心是封裝,将狀态以及狀态轉換邏輯封裝到類的内部來實作,也很好的展現了“開閉原則”和“單一職責原則”。每一個狀态都是一個子類,不管是修改還是增加狀态,隻需要修改或者增加一個子類即可。在我們的應用場景中,狀态數量以及狀态轉換遠比上述例子複雜,通過“狀态模式”避免了大量的if-else代碼,讓我們的邏輯變得更加清晰。同時由于狀态模式的良好的封裝性以及遵循的設計原則,讓我們在複雜的業務場景中,能夠遊刃有餘地管理各個狀态。
3.3 點評外賣投放系統中設計模式的實踐
3.3.1 業務簡介
繼續舉例,點評App的外賣頻道中會預留多個資源位為營銷使用,向使用者展示一些比較精品美味的外賣食品,為了增加使用者點外賣的意向。當使用者點選點評首頁的“美團外賣”入口時,資源位開始加載,會通過一些規則來篩選出合适的展示Banner。
3.3.2 設計模式實踐
對于投放業務,就是要在這些資源位中展示符合目前使用者的資源。其流程如下圖所示:
從流程中我們可以看到,首先營運人員會配置需要展示的資源,以及對資源進行過濾的規則。我們資源的過濾規則相對靈活多變,這裡展現為三點:
- 過濾規則大部分可重用,但也會有擴充和變更。
- 不同資源位的過濾規則和過濾順序是不同的。
- 同一個資源位由于業務所處的不同階段,過濾規則可能不同。
過濾規則本身是一個個的值對象,我們通過領域服務的方式,操作這些規則值對象完成資源位的過濾邏輯。下圖介紹了資源位在進行使用者特征相關規則過濾時的過程:
為了實作過濾規則的解耦,對單個規則值對象的修改封閉,并對規則集合組成的過濾鍊條開放,我們在資源位過濾的領域服務中引入了責任鍊模式。
模式:責任鍊模式
模式定義:使多個對象都有機會處理請求,進而避免了請求的發送者和接受者之間的耦合關系。将這些對象連成一條鍊,并沿着這條鍊傳遞該請求,直到有對象處理它為止。
責任鍊模式通用類圖如下:
我們通過一段比較通用的代碼來解釋如何使用責任鍊模式:
//定義一個抽象的handle
public abstract class Handler {
private Handler nextHandler; //指向下一個處理者
private int level; //處理者能夠處理的級别
public Handler(int level) {
this.level = level;
}
public void setNextHandler(Handler handler) {
this.nextHandler = handler;
}
// 處理請求傳遞,注意final,子類不可重寫
public final void handleMessage(Request request) {
if (level == request.getRequstLevel()) {
this.echo(request);
} else {
if (this.nextHandler != null) {
this.nextHandler.handleMessage(request);
} else {
System.out.println("已經到最盡頭了");
}
}
}
// 抽象方法,子類實作
public abstract void echo(Request request);
}
// 定義一個具體的handleA
public class HandleRuleA extends Handler {
public HandleRuleA(int level) {
super(level);
}
@Override
public void echo(Request request) {
System.out.println("我是處理者1,我正在處理A規則");
}
}
//定義一個具體的handleB
public class HandleRuleB extends Handler {} //...
//用戶端實作
class Client {
public static void main(String[] args) {
HandleRuleA handleRuleA = new HandleRuleA(1);
HandleRuleB handleRuleB = new HandleRuleB(2);
handleRuleA.setNextHandler(handleRuleB); //這是重點,将handleA和handleB串起來
handleRuleA.echo(new Request());
}
}
下面通過代碼向大家展示如何實作這一套流程:
//定義一個抽象的規則
public abstract class BasicRule<CORE_ITEM, T extends RuleContext>{
//有兩個方法,evaluate用于判斷是否經過規則執行,execute用于執行具體的規則内容。
public abstract boolean evaluate(T context);
public abstract void execute(T context) {
}
//定義所有的規則具體實作
//規則1:判斷服務可用性
public class ServiceAvailableRule extends BasicRule{
@Override
public boolean evaluate(UserPortraitRuleContext context) {
TakeawayUserPortraitBasicInfo basicInfo = context.getBasicInfo();
if (basicInfo.isServiceFail()) {
return false;
}
return true;
}
@Override
public void execute(UserPortraitRuleContext context) {}
}
//規則2:判斷目前使用者屬性是否符合目前資源位投放的使用者屬性要求
public class UserGroupRule extends BasicRule{
@Override
public boolean evaluate(UserPortraitRuleContext context) {}
@Override
public void execute(UserPortraitRuleContext context) {
UserPortrait userPortraitPO = context.getData();
if(userPortraitPO.getUserGroup() == context.getBasicInfo().getUserGroup().code) {
context.setValid(true);
} else {
context.setValid(false);
}
}
}
//規則3:判斷目前使用者是否在投放城市,具體邏輯省略
public class CityInfoRule extends BasicRule{}
//規則4:根據使用者的活躍度進行資源過濾,具體邏輯省略
public class UserPortraitRule extends BasicRule{}
//我們通過spring将這些規則串起來組成一個一個請求鍊//規則執行
public class DefaultRuleEngine{
@Autowired
ListuserPortraitRuleChain;
public void invokeAll(RuleContext ruleContext) {
for(Rule rule : userPortraitRuleChain) {
rule.evaluate(ruleContext)
}
}
}
責任鍊模式最重要的優點就是解耦,将用戶端與處理者分開,用戶端不需要了解是哪個處理者對事件進行處理,處理者也不需要知道處理的整個流程。在我們的系統中,背景的過濾規則會經常變動,規則和規則之間可能也會存在傳遞關系,通過責任鍊模式,我們将規則與規則分開,将規則與規則之間的傳遞關系通過Spring注入到List中,形成一個鍊的關系。當增加一個規則時,隻需要實作BasicRule接口,然後将新增的規則按照順序加入Spring中即可。當删除時,隻需删除相關規則即可,不需要考慮代碼的其他邏輯。進而顯著地提高了代碼的靈活性,提高了代碼的開發效率,同時也保證了系統的穩定性。