營銷系統是一個動態的、有機地結合的系統,常常會隨着業務的不斷變化發生調整,所以從事這一業務的開發可以讓我頭疼了。java
以前在工做中就不乏一次遇到過隨意調整營銷策略的狀況,在部分場景下因爲使用了硬編碼的方式來實現,所以在調整策略的時候顯得特別不靈活。git
下邊我列舉一個曾經遇到過的應用場景:面試
業務部門須要上線一款新型的產品,用戶在線上購買了對應的產品,而後下單支付以後須要享受不一樣的服務內容,這些服務包含了贈送優惠券,發送紅包補貼,加積分,升級等服務項。而且上線以後,可能會隨着市場的因素的調整,部分服務內容也會有所下架,後期調整因素極高。算法
下邊是一張用戶建模的圖:spring
線上買單,到選擇購買的產品類型,再到後續下單以後執行不一樣的營銷規則,每一個產品對應不一樣的服務項目而且服務項目的內容還可能會隨時調整。數據庫
舉個實際案例來講,線上有這麼幾款服務產品供消費者選購:設計模式
1.999元會員套餐安全
正常會員服務期1個月併發
發放5張優惠券app
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的使用及其原理