营销系统是一个动态的、有机地结合的系统,经常会随着业务的不断变化发生调整,因此从事这一业务的开发可让我头疼了。
之前在工作中就不乏一次遇到过随意调整营销策略的情况,在部分场景下由于使用了硬编码的方式来实现,因此在调整策略的时候显得特别不灵活。
下边我列举一个曾经遇到过的应用场景:
业务部门需要上线一款新型的产品,用户在线上购买了对应的产品,然后下单支付之后需要享受不同的服务内容,这些服务包含了赠送优惠券,发送红包补贴,加积分,升级等服务项。并且上线之后,可能会随着市场的因素的调整,部分服务内容也会有所下架,后期调整因素极高。
下边是一张用户建模的图:
线上买单,到选择购买的产品类型,再到后续下单之后执行不同的营销规则,每个产品对应不同的服务项目并且服务项目的内容还可能会随时调整。
举个实际案例来说,线上有这么几款服务产品供消费者选购:
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%E