1. 程式人生 > >策略模式在公司專案中的運用實踐,看完又可以漲一波實戰經驗了!

策略模式在公司專案中的運用實踐,看完又可以漲一波實戰經驗了!

營銷系統是一個動態的、有機地結合的系統,經常會隨著業務的不斷變化發生調整,因此從事這一業務的開發可讓我頭疼了。

之前在工作中就不乏一次遇到過隨意調整營銷策略的情況,在部分場景下由於使用了硬編碼的方式來實現,因此在調整策略的時候顯得特別不靈活。

下邊我列舉一個曾經遇到過的應用場景:

業務部門需要上線一款新型的產品,使用者在線上購買了對應的產品,然後下單支付之後需要享受不同的服務內容,這些服務包含了贈送優惠券,傳送紅包補貼,加積分,升級等服務項。並且上線之後,可能會隨著市場的因素的調整,部分服務內容也會有所下架,後期調整因素極高。

下邊是一張使用者建模的圖:

線上買單,到選擇購買的產品型別,再到後續下單之後執行不同的營銷規則,每個產品對應不同的服務專案並且服務專案的內容還可能會隨時調整。

舉個實際案例來說,線上有這麼幾款服務產品供消費者選購:

1.999元會員套餐

  • 正常會員服務期1個月

  • 發放5張優惠券

2.1999元會員套餐

  • 正常會員服務期2個月

  • 發放6張優惠券

  • 邀請新人加入app,新人在n天內購買套餐有優惠

3.2999元會員套餐

  • 正常會員服務期3個月

  • 發放7張優惠券

  • 滿2500元消費,返現50元紅包

….
大致看看,不同的產品對應不同的促銷規則,似乎毫無規律可言。

但是如果通過抽象的邏輯將其中的共同部分抽取出來,就會發現其實是有規則可循了。

下邊我給出來一段 “不那麼完整的程式碼案例” (關於這種營銷手段的設計核心在於思路,沒有完美的程式碼,只有不斷精進的設計)

這段程式碼主要採用來策略模式的設計思路,不同的產品對應不同的策略,產品和策略之間的關聯可以通過使用資料庫的方式來做繫結。

首先可以將每個服務專案看作是一條營銷的規則手段,因此我定義來一個marketing物件:

/**
 * 營銷物件實體類
 *
 * @Author idea
 * @Date created in 9:39 上午 2020/5/4
 */
@NoArgsConstructor
@Data
@Builder
@AllArgsConstructor
public class MarketingPO {

    /**
     * 主鍵id
     */
    private Integer id;

    /**
     * 營銷手段名稱 儲存class的名稱
     */
    private String marketingName;

    /**
     * 入參 多個可以逗號分割
     */
    private String inputVal;

    /**
     * 描述
     */
    private String des;

    /**
     * 建立時間
     */
    private Date createTime;

    /**
     * 更新時間
     */
    private Date updateTime;



}

 

接著便是產品和不同營銷手段之間做關聯

/**
 * 通過產品id和營銷手段做關聯
 *
 * @Author idea
 * @Date created in 3:37 下午 2020/5/4
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MarketingProductPO {

    /**
     * 主鍵id
     */
    private Integer id;

    /**
     * 營銷工具id
     */
    private Integer marketingId;

    /**
     * 產品編號
     */
    private String productNo;

    /**
     * 描述
     */
    private String des;

    /**
     * 是否有效
     */
    private Integer validStatus;

    /**
     * 建立時間
     */
    private Date createTime;

    /**
     * 更新時間
     */
    private Date updateTime;
}

 

接著是dao層的部分,不過這裡我簡單化地將持久層邏輯寫在來程式碼裡面,只做參考:

/**
 * 模擬dao層操作
 *
 * @Author idea
 * @Date created in 10:20 上午 2020/5/4
 */
@Repository
public class MarketingDao implements IMarketingDao {

    private static List<MarketingPO> MARKETING_LIST = new ArrayList();

