美團設計模式在外賣營銷業(yè)務中的實踐-學習筆記(一)
[TOC]
看了美團技術團隊的 設計模式在外賣營銷業(yè)務中的實踐 一文后的一些記錄劲藐,emmm為了證明我看過握爷,哈哈姑曙,還有最后一個責任鏈模式暫時還不知道怎么弄。服猪。供填。 感興趣的可以點這里去看看原文,干貨滿滿罢猪。第一次寫學習筆記近她,不知道寫的好不好,歡迎各位大佬評論交流膳帕,大家一起學習啊粘捎。
一、設計模式原則
面向對象的設計模式有七大基本原則:
- 開閉原則 (Open Closed Principle, OCP)
- 單一職責原則(Single Responseibility 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)
簡單理解就是:開閉原則是總綱危彩,它指導我們要對擴展開放攒磨,對修改關閉;單一職責原則則指導我們實現(xiàn)類要職責單一汤徽;里氏替換原則知道我們不要破壞繼承體系娩缰;依賴倒置原則指導我們要面向接口編程;接口隔離原則指導我們在設計接口的時候要精簡單一谒府;迪米特法則則指導我們要降低耦合拼坎。
二浮毯、設計模式在美團外賣營銷業(yè)務中的具體案例
2.1、工廠模式和策略模式
學習設計模式或者是在工程中實踐設計模式泰鸡,必須深入到某一個特定的業(yè)務場景中去债蓝,再結合對業(yè)務場景的理解和領域模型的建立,才能體會到設計模式思想的精髓盛龄。在這里美團結合了其“邀請下單”業(yè)務來進行設計模式實踐與分享饰迹。
2.1.1 業(yè)務簡介
“邀請下單”是美團外面用戶邀請其他用戶下單后給予獎勵的平臺。即用戶A邀請用戶B余舶,并且用戶B在美團下單后給予用戶A一定的現(xiàn)金獎勵啊鸭。同時為了協(xié)調(diào)成本與收益的關系,返獎會有多個計算策略欧芽。邀請下單后臺主要涉及兩個技術要點:
- 返獎金額計算莉掂,涉及到不同的計算原則。
- 從邀請開始到返獎結束的整個流程千扔。
2.1.2 返獎規(guī)則與設計模式
業(yè)務建模
如圖是美團邀請下單業(yè)務返獎規(guī)則計算的業(yè)務邏輯視圖:
<img src="https://tva1.sinaimg.cn/large/00831rSTgy1gd3v2l36x6j30u00elwgc.jpg" style="zoom: 67%;" />
從圖中可以看到返獎金額的計算規(guī)則憎妙。首先需要判斷用戶狀態(tài)是否符合返獎規(guī)則,符合則繼續(xù)判斷用戶是老用戶還是新用戶曲楚,從而給予不同的獎勵方案厘唾。
在計算完用戶返獎金額后還需要更新用戶的獎金信息及通知結算服務對用戶的金額進行結算。這兩個模塊對所有的獎勵來說否是一樣的龙誊。
可以看到抚垃,對用戶的整體返獎流程是不變的,變化的只有對反獎金額的計算流程即返獎規(guī)則趟大。此處我們可以參考開閉原則鹤树,對于返獎流程保持封閉,對于可能擴展的返獎規(guī)則進行開放逊朽。將放獎規(guī)則抽象為返獎策略罕伯,即針對不同的用戶類型的不同反獎方案我們視為不同的返獎策略,不同的返獎策略會產(chǎn)生不同的返獎金額結果叽讳。
其中返獎策略最終產(chǎn)生的是一個值對象追他,我們通過工廠的方式生產(chǎn)針對不同的用戶的獎勵策略。主要涉及的設計模式為工廠模式和策略模式岛蚤。
模式:工廠模式
模式定義:
定義一個用于創(chuàng)建對象接口邑狸,讓子類決定實例化哪一個類。工廠方法是一個類的實例化延遲到子類涤妒。
工廠模式通用類如下:
<img src="https://tva1.sinaimg.cn/large/00831rSTgy1gd3wcu8y3dj30oo0b4mxm.jpg" style="zoom:50%;" />
代碼實例如下:
// 抽象的產(chǎn)品
public abstract class Product {
public abstract void method();
}
// 產(chǎn)品A
public class ProductA extends Product {
@Override
public void method() {
System.out.println("This is ProductA");
}
}
// 產(chǎn)品B
public class ProductB extends Product {
@Override
public void method() {
System.out.println("This is Product B");
}
}
// 創(chuàng)建一個抽象的工廠
public abstract class Factory<T> {
public abstract Product createProduct(Class<T> c) throws Exception;
}
// 具體分工廠實現(xiàn) (注意 這里知識命名為FactoryA单雾,不是ProductA的專屬工廠)
public class FactoryA extends Factory {
@Override
public Product createProduct(Class c) throws Exception {
// 利用反射動態(tài)創(chuàng)建實例
return (Product) Class.forName(c.getName()).newInstance();
}
}
// 具體使用
public static void main(String[] args) throws Exception {
//創(chuàng)建工廠
FactoryA factoryA = new FactoryA();
// 使用工廠創(chuàng)建具體的產(chǎn)品
Product product = factoryA.createProduct(ProductA.class);
product.method();
Product product1 = factoryA.createProduct(ProductB.class);
product1.method();
}
// 輸出結果
Connected to the target VM, address: '127.0.0.1:65319', transport: 'socket'
This is ProductA
This is Product B
Disconnected from the target VM, address: '127.0.0.1:65319', transport: 'socket'
Process finished with exit code 0
模式:策略模式
模式定義:定義一系列算法,將每個算法都封裝起來,并且他們可以互換铁坎。策略模式是一種對象行為模式蜂奸。
策略模式通用類圖如下:
<img src="https://tva1.sinaimg.cn/large/00831rSTgy1gd3zy1h1zgj30no086jrr.jpg" style="zoom:50%;" />
代碼示例:
// 定義一個策略接口
public interface Strategy {
void strategyImplementation();
}
// 具體的策略實現(xiàn)
public class StrategyA implements Strategy {
@Override
public void strategyImplementation() {
System.out.println("正在執(zhí)行策略A");
}
}
public class StrategyB implements Strategy {
@Override
public void strategyImplementation() {
System.out.println("正在執(zhí)行策略B");
}
}
// 策略封裝 屏蔽高層模塊對策略、算法的直接訪問 使用context同一操作
public class Context {
private Strategy strategy = null;
public Context(Strategy strategy){
this.strategy = strategy;
}
public void doStrategy(){
strategy.strategyImplementation();
}
}
// 具體使用
public static void main(String[] args) throws Exception {
StrategyA strategy = new StrategyA();
Context contextA = new Context(strategy);
contextA.doStrategy();
StrategyB strategyB = new StrategyB();
Context contextB = new Context(strategyB);
contextB.doStrategy();
}
// 輸出結果
Connected to the target VM, address: '127.0.0.1:65488', transport: 'socket'
正在執(zhí)行策略A
正在執(zhí)行策略B
Disconnected from the target VM, address: '127.0.0.1:65488', transport: 'socket'
Process finished with exit code 0
工程實踐:
通過上文介紹的返獎業(yè)務模型硬萍,我們可以看到返獎的主流程就是選擇不同的返獎策略的過程,每個返獎策略都包含返獎金額計算围详、更新用戶獎金信息及結算這三個步驟朴乖。我們可以使用工廠模式生產(chǎn)出不同的策略,同時使用策略模式來進行不同的策略執(zhí)行助赞。示例代碼如下:
// 抽象策略
public abstract class RewardStrategy {
// 生成的返獎金額 不同策略實現(xiàn)不同 定義成抽象的 由子類自己實現(xiàn)
public abstract int reward(long userId);
// 更新賬戶及結算信息 每個用戶和返獎規(guī)則最后都要執(zhí)行 統(tǒng)一實現(xiàn)
public void insertRewardAndSettlement(long userId, int reward){
System.out.println("更新用戶信息以及結算成功:userId => " + userId + ",reward => " + reward);
}
}
// 新用戶返獎策略 (這里使用隨機數(shù)模擬返獎金額 ??)
public class NewUserRewardStrategy extends RewardStrategy {
@Override
public int reward(long userId) {
System.out.println("新用戶反獎策略买羞,用戶 => " + userId);
return (int)(Math.random() * 10);
}
}
// 老用戶返獎策略 (這里也使用隨機數(shù)模擬返獎金額 ??)
public class OldUserRewardStrategy extends RewardStrategy {
@Override
public int reward(long userId) {
System.out.println("老用戶反獎策略,用戶 => " + userId);
return (int)(Math.random() * 10);
}
}
// 抽象工廠
public abstract class StrategyFactory<T> {
abstract RewardStrategy createStrategy(Class<T> c);
}
// 具體的工廠 (根據(jù)具體的類生成不同的策略)
public class FactorStrategyFactory extends StrategyFactory {
@Override
public RewardStrategy createStrategy(Class c) {
RewardStrategy strategy = null;
try{
strategy = (RewardStrategy) Class.forName(c.getName()).newInstance();
}catch (Exception e){
e.printStackTrace();
}
return strategy;
}
}
// 使用策略模式來執(zhí)行具體的策略
public class RewardContext {
private RewardStrategy strategy;
// 構造方法 傳入具體到的策略
public RewardContext(RewardStrategy strategy){
this.strategy = strategy;
}
public void doStrategy(long userId){
int reward = strategy.reward(userId);
strategy.insertRewardAndSettlement(userId, reward);
}
}
// 具體使用 使用時沒有直接對策略雹食、算法的直接訪問 而是通過context進行操作
// 這里使用隨機數(shù)的大小來判斷 使用 老用戶策略還是新用戶策略 ??
public static void main(String[] args) {
FactorStrategyFactory factory = new FactorStrategyFactory();
RewardContext context;
RewardStrategy strategy;
double i = Math.random();
System.out.println(i);
if(i > 0.4){
strategy = factory.createStrategy(OldUserRewardStrategy.class);
context = new RewardContext(strategy);
context.doStrategy(123456);
}else {
strategy = factory.createStrategy(NewUserRewardStrategy.class);
context = new RewardContext(strategy);
context.doStrategy(456789);
}
}
// 執(zhí)行結果
Connected to the target VM, address: '127.0.0.1:49241', transport: 'socket'
0.4496623743530703 // 這個是生成的隨機數(shù)
老用戶反獎策略畜普,用戶 => 123456
更新用戶信息以及結算成功:userId => 123456,reward => 3
Disconnected from the target VM, address: '127.0.0.1:49241', transport: 'socket'
Process finished with exit code 0
工廠方法模式幫助我們直接產(chǎn)生一個具體的策略對象,策略模式幫助我們保證這些策略對象可以自由的切換而不需要改動其他邏輯群叶,從而達到解耦的目的吃挑。通過這兩個模式組合,當我們系統(tǒng)需要增加一種返獎策略時街立,只需要實現(xiàn)RewardStrategy接口即可舶衬,無需考慮其他的改動。當我們需要改變策略時赎离,只需要修改策略的類名即可逛犹。不僅增強了系統(tǒng)的可擴展性,避免了大量的條件判斷梁剔,而且從真正意義上達到了高內(nèi)聚虽画、低耦合的目的。
2.1.3 返獎流程與設計模式實踐
業(yè)務建模
當受邀人在接受邀請人的邀請并且下單后荣病,返獎后臺接收到受邀人的下單記錄码撰,此時邀請人也進入返獎流程。首先我們訂閱用戶訂單消息并對訂單進行返獎規(guī)則校驗众雷。例如灸拍,是否使用紅包下單,是否在紅包有效期內(nèi)下單砾省,訂單是否滿足一定的優(yōu)惠金額等等條件鸡岗。當滿足這些條件以后,我們將訂單信息放入延遲隊列中進行后續(xù)處理编兄。經(jīng)過T+N天之后處理該延遲消息轩性,判斷用戶是否對該訂單進行了退款,如果未退款狠鸳,對用戶進行返獎揣苏。若返獎失敗悯嗓,后臺還有返獎補償流程,再次進行返獎卸察。其流程如下圖所示:
<img src="https://tva1.sinaimg.cn/large/00831rSTgy1gd3yfickakj30u009lt9x.jpg" style="zoom: 80%;" />
我們對上述業(yè)務流程進行領域建模:
- 在接收到訂單消息后脯厨,用戶進入待校驗狀態(tài);
- 在校驗后坑质,若校驗通過合武,用戶進入預返獎狀態(tài),并放入延遲隊列涡扼。若校驗未通過稼跳,用戶進入不返獎狀態(tài),結束流程吃沪;
- T+N天后汤善,處理延遲消息,若用戶未退款票彪,進入待返獎狀態(tài)红淡。若用戶退款,進入失敗狀態(tài)抹镊,結束流程锉屈;
- 執(zhí)行返獎,若返獎成功垮耳,進入完成狀態(tài)颈渊,結束流程。若返獎不成功终佛,進入待補償狀態(tài)俊嗽;
- 待補償狀態(tài)的用戶會由任務定期觸發(fā)補償機制,直至返獎成功铃彰,進入完成狀態(tài)绍豁,保障流程結束。
<img src="https://tva1.sinaimg.cn/large/00831rSTgy1gd3ygq8et6j30u00abq3r.jpg" style="zoom:80%;" />
通過建模將返獎流程的多個步驟映射位系統(tǒng)的狀態(tài)牙捉。在邀請下單系統(tǒng)中竹揍,我們的主要流程是返獎。對于返獎邪铲,每一個狀態(tài)要進行的動作和操作都是不同的芬位。因此,使用狀態(tài)模式带到,能夠幫助我們對系統(tǒng)狀態(tài)以及狀態(tài)間的流轉進行統(tǒng)一的管理和擴展昧碉。
模式:狀態(tài)模式
模式定義:當一個對象內(nèi)在改變狀態(tài)時允許其改變行為,這個對象看起來想改變了其類。(看完一臉懵逼被饿。四康。。)
狀態(tài)模式的通用類圖如下圖所示:
<img src="https://tva1.sinaimg.cn/large/00831rSTgy1gd3ykw0vkhj30nc0b4aak.jpg" style="zoom: 50%;" />
對比策略模式的類型會發(fā)現(xiàn)和狀態(tài)模式的類圖很類似狭握,但實際上有很大的區(qū)別闪金,具體體現(xiàn)在concrete class上。策略模式通過Context產(chǎn)生唯一一個ConcreteStrategy作用于代碼中论颅,而狀態(tài)模式則是通過context組織多個ConcreteState形成一個狀態(tài)轉換圖來實現(xiàn)業(yè)務邏輯毕泌。代碼示例:
// 定義一個抽象的狀態(tài)類
public abstract class State {
protected StateContext context;
public void setContext(StateContext context){
this.context = context;
}
public abstract void handle1();
public abstract void handle2();
}
// 定義A狀態(tài)
public class ConcreteStateA extends State {
@Override
public void handle1() {
System.out.println("執(zhí)行狀態(tài)1。嗅辣。。");
}
@Override
public void handle2() {
//切換為狀態(tài)B
super.context.setCurrentState(StateContext.concreteStateB);
//執(zhí)行狀態(tài)B的任務
super.context.handle2();
}
}
// 定義B狀態(tài)
public class ConcreteStateB extends State {
@Override
public void handle1() {
//切換回狀態(tài)A
super.context.setCurrentState(StateContext.cincreteStateA);
//執(zhí)行狀態(tài)A的任務
super.context.handle1();
}
@Override
public void handle2() {
System.out.println("正在執(zhí)行狀態(tài)B");
}
}
// 定義一個上下文管理環(huán)境
public class StateContext {
public final static ConcreteStateB concreteStateB = new ConcreteStateB();
public final static ConcreteStateA concreteStateA = new ConcreteStateA();
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();}
}
// 使用示例
public static void main(String[] args) {
StateContext context = new StateContext();
context.setCurrentState(new CincreteStateA());
context.handle1();
context.handle2();
}
// 運行結果 (在A狀態(tài)中轉換成狀態(tài)B并執(zhí)行狀態(tài)B的方法)
Connected to the target VM, address: '127.0.0.1:49837', transport: 'socket'
執(zhí)行狀態(tài)1挠说。澡谭。。
正在執(zhí)行狀態(tài)B
Disconnected from the target VM, address: '127.0.0.1:49837', transport: 'socket'
Process finished with exit code 0
工程實踐
通過前文對狀態(tài)模式的簡介损俭,我們可以看到當狀態(tài)之間的轉換在不是非常復雜的情況下蛙奖,通用的狀態(tài)模式存在大量的與狀態(tài)無關的動作從而產(chǎn)生大量的無用代碼。在美團的實踐中杆兵,一個狀態(tài)的下游不會涉及特別多的狀態(tài)裝換雁仲,所以我們簡化了狀態(tài)模式。當前的狀態(tài)只負責當前狀態(tài)要處理的事情琐脏,狀態(tài)的流轉則由第三方類負責攒砖。其實踐代碼如下:
// 返獎狀態(tài)執(zhí)行的上下文
public class StateContext {
private State state;
public void setState(State state){
this.state = state;
}
public State getState(){return state;}
public void echo(StateContext context){
state.doReward(context);
}
public boolean isResultFlag(){
return state.isResultFlag();
}
}
// 返獎狀態(tài)抽象類
public abstract class State {
// 具體執(zhí)行
public abstract void doReward(StateContext context);
// 判斷是否通過改判斷
public abstract boolean isResultFlag();
}
// 各狀態(tài)下的處理邏輯 根據(jù)上面的業(yè)務建模來實現(xiàn)的 這里還是用隨機數(shù)來模擬流程是否執(zhí)行成功
// 訂單狀態(tài)檢查
public class CheckOrderState extends State{
private boolean flag = false;
@Override
public void doReward(StateContext context) {
System.out.println(context.getClass().getName());
System.out.println("CheckOrderState 訂單狀態(tài)檢查...");
double i = Math.random();
if(i > 0.4){
flag = true;
}
}
@Override
public boolean isResultFlag() {
return flag;
}
}
// 預返獎檢查
public class BeforeRewardCheckState extends State{
private boolean flag = false;
@Override
public void doReward(StateContext context) {
System.out.println(context.getClass().getName());
System.out.println("BeforeRewardCheckState 預反獎狀態(tài)檢查...");
double i = Math.random();
if(i > 0.4){
flag = true;
}
}
@Override
public boolean isResultFlag() {
return flag;
}
}
// 返獎流程
public class SendRewardCheckState extends State{
private boolean flag = false;
@Override
public void doReward(StateContext context) {
System.out.println(context.getClass().getName());
System.out.println("SendRewardCheckState 待反獎狀態(tài)檢查...");
double i = Math.random();
if(i > 0.4){
flag = true;
}
}
@Override
public boolean isResultFlag() {
return flag;
}
}
// 補償放獎流程
public class CompentstateRewardState extends State{
private boolean flag = false;
@Override
public void doReward(StateContext context) {
System.out.println(context.getClass().getName());
System.out.println("CompentstateRewardState 補償反獎狀態(tài)...");
double i = Math.random();
if(i > 0.4){
flag = true;
}
}
@Override
public boolean isResultFlag() {
return true;
}
}
// 返獎失敗狀態(tài)
public class RewardFailState extends State{
@Override
public void doReward(StateContext context) {
System.out.println(context.getClass().getName());
System.out.println("RewardFailState 反獎失敗狀態(tài)...");
}
@Override
public boolean isResultFlag() {
return false;
}
}
// 返獎成功狀態(tài)
public class RewardSuccessState extends State{
@Override
public void doReward(StateContext context) {
System.out.println(context.getClass().getName());
System.out.println("RewardSuccessState 反獎成功狀態(tài)...");
}
@Override
public boolean isResultFlag() {
return false;
}
}
// 全部流程整合
public static void main(String[] args) {
dosomething();
}
public static boolean dosomething(){
StateContext context = new StateContext();
context.setState(new CheckOrderState());
context.echo(context); //訂單流程校驗
//此處的if-else邏輯只是為了表達狀態(tài)的轉換過程,并非實際的業(yè)務邏輯
if(context.isResultFlag()){ // 訂單校驗成功 進入預返獎狀態(tài)
context.setState(new BeforeRewardCheckState());
context.echo(context);
}else {// 訂單校驗失敗 進入返獎失敗狀態(tài)
context.setState(new RewardFailState());
context.echo(context);
return false;
}
if(context.isResultFlag()){ // 預返獎檢查成功 進入返獎狀態(tài)
context.setState(new SendRewardCheckState());
context.echo(context);
}else { // 預返獎檢查失敗 進入返獎失敗狀態(tài)
context.setState(new RewardFailState());
context.echo(context);
return false;
}
if(context.isResultFlag()){ // 返獎成功 進入返獎成功狀態(tài)
context.setState(new RewardSuccessState());
context.echo(context);
}else { // 返獎失敗日裙。進入補償放獎狀態(tài)
context.setState(new CompentstateRewardState());
context.echo(context);
}
if(context.isResultFlag()){ // 補償返獎成功 進入成功狀態(tài)
context.setState(new RewardSuccessState());
context.echo(context);
}else { // 補償返獎失敗 這里可以繼續(xù)補償返獎 可以認為控制補償返獎次數(shù)吹艇。這里直接退出了
System.out.println("補償反獎失敗");
}
return true;
}
狀態(tài)模式的核心是封裝,將狀態(tài)以及狀態(tài)轉換邏輯封裝到類的內(nèi)部來實現(xiàn)昂拂,也很好的體現(xiàn)了“開閉原則”和“單一職責原則”受神。每一個狀態(tài)都是一個子類,不管是修改還是增加狀態(tài)格侯,只需要修改或者增加一個子類即可鼻听。在我們的應用場景中,狀態(tài)數(shù)量以及狀態(tài)轉換遠比上述例子復雜联四,通過“狀態(tài)模式”避免了大量的if-else代碼撑碴,讓我們的邏輯變得更加清晰。同時由于狀態(tài)模式的良好的封裝性以及遵循的設計原則碎连,讓我們在復雜的業(yè)務場景中,能夠游刃有余地管理各個狀態(tài)廉嚼。
還有一個責任鏈模式,我暫時還沒搞懂怠噪,這里就不貼了恐似,感興趣的大佬可以去這里看原文呀。
ps:如有侵權傍念,聯(lián)系刪除呀 謝謝