營銷系統是一個動態的、有機地結合的系統,經常會隨着業務的不斷變化發生調整,因此從事這一業務的開發可讓我頭疼了。
之前在工作中就不乏一次遇到過隨意調整營銷策略的情況,在部分場景下由於使用了硬編碼的方式來實現,因此在調整策略的時候顯得特別不靈活。
下邊我列舉一個曾經遇到過的應用場景:
業務部門需要上線一款新型的產品,用戶在線上購買了對應的產品,然後下單支付之後需要享受不同的服務內容,這些服務包含了贈送優惠券,發送紅包補貼,加積分,升級等服務項。並且上線之後,可能會隨着市場的因素的調整,部分服務內容也會有所下架,後期調整因素極高。
下邊是一張用戶建模的圖:
線上買單,到選擇購買的產品類型,再到後續下單之後執行不同的營銷規則,每個產品對應不同的服務項目並且服務項目的內容還可能會隨時調整。
舉個實際案例來說,線上有這麼幾款服務產品供消費者選購:
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模式,但是當通過某種較爲靈活的方式來實現某樣功能時,可能就已經使用了設計模式。
關於本案例的完整demo地址如下:
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