    static {
        MarketingPO disCountMarket = MarketingPO.builder()
                .id(1).marketingName("com.sise.idea.present.impl.DiscountStrategy").des("折扣優惠").inputVal("7").build();
        MarketingPO redPacketMarket = MarketingPO.builder()
                .id(2).marketingName("com.sise.idea.present.impl.RedPacketStrategy").des("紅包優惠").inputVal("8").build();
        MarketingPO newMemberCouponMarket = MarketingPO.builder()
                .id(3).marketingName("com.sise.idea.present.impl.NewMemberCouponStrategy").des("新人優惠券傳送").inputVal("10").build();
        MARKETING_LIST.add(newMemberCouponMarket);
        MARKETING_LIST.add(disCountMarket);
        MARKETING_LIST.add(redPacketMarket);
    }

    @Override
    public List<MarketingPO> selectMarketingByIds(List<Integer> idList) {
        List<MarketingPO> marketingPOS = new ArrayList<>(idList.size());
        for (MarketingPO marketingPO : MARKETING_LIST) {
            if (idList.contains(marketingPO.getId())) {
                marketingPOS.add(marketingPO);
            }
        }
        return marketingPOS;
    }
}
/**
 * @Author idea
 * @Date created in 3:45 下午 2020/5/4
 */
@Repository
public class MarketingProductDao implements IMarketingProductDao {

    private static List<MarketingProductPO> MARKET_PRODUCT_LIST = new ArrayList<>();

    static {
        MarketingProductPO marketingProductPO = MarketingProductPO.builder()
                .productNo("p111")
                .marketingId(2)
                .validStatus(1)
                .des("2999套餐-發放優惠券")
                .build();
        MarketingProductPO marketingProductPO2 = MarketingProductPO.builder()
                .productNo("p111")
                .marketingId(3)
                .validStatus(1)
                .des("2999套餐-滿額紅包返現")
                .build();

        MARKET_PRODUCT_LIST.add(marketingProductPO);
        MARKET_PRODUCT_LIST.add(marketingProductPO2);
    }

    @Override
    public List<MarketingProductPO> selectByProductNo(String productNo) {
        List<MarketingProductPO> marketingProductPOS = new ArrayList<>();
        for (MarketingProductPO marketingProductPO : MARKET_PRODUCT_LIST) {
            //產品編碼一致 而且規則有效
            if(marketingProductPO.getProductNo().equals(productNo) && marketingProductPO.getValidStatus()==1){
                marketingProductPOS.add(marketingProductPO);
            }
        }
        return marketingProductPOS;
    }
}

 

接著便是對所有的營銷手段都做了一層統一的封裝和抽象:

package com.sise.策略模式.present;

/**
 * 關於營銷手段的策略
 *
 * @Author idea
 * @Date created in 9:20 上午 2020/5/4
 */
public interface IMarketingStrategy {

    /**
     * 服務贈送的策略執行
     *
     * @param param 引數
     * @return
     */
    boolean doMarketing(Object ...param);
}

 

接下來便是不同的營銷手段對應不同的實現,這裡面我簡單做了一些實現:

@Service
public class RedPacketStrategy implements IMarketingStrategy {

    @Override
    public boolean doMarketing(Object... param) {
        System.out.println("紅包贈送策略");
        return false;
    }
}

@Service
public class DiscountStrategy implements IMarketingStrategy {

    @Override
    public boolean doMarketing(Object... param) {
        System.out.println("打折優惠");
        return false;
    }
}

@Service
public class NewMemberCouponStrategy implements IMarketingStrategy {

    @Override
    public boolean doMarketing(Object... param) {
        System.out.println("新人贈送策略");
        return false;
    }
}

@Service
public class UpgradeStrategy implements IMarketingStrategy {

    @Override
    public boolean doMarketing(Object... param) {
        System.out.println("升級策略");
        return false;
    }
}

 

既然有了不同營銷手段的具體實現方式,那麼對於購買不同的產品也需要查詢到不同的營銷手段,這個時候就需要有一個轉換中間者的角色出現了:

/**
 * 營銷工具核心執行器
 *
 * @Author idea
 * @Date created in 9:34 上午 2020/5/4
 */
public interface IMarketingCoreService {

    /**
     * 執行不同的營銷工具
     *
     * @param productNo 產品編碼
     * @return
     */
    boolean doMarketingJob(String productNo) throws Exception;
}


/**
 * 營銷工具核心執行器
 *
 * @Author idea
 * @Date created in 9:34 上午 2020/5/4
 */
