<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          設(shè)計(jì)模式在外賣營銷業(yè)務(wù)中的實(shí)踐

          共 27933字,需瀏覽 56分鐘

           ·

          2021-05-18 21:29


          業(yè)務(wù)策略多變導(dǎo)致需求多變,是業(yè)界很多技術(shù)團(tuán)隊(duì)面臨的最具挑戰(zhàn)的問題之一。那么如何設(shè)計(jì)一套易于擴(kuò)展和維護(hù)的營銷系統(tǒng)呢?


          今天的文章來自美團(tuán)外賣營銷技術(shù)團(tuán)隊(duì),他們分享了從領(lǐng)域模型到代碼工程之間的轉(zhuǎn)化,從DDD引出了設(shè)計(jì)模式,并詳細(xì)介紹了工廠方法模式、策略模式、責(zé)任鏈模式以及狀態(tài)模式這四種模式在美團(tuán)營銷業(yè)務(wù)中的具體實(shí)現(xiàn),將理論與實(shí)踐進(jìn)行了一次深度結(jié)合。

          一、前言

          隨著美團(tuán)外賣業(yè)務(wù)的不斷迭代與發(fā)展,外賣用戶數(shù)量也在高速地增長。在這個(gè)過程中,外賣營銷發(fā)揮了“中流砥柱”的作用,因?yàn)橛脩舻目焖僭鲩L離不開高效的營銷策略。而由于市場環(huán)境和業(yè)務(wù)環(huán)境的多變,營銷策略往往是復(fù)雜多變的,營銷技術(shù)團(tuán)隊(duì)作為營銷業(yè)務(wù)的支持部門,就需要快速高效地響應(yīng)營銷策略變更帶來的需求變動(dòng)。因此,設(shè)計(jì)并實(shí)現(xiàn)易于擴(kuò)展和維護(hù)的營銷系統(tǒng),是美團(tuán)外賣營銷技術(shù)團(tuán)隊(duì)不懈追求的目標(biāo)和必修的基本功。

          本文通過自頂向下的方式,來介紹設(shè)計(jì)模式如何幫助我們構(gòu)建一套易擴(kuò)展、易維護(hù)的營銷系統(tǒng)。本文會(huì)首先介紹設(shè)計(jì)模式與領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)(Domain-Driven Design,以下簡稱為DDD)之間的關(guān)系,然后再闡述外賣營銷業(yè)務(wù)引入業(yè)務(wù)中用到的設(shè)計(jì)模式以及其具體實(shí)踐案例。

          二、設(shè)計(jì)模式與領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)

          設(shè)計(jì)一個(gè)營銷系統(tǒng),我們通常的做法是采用自頂向下的方式來解構(gòu)業(yè)務(wù),為此我們引入了DDD。從戰(zhàn)略層面上講,DDD能夠指導(dǎo)我們完成從問題空間到解決方案的剖析,將業(yè)務(wù)需求映射為領(lǐng)域上下文以及上下文間的映射關(guān)系。從戰(zhàn)術(shù)層面上,DDD能夠細(xì)化領(lǐng)域上下文,并形成有效的、細(xì)化的領(lǐng)域模型來指導(dǎo)工程實(shí)踐。建立領(lǐng)域模型的一個(gè)關(guān)鍵意義在于,能夠確保不斷擴(kuò)展和變化的需求在領(lǐng)域模型內(nèi)不斷地演進(jìn)和發(fā)展,而不至于出現(xiàn)模型的腐化和領(lǐng)域邏輯的外溢。關(guān)于DDD的實(shí)踐,大家可以參考此前美團(tuán)技術(shù)團(tuán)隊(duì)推出的《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)在互聯(lián)網(wǎng)業(yè)務(wù)開發(fā)中的實(shí)踐》一文。

          同時(shí),我們也需要在代碼工程中貫徹和實(shí)現(xiàn)領(lǐng)域模型。因?yàn)榇a工程是領(lǐng)域模型在工程實(shí)踐中的直觀體現(xiàn),也是領(lǐng)域模型在技術(shù)層面的直接表述。而設(shè)計(jì)模式,可以說是連接領(lǐng)域模型與代碼工程的一座橋梁,它能有效地解決從領(lǐng)域模型到代碼工程的轉(zhuǎn)化。

          為什么說設(shè)計(jì)模式天然具備成為領(lǐng)域模型到代碼工程之間橋梁的作用呢?其實(shí),2003年出版的《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)》一書的作者Eric Evans在這部開山之作中就已經(jīng)給出了解釋。他認(rèn)為,立場不同會(huì)影響人們?nèi)绾慰创裁词恰澳J健薄R虼耍瑹o論是領(lǐng)域驅(qū)動(dòng)模式還是設(shè)計(jì)模式,本質(zhì)上都是“模式”,只是解決的問題不一樣。站在業(yè)務(wù)建模的立場上,DDD的模式解決的是如何進(jìn)行領(lǐng)域建模。而站在代碼實(shí)踐的立場上,設(shè)計(jì)模式主要關(guān)注于代碼的設(shè)計(jì)與實(shí)現(xiàn)。既然本質(zhì)都是模式,那么它們天然就具有一定的共通之處。

          所謂“模式”,就是一套反復(fù)被人使用或驗(yàn)證過的方法論。從抽象或者更宏觀的角度上看,只要符合使用場景并且能解決實(shí)際問題,模式應(yīng)該既可以應(yīng)用在DDD中,也可以應(yīng)用在設(shè)計(jì)模式中。事實(shí)上,Evans也是這么做的。他在著作中闡述了Strategy和Composite這兩個(gè)傳統(tǒng)的GOF設(shè)計(jì)模式是如何來解決領(lǐng)域模型建設(shè)的。因此,當(dāng)領(lǐng)域模型需要轉(zhuǎn)化為代碼工程時(shí),同構(gòu)的模式,天然能夠?qū)㈩I(lǐng)域模型翻譯成代碼模型。

          三、設(shè)計(jì)模式在外賣營銷業(yè)務(wù)中的具體案例

          3.1 為什么需要設(shè)計(jì)模式

          營銷業(yè)務(wù)的特點(diǎn)

          如前文所述,營銷業(yè)務(wù)與交易等其他模式相對穩(wěn)定的業(yè)務(wù)的區(qū)別在于,營銷需求會(huì)隨著市場、用戶、環(huán)境的不斷變化而進(jìn)行調(diào)整。也正是因此,外賣營銷技術(shù)團(tuán)隊(duì)選擇了DDD進(jìn)行領(lǐng)域建模,并在適用的場景下,用設(shè)計(jì)模式在代碼工程的層面上實(shí)踐和反映了領(lǐng)域模型。以此來做到在支持業(yè)務(wù)變化的同時(shí),讓領(lǐng)域和代碼模型健康演進(jìn),避免模型腐化。

          理解設(shè)計(jì)模式

          軟件設(shè)計(jì)模式(Design pattern),又稱設(shè)計(jì)模式,是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過分類編目的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。使用設(shè)計(jì)模式是為了可重用代碼,讓代碼更容易被他人理解,保證代碼可靠性,程序的重用性。可以理解為:“世上本來沒有設(shè)計(jì)模式,用的人多了,便總結(jié)出了一套設(shè)計(jì)模式。”

          設(shè)計(jì)模式原則

          面向?qū)ο蟮脑O(shè)計(jì)模式有七大基本原則:

          • 開閉原則(Open Closed Principle,OCP

          • 單一職責(zé)原則(Single Responsibility Principle, SRP

          • 里氏代換原則(Liskov Substitution Principle,LSP

          • 依賴倒轉(zhuǎn)原則(Dependency Inversion Principle,DIP

          • 接口隔離原則(Interface Segregation Principle,ISP

          • 合成/聚合復(fù)用原則(Composite/Aggregate Reuse Principle,CARP

          • 最少知識原則(Least Knowledge Principle,LKP)或者迪米特法則(Law of Demeter,LOD

          簡單理解就是:開閉原則是總綱,它指導(dǎo)我們要對擴(kuò)展開放,對修改關(guān)閉;單一職責(zé)原則指導(dǎo)我們實(shí)現(xiàn)類要職責(zé)單一;里氏替換原則指導(dǎo)我們不要破壞繼承體系;依賴倒置原則指導(dǎo)我們要面向接口編程;接口隔離原則指導(dǎo)我們在設(shè)計(jì)接口的時(shí)候要精簡單一;迪米特法則指導(dǎo)我們要降低耦合。

          設(shè)計(jì)模式就是通過這七個(gè)原則,來指導(dǎo)我們?nèi)绾巫鲆粋€(gè)好的設(shè)計(jì)。但是設(shè)計(jì)模式不是一套“奇技淫巧”,它是一套方法論,一種高內(nèi)聚、低耦合的設(shè)計(jì)思想。我們可以在此基礎(chǔ)上自由的發(fā)揮,甚至設(shè)計(jì)出自己的一套設(shè)計(jì)模式。

          當(dāng)然,學(xué)習(xí)設(shè)計(jì)模式或者是在工程中實(shí)踐設(shè)計(jì)模式,必須深入到某一個(gè)特定的業(yè)務(wù)場景中去,再結(jié)合對業(yè)務(wù)場景的理解和領(lǐng)域模型的建立,才能體會(huì)到設(shè)計(jì)模式思想的精髓。如果脫離具體的業(yè)務(wù)邏輯去學(xué)習(xí)或者使用設(shè)計(jì)模式,那是極其空洞的。接下來我們將通過外賣營銷業(yè)務(wù)的實(shí)踐,來探討如何用設(shè)計(jì)模式來實(shí)現(xiàn)可重用、易維護(hù)的代碼。

          3.2 “邀請下單”業(yè)務(wù)中設(shè)計(jì)模式的實(shí)踐

          3.2.1 業(yè)務(wù)簡介

          “邀請下單”是美團(tuán)外賣用戶邀請其他用戶下單后給予獎(jiǎng)勵(lì)的平臺。即用戶A邀請用戶B,并且用戶B在美團(tuán)下單后,給予用戶A一定的現(xiàn)金獎(jiǎng)勵(lì)(以下簡稱返獎(jiǎng))。同時(shí)為了協(xié)調(diào)成本與收益的關(guān)系,返獎(jiǎng)會(huì)有多個(gè)計(jì)算策略。邀請下單后臺主要涉及兩個(gè)技術(shù)要點(diǎn):

          • 返獎(jiǎng)金額的計(jì)算,涉及到不同的計(jì)算規(guī)則。

          • 從邀請開始到返獎(jiǎng)結(jié)束的整個(gè)流程。

          3.2.2 返獎(jiǎng)規(guī)則與設(shè)計(jì)模式實(shí)踐

          業(yè)務(wù)建模

          如圖是返獎(jiǎng)規(guī)則計(jì)算的業(yè)務(wù)邏輯視圖:

          從這份業(yè)務(wù)邏輯圖中可以看到返獎(jiǎng)金額計(jì)算的規(guī)則。首先要根據(jù)用戶狀態(tài)確定用戶是否滿足返獎(jiǎng)條件。如果滿足返獎(jiǎng)條件,則繼續(xù)判斷當(dāng)前用戶屬于新用戶還是老用戶,從而給予不同的獎(jiǎng)勵(lì)方案。一共涉及以下幾種不同的獎(jiǎng)勵(lì)方案:

          新用戶

          • 普通獎(jiǎng)勵(lì)(給予固定金額的獎(jiǎng)勵(lì)

          • 梯度獎(jiǎng)(根據(jù)用戶邀請的人數(shù)給予不同的獎(jiǎng)勵(lì)金額,邀請的人越多,獎(jiǎng)勵(lì)金額越多

          老用戶

          • 根據(jù)老用戶的用戶屬性來計(jì)算返獎(jiǎng)金額。為了評估不同的邀新效果,老用戶返獎(jiǎng)會(huì)存在多種返獎(jiǎng)機(jī)制。

          計(jì)算完獎(jiǎng)勵(lì)金額以后,還需要更新用戶的獎(jiǎng)金信息,以及通知結(jié)算服務(wù)對用戶的金額進(jìn)行結(jié)算。這兩個(gè)模塊對于所有的獎(jiǎng)勵(lì)來說都是一樣的。

          可以看到,無論是何種用戶,對于整體返獎(jiǎng)流程是不變的,唯一變化的是返獎(jiǎng)規(guī)則。此處,我們可參考開閉原則,對于返獎(jiǎng)流程保持封閉,對于可能擴(kuò)展的返獎(jiǎng)規(guī)則進(jìn)行開放。我們將返獎(jiǎng)規(guī)則抽象為返獎(jiǎng)策略,即針對不同用戶類型的不同返獎(jiǎng)方案,我們視為不同的返獎(jiǎng)策略,不同的返獎(jiǎng)策略會(huì)產(chǎn)生不同的返獎(jiǎng)金額結(jié)果。

          在我們的領(lǐng)域模型里,返獎(jiǎng)策略是一個(gè)值對象,我們通過工廠的方式生產(chǎn)針對不同用戶的獎(jiǎng)勵(lì)策略值對象。下文我們將介紹以上領(lǐng)域模型的工程實(shí)現(xiàn),即工廠模式策略模式的實(shí)際應(yīng)用。

          模式:工廠模式

          工廠模式又細(xì)分為工廠方法模式和抽象工廠模式,本文主要介紹工廠方法模式。

          模式定義:定義一個(gè)用于創(chuàng)建對象的接口,讓子類決定實(shí)例化哪一個(gè)類。工廠方法是一個(gè)類的實(shí)例化延遲到其子類。

          工廠模式通用類圖如下:

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

          //抽象的產(chǎn)品
          public abstract class Product {
              public abstract void method();
          }
          //定義一個(gè)具體的產(chǎn)品 (可以定義多個(gè)具體的產(chǎn)品)
          class ProductA extends Product {
              @Override
              public void method() {}  //具體的執(zhí)行邏輯
          }
          //抽象的工廠
          abstract class Factory<T{
              abstract Product createProduct(Class<T> c);
          }
          //具體的工廠可以生產(chǎn)出相應(yīng)的產(chǎn)品
          class FactoryA extends Factory{
              @Override
              Product createProduct(Class c) {
                  Product product = (Product) Class.forName(c.getName()).newInstance();
                  return product;
              }
          }

          模式:策略模式

          模式定義:定義一系列算法,將每個(gè)算法都封裝起來,并且它們可以互換。策略模式是一種對象行為模式。

          策略模式通用類圖如下:

          我們通過一段比較通用的代碼來解釋怎么使用策略模式:

          //定義一個(gè)策略接口
          public interface Strategy {
              void strategyImplementation();
          }

          //具體的策略實(shí)現(xiàn)(可以定義多個(gè)具體的策略實(shí)現(xiàn))
          public class StrategyA implements Strategy{
              @Override
              public void strategyImplementation() {
                  System.out.println("正在執(zhí)行策略A");
              }
          }

          //封裝策略,屏蔽高層模塊對策略、算法的直接訪問,屏蔽可能存在的策略變化
          public class Context {
              private Strategy strategy = null;

              public Context(Strategy strategy) {
                  this.strategy = strategy;
              }

              public void doStrategy() {
                  strategy.strategyImplementation();
              }
          }

          工程實(shí)踐

          通過上文介紹的返獎(jiǎng)業(yè)務(wù)模型,我們可以看到返獎(jiǎng)的主流程就是選擇不同的返獎(jiǎng)策略的過程,每個(gè)返獎(jiǎng)策略都包括返獎(jiǎng)金額計(jì)算、更新用戶獎(jiǎng)金信息、以及結(jié)算這三個(gè)步驟。我們可以使用工廠模式生產(chǎn)出不同的策略,同時(shí)使用策略模式來進(jìn)行不同的策略執(zhí)行。首先確定我們需要生成出n種不同的返獎(jiǎng)策略,其編碼如下:

          //抽象策略
          public abstract class RewardStrategy {
              public abstract void reward(long userId);

              public void insertRewardAndSettlement(long userId, int reward) {} ; //更新用戶信息以及結(jié)算
          }
          //新用戶返獎(jiǎng)具體策略A
          public class newUserRewardStrategyA extends RewardStrategy {
              @Override
              public void reward(long userId) {}  //具體的計(jì)算邏輯,...
          }

          //老用戶返獎(jiǎng)具體策略A
          public class OldUserRewardStrategyA extends RewardStrategy {
              @Override
              public void reward(long userId) {}  //具體的計(jì)算邏輯,...
          }

          //抽象工廠
          public abstract class StrategyFactory<T{
              abstract RewardStrategy createStrategy(Class<T> c);
          }

          //具體工廠創(chuàng)建具體的策略
          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;
              }
          }

          通過工廠模式生產(chǎn)出具體的策略之后,根據(jù)我們之前的介紹,很容易就可以想到使用策略模式來執(zhí)行我們的策略。具體代碼如下:

          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);
                 }  
              }
          }

          接下來我們將工廠模式和策略模式結(jié)合在一起,就完成了整個(gè)返獎(jiǎng)的過程:

          public class InviteRewardImpl {
              //返獎(jiǎng)主流程
              public void sendReward(long userId) {
                  FactorRewardStrategyFactory strategyFactory = new FactorRewardStrategyFactory();  //創(chuàng)建工廠
                  Invitee invitee = getInviteeByUserId(userId);  //根據(jù)用戶id查詢用戶信息
                  if (invitee.userType == UserTypeEnum.NEW_USER) {  //新用戶返獎(jiǎng)策略
                      NewUserBasicReward newUserBasicReward = (NewUserBasicReward) strategyFactory.createStrategy(NewUserBasicReward.class);
                      RewardContext rewardContext = new RewardContext(newUserBasicReward);
                      rewardContext.doStrategy(userId); //執(zhí)行返獎(jiǎng)策略
                  }if(invitee.userType == UserTypeEnum.OLD_USER){}  //老用戶返獎(jiǎng)策略,... 
              }
          }

          工廠方法模式幫助我們直接產(chǎn)生一個(gè)具體的策略對象,策略模式幫助我們保證這些策略對象可以自由地切換而不需要改動(dòng)其他邏輯,從而達(dá)到解耦的目的。通過這兩個(gè)模式的組合,當(dāng)我們系統(tǒng)需要增加一種返獎(jiǎng)策略時(shí),只需要實(shí)現(xiàn)RewardStrategy接口即可,無需考慮其他的改動(dòng)。當(dāng)我們需要改變策略時(shí),只要修改策略的類名即可。不僅增強(qiáng)了系統(tǒng)的可擴(kuò)展性,避免了大量的條件判斷,而且從真正意義上達(dá)到了高內(nèi)聚、低耦合的目的。

          3.2.3 返獎(jiǎng)流程與設(shè)計(jì)模式實(shí)踐

          業(yè)務(wù)建模

          當(dāng)受邀人在接受邀請人的邀請并且下單后,返獎(jiǎng)后臺接收到受邀人的下單記錄,此時(shí)邀請人也進(jìn)入返獎(jiǎng)流程。首先我們訂閱用戶訂單消息并對訂單進(jìn)行返獎(jiǎng)規(guī)則校驗(yàn)。例如,是否使用紅包下單,是否在紅包有效期內(nèi)下單,訂單是否滿足一定的優(yōu)惠金額等等條件。當(dāng)滿足這些條件以后,我們將訂單信息放入延遲隊(duì)列中進(jìn)行后續(xù)處理。經(jīng)過T+N天之后處理該延遲消息,判斷用戶是否對該訂單進(jìn)行了退款,如果未退款,對用戶進(jìn)行返獎(jiǎng)。若返獎(jiǎng)失敗,后臺還有返獎(jiǎng)補(bǔ)償流程,再次進(jìn)行返獎(jiǎng)。其流程如下圖所示:

          我們對上述業(yè)務(wù)流程進(jìn)行領(lǐng)域建模:

          1. 在接收到訂單消息后,用戶進(jìn)入待校驗(yàn)狀態(tài);

          2. 在校驗(yàn)后,若校驗(yàn)通過,用戶進(jìn)入預(yù)返獎(jiǎng)狀態(tài),并放入延遲隊(duì)列。若校驗(yàn)未通過,用戶進(jìn)入不返獎(jiǎng)狀態(tài),結(jié)束流程;

          3. T+N天后,處理延遲消息,若用戶未退款,進(jìn)入待返獎(jiǎng)狀態(tài)。若用戶退款,進(jìn)入失敗狀態(tài),結(jié)束流程;

          4. 執(zhí)行返獎(jiǎng),若返獎(jiǎng)成功,進(jìn)入完成狀態(tài),結(jié)束流程。若返獎(jiǎng)不成功,進(jìn)入待補(bǔ)償狀態(tài);

          5. 待補(bǔ)償狀態(tài)的用戶會(huì)由任務(wù)定期觸發(fā)補(bǔ)償機(jī)制,直至返獎(jiǎng)成功,進(jìn)入完成狀態(tài),保障流程結(jié)束。

          可以看到,我們通過建模將返獎(jiǎng)流程的多個(gè)步驟映射為系統(tǒng)的狀態(tài)。對于系統(tǒng)狀態(tài)的表述,DDD中常用到的概念是領(lǐng)域事件,另外也提及過事件溯源的實(shí)踐方案。當(dāng)然,在設(shè)計(jì)模式中,也有一種能夠表述系統(tǒng)狀態(tài)的代碼模型,那就是狀態(tài)模式。在邀請下單系統(tǒng)中,我們的主要流程是返獎(jiǎng)。對于返獎(jiǎng),每一個(gè)狀態(tài)要進(jìn)行的動(dòng)作和操作都是不同的。因此,使用狀態(tài)模式,能夠幫助我們對系統(tǒng)狀態(tài)以及狀態(tài)間的流轉(zhuǎn)進(jìn)行統(tǒng)一的管理和擴(kuò)展。

          模式:狀態(tài)模式

          模式定義:當(dāng)一個(gè)對象內(nèi)在狀態(tài)改變時(shí)允許其改變行為,這個(gè)對象看起來像改變了其類。

          狀態(tài)模式的通用類圖如下圖所示:

          對比策略模式的類型會(huì)發(fā)現(xiàn)和狀態(tài)模式的類圖很類似,但實(shí)際上有很大的區(qū)別,具體體現(xiàn)在concrete class上。策略模式通過Context產(chǎn)生唯一一個(gè)ConcreteStrategy作用于代碼中,而狀態(tài)模式則是通過context組織多個(gè)ConcreteState形成一個(gè)狀態(tài)轉(zhuǎn)換圖來實(shí)現(xiàn)業(yè)務(wù)邏輯。接下來,我們通過一段通用代碼來解釋怎么使用狀態(tài)模式:

          //定義一個(gè)抽象的狀態(tài)類
          public abstract class State {
              Context context;
              public void setContext(Context context) {
                  this.context = context;
              }
              public abstract void handle1();
              public abstract void handle2();
          }
          //定義狀態(tài)A
          public class ConcreteStateA extends State {
              @Override
              public void handle1() {}  //本狀態(tài)下必須要處理的事情

              @Override
              public void handle2() {
                  super.context.setCurrentState(Context.contreteStateB);  //切換到狀態(tài)B        
                  super.context.handle2();  //執(zhí)行狀態(tài)B的任務(wù)
              }
          }
          //定義狀態(tài)B
          public class ConcreteStateB extends State {
              @Override
              public void handle2() {}  //本狀態(tài)下必須要處理的事情,...

              @Override
              public void handle1() {
                  super.context.setCurrentState(Context.contreteStateA);  //切換到狀態(tài)A
                  super.context.handle1();  //執(zhí)行狀態(tài)A的任務(wù)
              }
          }
          //定義一個(gè)上下文管理環(huán)境
          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執(zhí)行
          public class client {
              public static void main(String[] args) {
                  Context context = new Context();
                  context.setCurrentState(new ContreteStateA());
                  context.handle1();
                  context.handle2();
              }
          }

          工程實(shí)踐

          通過前文對狀態(tài)模式的簡介,我們可以看到當(dāng)狀態(tài)之間的轉(zhuǎn)換在不是非常復(fù)雜的情況下,通用的狀態(tài)模式存在大量的與狀態(tài)無關(guān)的動(dòng)作從而產(chǎn)生大量的無用代碼。在我們的實(shí)踐中,一個(gè)狀態(tài)的下游不會(huì)涉及特別多的狀態(tài)裝換,所以我們簡化了狀態(tài)模式。當(dāng)前的狀態(tài)只負(fù)責(zé)當(dāng)前狀態(tài)要處理的事情,狀態(tài)的流轉(zhuǎn)則由第三方類負(fù)責(zé)。其實(shí)踐代碼如下:

          //返獎(jiǎng)狀態(tài)執(zhí)行的上下文
          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);
          }

          //待校驗(yàn)狀態(tài)
          public class OrderCheckState extends RewardState {
              @Override
              public void doReward(RewardStateContext context, Request request) {
                  orderCheck(context, request);  //對進(jìn)來的訂單進(jìn)行校驗(yàn),判斷是否用券,是否滿足優(yōu)惠條件等等
              }
          }

          //待補(bǔ)償狀態(tài)
          public class CompensateRewardState extends RewardState {
              @Override
              public void doReward(RewardStateContext context, Request request) {
                  compensateReward(context, request);  //返獎(jiǎng)失敗,需要對用戶進(jìn)行返獎(jiǎng)補(bǔ)償
              }
          }

          //預(yù)返獎(jiǎng)狀態(tài),待返獎(jiǎng)狀態(tài),成功狀態(tài),失敗狀態(tài)(此處邏輯省略)
          //..

          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);  //開始返獎(jiǎng),訂單校驗(yàn)
                  //此處的if-else邏輯只是為了表達(dá)狀態(tài)的轉(zhuǎn)換過程,并非實(shí)際的業(yè)務(wù)邏輯
                  if (rewardContext.isResultFlag()) {  //如果訂單校驗(yàn)成功,進(jìn)入預(yù)返獎(jiǎng)狀態(tài)
                      rewardContext.setRewardState(new BeforeRewardCheckState());
                      rewardContext.echo(rewardContext, request);
                  } else {//如果訂單校驗(yàn)失敗,進(jìn)入返獎(jiǎng)失敗流程,...
                      rewardContext.setRewardState(new RewardFailedState());
                      rewardContext.echo(rewardContext, request);
                      return false;
                  }
                  if (rewardContext.isResultFlag()) {//預(yù)返獎(jiǎng)檢查成功,進(jìn)入待返獎(jiǎng)流程,...
                      rewardContext.setRewardState(new SendRewardState());
                      rewardContext.echo(rewardContext, request);
                  } else {  //如果預(yù)返獎(jiǎng)檢查失敗,進(jìn)入返獎(jiǎng)失敗流程,...
                      rewardContext.setRewardState(new RewardFailedState());
                      rewardContext.echo(rewardContext, request);
                      return false;
                  }
                  if (rewardContext.isResultFlag()) {  //返獎(jiǎng)成功,進(jìn)入返獎(jiǎng)結(jié)束流程,...
                      rewardContext.setRewardState(new RewardSuccessState());
                      rewardContext.echo(rewardContext, request);
                  } else {  //返獎(jiǎng)失敗,進(jìn)入返獎(jiǎng)補(bǔ)償階段,...
                      rewardContext.setRewardState(new CompensateRewardState());
                      rewardContext.echo(rewardContext, request);
                  }
                  if (rewardContext.isResultFlag()) {  //補(bǔ)償成功,進(jìn)入返獎(jiǎng)完成階段,...
                      rewardContext.setRewardState(new RewardSuccessState());
                      rewardContext.echo(rewardContext, request);
                  } else {  //補(bǔ)償失敗,仍然停留在當(dāng)前態(tài),直至補(bǔ)償成功(或多次補(bǔ)償失敗后人工介入處理)
                      rewardContext.setRewardState(new CompensateRewardState());
                      rewardContext.echo(rewardContext, request);
                  }
                  return true;
              }
          }

          狀態(tài)模式的核心是封裝,將狀態(tài)以及狀態(tài)轉(zhuǎn)換邏輯封裝到類的內(nèi)部來實(shí)現(xiàn),也很好的體現(xiàn)了“開閉原則”和“單一職責(zé)原則”。每一個(gè)狀態(tài)都是一個(gè)子類,不管是修改還是增加狀態(tài),只需要修改或者增加一個(gè)子類即可。在我們的應(yīng)用場景中,狀態(tài)數(shù)量以及狀態(tài)轉(zhuǎn)換遠(yuǎn)比上述例子復(fù)雜,通過“狀態(tài)模式”避免了大量的if-else代碼,讓我們的邏輯變得更加清晰。同時(shí)由于狀態(tài)模式的良好的封裝性以及遵循的設(shè)計(jì)原則,讓我們在復(fù)雜的業(yè)務(wù)場景中,能夠游刃有余地管理各個(gè)狀態(tài)。

          3.3 點(diǎn)評外賣投放系統(tǒng)中設(shè)計(jì)模式的實(shí)踐

          3.3.1 業(yè)務(wù)簡介

          繼續(xù)舉例,點(diǎn)評App的外賣頻道中會(huì)預(yù)留多個(gè)資源位為營銷使用,向用戶展示一些比較精品美味的外賣食品,為了增加用戶點(diǎn)外賣的意向。當(dāng)用戶點(diǎn)擊點(diǎn)評首頁的“美團(tuán)外賣”入口時(shí),資源位開始加載,會(huì)通過一些規(guī)則來篩選出合適的展示Banner。

          3.3.2 設(shè)計(jì)模式實(shí)踐

          業(yè)務(wù)建模

          對于投放業(yè)務(wù),就是要在這些資源位中展示符合當(dāng)前用戶的資源。其流程如下圖所示:

          從流程中我們可以看到,首先運(yùn)營人員會(huì)配置需要展示的資源,以及對資源進(jìn)行過濾的規(guī)則。我們資源的過濾規(guī)則相對靈活多變,這里體現(xiàn)為三點(diǎn):

          1. 過濾規(guī)則大部分可重用,但也會(huì)有擴(kuò)展和變更。

          2. 不同資源位的過濾規(guī)則和過濾順序是不同的。

          3. 同一個(gè)資源位由于業(yè)務(wù)所處的不同階段,過濾規(guī)則可能不同。

          過濾規(guī)則本身是一個(gè)個(gè)的值對象,我們通過領(lǐng)域服務(wù)的方式,操作這些規(guī)則值對象完成資源位的過濾邏輯。下圖介紹了資源位在進(jìn)行用戶特征相關(guān)規(guī)則過濾時(shí)的過程:

          為了實(shí)現(xiàn)過濾規(guī)則的解耦,對單個(gè)規(guī)則值對象的修改封閉,并對規(guī)則集合組成的過濾鏈條開放,我們在資源位過濾的領(lǐng)域服務(wù)中引入了責(zé)任鏈模式。

          模式:責(zé)任鏈模式

          模式定義:使多個(gè)對象都有機(jī)會(huì)處理請求,從而避免了請求的發(fā)送者和接受者之間的耦合關(guān)系。將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有對象處理它為止。

          責(zé)任鏈模式通用類圖如下:

          我們通過一段比較通用的代碼來解釋如何使用責(zé)任鏈模式:

          //定義一個(gè)抽象的handle
          public abstract class Handler {
              private Handler nextHandler;  //指向下一個(gè)處理者
              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("已經(jīng)到最盡頭了");
                      }
                  }
              }
              // 抽象方法,子類實(shí)現(xiàn)
              public abstract void echo(Request request);
          }

          // 定義一個(gè)具體的handleA
          public class HandleRuleA extends Handler {
              public HandleRuleA(int level) {
                  super(level);
              }
              @Override
              public void echo(Request request) {
                  System.out.println("我是處理者1,我正在處理A規(guī)則");
              }
          }

          //定義一個(gè)具體的handleB
          public class HandleRuleB extends Handler {}  //...

          //客戶端實(shí)現(xiàn)
          class Client {
              public static void main(String[] args) {
                  HandleRuleA handleRuleA = new HandleRuleA(1);
                  HandleRuleB handleRuleB = new HandleRuleB(2);
                  handleRuleA.setNextHandler(handleRuleB);  //這是重點(diǎn),將handleA和handleB串起來
                  handleRuleA.echo(new Request());
              }
          }

          工程實(shí)踐

          下面通過代碼向大家展示如何實(shí)現(xiàn)這一套流程:

          //定義一個(gè)抽象的規(guī)則
          public abstract class BasicRule<CORE_ITEMT extends RuleContext<CORE_ITEM>>{
              //有兩個(gè)方法,evaluate用于判斷是否經(jīng)過規(guī)則執(zhí)行,execute用于執(zhí)行具體的規(guī)則內(nèi)容。
              public abstract boolean evaluate(T context);
              public abstract void execute(T context) {
          }

          //定義所有的規(guī)則具體實(shí)現(xiàn)
          //規(guī)則1:判斷服務(wù)可用性
          public class ServiceAvailableRule extends BasicRule<UserPortraitUserPortraitRuleContext{
              @Override
              public boolean evaluate(UserPortraitRuleContext context) {
                  TakeawayUserPortraitBasicInfo basicInfo = context.getBasicInfo();
                  if (basicInfo.isServiceFail()) {
                        return false;
                  }
                  return true;
              }

              @Override
              public void execute(UserPortraitRuleContext context) {}

          }
          //規(guī)則2:判斷當(dāng)前用戶屬性是否符合當(dāng)前資源位投放的用戶屬性要求
          public class UserGroupRule extends BasicRule<UserPortraitUserPortraitRuleContext{
              @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);
                  }
              }
          }

          //規(guī)則3:判斷當(dāng)前用戶是否在投放城市,具體邏輯省略
          public class CityInfoRule extends BasicRule<UserPortraitUserPortraitRuleContext{}
          //規(guī)則4:根據(jù)用戶的活躍度進(jìn)行資源過濾,具體邏輯省略
          public class UserPortraitRule extends BasicRule<UserPortraitUserPortraitRuleContext{} 

          //我們通過spring將這些規(guī)則串起來組成一個(gè)一個(gè)請求鏈
              <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>

          //規(guī)則執(zhí)行
          public class DefaultRuleEngine{
              @Autowired
              List<BasicRule> userPortraitRuleChain;

              public void invokeAll(RuleContext ruleContext) {
                  for(Rule rule : userPortraitRuleChain) {
                      rule.evaluate(ruleContext)
                  }
              }
          }

          責(zé)任鏈模式最重要的優(yōu)點(diǎn)就是解耦,將客戶端與處理者分開,客戶端不需要了解是哪個(gè)處理者對事件進(jìn)行處理,處理者也不需要知道處理的整個(gè)流程。

          在我們的系統(tǒng)中,后臺的過濾規(guī)則會(huì)經(jīng)常變動(dòng),規(guī)則和規(guī)則之間可能也會(huì)存在傳遞關(guān)系,通過責(zé)任鏈模式,我們將規(guī)則與規(guī)則分開,將規(guī)則與規(guī)則之間的傳遞關(guān)系通過Spring注入到List中,形成一個(gè)鏈的關(guān)系。當(dāng)增加一個(gè)規(guī)則時(shí),只需要實(shí)現(xiàn)BasicRule接口,然后將新增的規(guī)則按照順序加入Spring中即可。當(dāng)刪除時(shí),只需刪除相關(guān)規(guī)則即可,不需要考慮代碼的其他邏輯。從而顯著地提高了代碼的靈活性,提高了代碼的開發(fā)效率,同時(shí)也保證了系統(tǒng)的穩(wěn)定性。

          四、總結(jié)

          本文從營銷業(yè)務(wù)出發(fā),介紹了領(lǐng)域模型到代碼工程之間的轉(zhuǎn)化,從DDD引出了設(shè)計(jì)模式,詳細(xì)介紹了工廠方法模式、策略模式、責(zé)任鏈模式以及狀態(tài)模式這四種模式在營銷業(yè)務(wù)中的具體實(shí)現(xiàn)。

          除了這四種模式以外,我們的代碼工程中還大量使用了代理模式、單例模式、適配器模式等等,例如在我們對DDD防腐層的實(shí)現(xiàn)就使用了適配器模式,通過適配器模式屏蔽了業(yè)務(wù)邏輯與第三方服務(wù)的交互。因篇幅原因,這里不再進(jìn)行過多的闡述。

          對于營銷業(yè)務(wù)來說,業(yè)務(wù)策略多變導(dǎo)致需求多變是我們面臨的主要問題。如何應(yīng)對復(fù)雜多變的需求,是我們提煉領(lǐng)域模型和實(shí)現(xiàn)代碼模型時(shí)必須要考慮的內(nèi)容。DDD以及設(shè)計(jì)模式提供了一套相對完整的方法論幫助我們完成了領(lǐng)域建模及工程實(shí)現(xiàn)。其實(shí),設(shè)計(jì)模式就像一面鏡子,將領(lǐng)域模型映射到代碼模型中,切實(shí)地提高代碼的復(fù)用性、可擴(kuò)展性,也提高了系統(tǒng)的可維護(hù)性。

          當(dāng)然,設(shè)計(jì)模式只是軟件開發(fā)領(lǐng)域內(nèi)多年來的經(jīng)驗(yàn)總結(jié),任何一個(gè)或簡單或復(fù)雜的設(shè)計(jì)模式都會(huì)遵循上述的七大設(shè)計(jì)原則,只要大家真正理解了七大設(shè)計(jì)原則,設(shè)計(jì)模式對我們來說應(yīng)該就不再是一件難事。但是,使用設(shè)計(jì)模式也不是要求我們循規(guī)蹈矩,只要我們的代碼模型設(shè)計(jì)遵循了上述的七大原則,我們會(huì)發(fā)現(xiàn)原來我們的設(shè)計(jì)中就已經(jīng)使用了某種設(shè)計(jì)模式。

          五、參考資料

          • 軟件設(shè)計(jì)模式-百度百科

          • 快速理解-設(shè)計(jì)模式六大原則

          • Software design pattern

          • 《設(shè)計(jì)模式之禪》,秦小波,機(jī)械工業(yè)出版社

          • 《領(lǐng)域驅(qū)動(dòng)設(shè)計(jì)-軟件核心復(fù)雜性應(yīng)對之道》,Eric Evans,人民郵電出版社。

          六、作者簡介

          亮亮,2017年加入美團(tuán)外賣,美團(tuán)外賣營銷后臺團(tuán)隊(duì)開發(fā)工程師。



          瀏覽 80
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  黑大巨大一区二区三区 | 干逼免费视频 | 丁香花婷婷 | 国产系列第一页 | 欧美xxxx操 |