設計模式在美團外賣營銷業務中的實踐

1、前言

隨着美團外賣業務的不斷迭代與發展,外賣用戶數量也在高速地增加。在這個過程當中,外賣營銷發揮了「中流砥柱」的做用,由於用戶的快速增加離不開高效的營銷策略。而因爲市場環境和業務環境的多變,營銷策略每每是複雜多變的,營銷技術團隊做爲營銷業務的支持部門,就須要快速高效地響應營銷策略變動帶來的需求變更。所以,設計並實現易於擴展和維護的營銷系統,是美團外賣營銷技術團隊不懈追求的目標和必修的基本功。html

本文經過自頂向下的方式,來介紹設計模式如何幫助咱們構建一套易擴展、易維護的營銷系統。本文會首先介紹設計模式與領域驅動設計(Domain-Driven Design,如下簡稱爲DDD)之間的關係,而後再闡述外賣營銷業務引入業務中用到的設計模式以及其具體實踐案例。前端

2、設計模式與領域驅動設計

設計一個營銷系統,咱們一般的作法是採用自頂向下的方式來解構業務,爲此咱們引入了DDD。從戰略層面上講,DDD可以指導咱們完成從問題空間到解決方案的剖析,將業務需求映射爲領域上下文以及上下文間的映射關係。從戰術層面上,DDD可以細化領域上下文,並造成有效的、細化的領域模型來指導工程實踐。創建領域模型的一個關鍵意義在於,可以確保不斷擴展和變化的需求在領域模型內不斷地演進和發展,而不至於出現模型的腐化和領域邏輯的外溢。關於DDD的實踐,你們能夠參考此前美團技術團隊推出的《領域驅動設計在互聯網業務開發中的實踐》一文。算法

同時,咱們也須要在代碼工程中貫徹和實現領域模型。由於代碼工程是領域模型在工程實踐中的直觀體現,也是領域模型在技術層面的直接表述。而設計模式,能夠說是鏈接領域模型與代碼工程的一座橋樑,它能有效地解決從領域模型到代碼工程的轉化。spring

爲何說設計模式自然具有成爲領域模型到代碼工程之間橋樑的做用呢?其實,2003年出版的《領域驅動設計》一書的做者Eric Evans在這部開山之做中就已經給出瞭解釋。他認爲,立場不一樣會影響人們如何看待什麼是「模式」。所以,不管是領域驅動模式仍是設計模式,本質上都是「模式」,只是解決的問題不同。站在業務建模的立場上,DDD的模式解決的是如何進行領域建模。而站在代碼實踐的立場上,設計模式主要關注於代碼的設計與實現。既然本質都是模式,那麼它們自然就具備必定的共通之處。編程

所謂「模式」,就是一套反覆被人使用或驗證過的方法論。從抽象或者更宏觀的角度上看,只要符合使用場景而且能解決實際問題,模式應該既能夠應用在DDD中,也能夠應用在設計模式中。事實上,Evans也是這麼作的。他在著做中闡述了Strategy和Composite這兩個傳統的GOF設計模式是如何來解決領域模型建設的。所以,當領域模型須要轉化爲代碼工程時,同構的模式,自然可以將領域模型翻譯成代碼模型。設計模式

3、設計模式在外賣營銷業務中的具體案例

3.1 爲何須要設計模式

營銷業務的特色機器學習

如前文所述,營銷業務與交易等其餘模式相對穩定的業務的區別在於,營銷需求會隨着市場、用戶、環境的不斷變化而進行調整。也正是所以,外賣營銷技術團隊選擇了DDD進行領域建模,並在適用的場景下,用設計模式在代碼工程的層面上實踐和反映了領域模型。以此來作到在支持業務變化的同時,讓領域和代碼模型健康演進,避免模型腐化。ide

理解設計模式學習

軟件設計模式(Design pattern),又稱設計模式,是一套被反覆使用、多數人知曉的、通過分類編目的、代碼設計經驗的總結。使用設計模式是爲了可重用代碼,讓代碼更容易被他人理解,保證代碼可靠性,程序的重用性。能夠理解爲:「世上原本沒有設計模式,用的人多了,便總結出了一套設計模式。」this

設計模式原則