@Service
public class MarketingCoreService implements IMarketingCoreService {

    @Resource
    private IMarketingDao iMarketingDao;

    @Resource
    private IMarketingProductDao iMarketingProductDao;

    @Resource
    private ApplicationContext applicationContext;

    @Override
    public boolean doMarketingJob(String productNo) throws ClassNotFoundException {
        System.out.println("doMarketingJob begin =============");
        System.out.println(productNo);
        List<MarketingProductPO> marketingProductPOS = iMarketingProductDao.selectByProductNo(productNo);
        if (marketingProductPOS != null) {
            List<Integer> marketingIdList = marketingProductPOS.stream().map(MarketingProductPO::getMarketingId).collect(Collectors.toList());
            List<MarketingPO> marketingPOS = iMarketingDao.selectMarketingByIds(marketingIdList);
            for (MarketingPO marketingPO : marketingPOS) {
                String marketingName = marketingPO.getMarketingName();
                Class<?> clazz = Class.forName(marketingName);
                IMarketingStrategy marketingStrategy = (IMarketingStrategy) applicationContext.getBean(clazz);
                marketingStrategy.doMarketing(marketingPO.getInputVal());
            }
            System.out.println("doMarketingJob end =============");
            return true;
        }
        System.out.println("doMarketingJob setting is empty ===========");
        return false;
    }
}

 

具體的思路就和策略模式有點類似:

策略模式

模式定義:定義一系列演算法,將每個演算法都封裝起來,並且它們可以互換。策略模式是一種物件行為模式。

例如下圖:


最後為了方便測試,我在工程裡面引入了spring-context的依賴:

 <!-- 關於spring的核型模組程式碼       -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.1.RELEASE</version>
        </dependency>

 

測試的入口程式碼:

/**
 * @Author idea
 * @Date created in 10:14 上午 2020/5/4
 */
public class ApplicationDemo {

    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        applicationContext.scan("com.sise.idea.present");
        //啟動上下文
        applicationContext.refresh();
        IMarketingCoreService marketingCoreService = applicationContext.getBean(MarketingCoreService.class);
        marketingCoreService.doMarketingJob("p111");
    }
}

 

最後根據規則,通過產品編碼來搜尋到指定的營銷手段,並執行對應的程式邏輯:

設計不足點

文章上邊我曾經提及過,沒有完美點程式碼,只有隨著業務需求不斷變化的設計思路,因此在真正落地整套營銷系統的時候,還需要額外考慮很多的要素。例如說目前的這種設計只能滿足於針對單個產品層面,如果以後有出現針對完整訂單層面(例如說總支付訂單滿xxx元,享受xxx優惠)的還需要額外去思考,加上不同的營銷手段之間是否有出現互斥的場景都是會有可能遇到的情況。

設計模式,是一套被反覆使用、多數人知曉的、經過分類編目的、程式碼設計經驗的總結。使用設計模式是為了可重用程式碼、讓程式碼更容易被他人理解、保證程式碼可靠性、程式的重用性。文中我並沒有過多地去講解什麼是xx模式,但是當通過某種較為靈活的方式來實現某樣功能時,可能就已經使用了設計模式。

https://gitee.com/IdeaHome_admin/design_pattern/tree/master/design-model/src/main/java/com/sise/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F/present

END

Java面試題專欄

【61期】MySQL行鎖和表鎖的含義及區別(MySQL面試第四彈)
【62期】解釋一下MySQL中內連線,外連線等的區別(MySQL面試第五彈)
【63期】談談MySQL 索引,B+樹原理,以及建索引的幾大原則(MySQL面試第六彈)
【64期】MySQL 服務佔用cpu 100%,如何排查問題? (MySQL面試第七彈)
【65期】Spring的IOC是啥?有什麼好處?
【66期】Java容器面試題:談談你對 HashMap 的理解
【67期】談談ConcurrentHashMap是如何保證執行緒安全的?
【68期】面試官:對併發熟悉嗎?說說Synchronized及實現原理
【69期】面試官:對併發熟悉嗎?談談執行緒間的協作(wait/notify/sleep/yield/join)
【70期】面試官:對併發熟悉嗎?談談對volatile的使用及其