面向對象的設計模式有七大基本原則:

  • 開閉原則(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必定的現金獎勵(如下簡稱返獎)。同時爲了協調成本與收益的關係,返獎會有多個計算策略。邀請下單後臺主要涉及兩個技術要點:

  1. 返獎金額的計算,涉及到不一樣的計算規則。
  2. 從邀請開始到返獎結束的整個流程。

3.2.2 返獎規則與設計模式實踐

業務建模

如圖是返獎規則計算的業務邏輯視圖:

從這份業務邏輯圖中能夠看到返獎金額計算的規則。首先要根據用戶狀態肯定用戶是否知足返獎條件。若是知足返獎條件,則繼續判斷當前用戶屬於新用戶仍是老用戶,從而給予不一樣的獎勵方案。一共涉及如下幾種不一樣的獎勵方案:

新用戶

  • 普通獎勵(給予固定金額的獎勵)
  • 梯度獎(根據用戶邀請的人數給予不一樣的獎勵金額,邀請的人越多,獎勵金額越多)

老用戶

  • 根據老用戶的用戶屬性來計算返獎金額。爲了評估不一樣的邀新效果,老用戶返獎會存在多種返獎機制。

計算完獎勵金額之後,還須要更新用戶的獎金信息,以及通知結算服務對用戶的金額進行結算。這兩個模塊對於全部的獎勵來講都是同樣的。

能夠看到,不管是何種用戶,對於總體返獎流程是不變的,惟一變化的是返獎規則。此處,咱們可參考開閉原則,對於返獎流程保持封閉,對於可能擴展的返獎規則進行開放。咱們將返獎規則抽象爲返獎策略,即針對不一樣用戶類型的不一樣返獎方案,咱們視爲不一樣的返獎策略,不一樣的返獎策略會產生不一樣的返獎金額結果。

在咱們的領域模型裏,返獎策略是一個值對象,咱們經過工廠的方式生產針對不一樣用戶的獎勵策略值對象。下文咱們將介紹以上領域模型的工程實現,即工廠模式策略模式的實際應用。

模式:工廠模式

工廠模式又細分爲工廠方法模式和抽象工廠模式,本文主要介紹工廠方法模式。

模式定義:定義一個用於建立對象的接口,讓子類決定實例化哪個類。工廠方法是一個類的實例化延遲到其子類。

工廠模式通用類圖以下:

咱們經過一段較爲通用的代碼來解釋如何使用工廠模式:

//抽象的產品
public abstract class Product {
    public abstract void method();
}
//定義一個具體的產品 (能夠定義多個具體的產品)
class ProductA extends Product {
    @Override
    public void method() {}  //具體的執行邏輯
}
//抽象的工廠
abstract class Factory<T> {
    abstract Product createProduct(Class<T> c);
}
//具體的工廠能夠生產出相應的產品
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<T> {
    abstract RewardStrategy createStrategy(Class<T> c);
}
​
//具體工廠建立具體的策略
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天以後處理該延遲消息,判斷用戶是否對該訂單進行了退款,若是未退款,對用戶進行返獎。若返獎失敗,後臺還有返獎補償流程,再次進行返獎。其流程以下圖所示:

咱們對上述業務流程進行領域建模:

  1. 在接收到訂單消息後,用戶進入待校驗狀態;
  2. 在校驗後,若校驗經過,用戶進入預返獎狀態,並放入延遲隊列。若校驗未經過,用戶進入不返獎狀態,結束流程;
  3. T+N天后,處理延遲消息,若用戶未退款,進入待返獎狀態。若用戶退款,進入失敗狀態,結束流程;
  4. 執行返獎,若返獎成功,進入完成狀態,結束流程。若返獎不成功,進入待補償狀態;
  5. 待補償狀態的用戶會由任務按期觸發補償機制,直至返獎成功,進入完成狀態,保障流程結束。

能夠看到,咱們經過建模將返獎流程的多個步驟映射爲系統的狀態。對於系統狀態的表述,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 設計模式實踐

業務建模

對於投放業務,就是要在這些資源位中展現符合當前用戶的資源。其流程以下圖所示:

從流程中咱們能夠看到,首先運營人員會配置須要展現的資源,以及對資源進行過濾的規則。咱們資源的過濾規則相對靈活多變,這裏體現爲三點:

  1. 過濾規則大部分可重用,但也會有擴展和變動。
  2. 不一樣資源位的過濾規則和過濾順序是不一樣的。
  3. 同一個資源位因爲業務所處的不一樣階段,過濾規則可能不一樣。

過濾規則自己是一個個的值對象,咱們經過領域服務的方式,操做這些規則值對象完成資源位的過濾邏輯。下圖介紹了資源位在進行用戶特徵相關規則過濾時的過程:

爲了實現過濾規則的解耦,對單個規則值對象的修改封閉,並對規則集合組成的過濾鏈條開放,咱們在資源位過濾的領域服務中引入了責任鏈模式。

模式:責任鏈模式

模式定義:使多個對象都有機會處理請求,從而避免了請求的發送者和接受者之間的耦合關係。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有對象處理它爲止。

責任鏈模式通用類圖以下:

咱們經過一段比較通用的代碼來解釋如何使用責任鏈模式:

//定義一個抽象的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<CORE_ITEM>>{
    //有兩個方法,evaluate用於判斷是否通過規則執行,execute用於執行具體的規則內容。
    public abstract boolean evaluate(T context);
    public abstract void execute(T context) {
}
​
//定義全部的規則具體實現
//規則1:判斷服務可用性
public class ServiceAvailableRule extends BasicRule<UserPortrait, UserPortraitRuleContext> {
    @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<UserPortrait, UserPortraitRuleContext> {
    @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<UserPortrait, UserPortraitRuleContext> {}
//規則4:根據用戶的活躍度進行資源過濾,具體邏輯省略
public class UserPortraitRule extends BasicRule<UserPortrait, UserPortraitRuleContext> {} 
​
//咱們經過spring將這些規則串起來組成一個一個請求鏈
    <bean name="serviceAvailableRule" class="com.dianping.takeaway.ServiceAvailableRule"/>
    <bean name="userGroupValidRule" class="com.dianping.takeaway.UserGroupRule"/>
    <bean name="cityInfoValidRule" class="com.dianping.takeaway.CityInfoRule"/>
    <bean name="userPortraitRule" class="com.dianping.takeaway.UserPortraitRule"/>
      
    <util:list id="userPortraitRuleChain" value-type="com.dianping.takeaway.Rule">
        <ref bean="serviceAvailableRule"/>
        <ref bean="userGroupValidRule"/>
        <ref bean="cityInfoValidRule"/>
        <ref bean="userPortraitRule"/>
    </util:list>
      
//規則執行
public class DefaultRuleEngine{
    @Autowired
    List<BasicRule> userPortraitRuleChain;
​
    public void invokeAll(RuleContext ruleContext) {
        for(Rule rule : userPortraitRuleChain) {
            rule.evaluate(ruleContext)
        }
    }
}

責任鏈模式最重要的優勢就是解耦,將客戶端與處理者分開,客戶端不須要了解是哪一個處理者對事件進行處理,處理者也不須要知道處理的整個流程。在咱們的系統中,後臺的過濾規則會常常變更,規則和規則之間可能也會存在傳遞關係,經過責任鏈模式,咱們將規則與規則分開,將規則與規則之間的傳遞關係經過Spring注入到List中,造成一個鏈的關係。當增長一個規則時,只須要實現BasicRule接口,而後將新增的規則按照順序加入Spring中便可。當刪除時,只需刪除相關規則便可,不須要考慮代碼的其餘邏輯。從而顯著地提升了代碼的靈活性,提升了代碼的開發效率,同時也保證了系統的穩定性。

4、總結

本文從營銷業務出發,介紹了領域模型到代碼工程之間的轉化,從DDD引出了設計模式,詳細介紹了工廠方法模式、策略模式、責任鏈模式以及狀態模式這四種模式在營銷業務中的具體實現。除了這四種模式之外,咱們的代碼工程中還大量使用了代理模式、單例模式、適配器模式等等,例如在咱們對DDD防腐層的實現就使用了適配器模式,經過適配器模式屏蔽了業務邏輯與第三方服務的交互。因篇幅緣由再也不進行過多的闡述。

對於營銷業務來講,業務策略多變致使需求多變是咱們面臨的主要問題。如何應對複雜多變的需求,是咱們提煉領域模型和實現代碼模型時必需要考慮的內容。DDD以及設計模式提供了一套相對完整的方法論幫助咱們完成了領域建模及工程實現。其實,設計模式就像一面鏡子,將領域模型映射到代碼模型中,切實地提升代碼的複用性、可擴展性,也提升了系統的可維護性。

固然,設計模式只是軟件開發領域內多年來的經驗總結,任何一個或簡單或複雜的設計模式都會遵循上述的七大設計原則,只要你們真正理解了七大設計原則,設計模式對咱們來講應該就再也不是一件難事。可是,使用設計模式也不是要求咱們循規蹈矩,只要咱們的代碼模型設計遵循了上述的七大原則,咱們會發現原來咱們的設計中就已經使用了某種設計模式。

5、參考資料

6、做者簡介

吳亮亮,2017年加入美團外賣,美團外賣營銷後臺團隊開發工程師。

招聘信息

美團外賣上海研發中心長期招聘前端、數據倉庫、機器學習/數據挖掘算法工程師,歡迎感興趣的同窗發送簡歷到:tech@meituan.com(郵件標題註明:美團外賣-上海)

相關文章
相關標籤/搜索