策略模式在公司项目中的运用实践,看完又能够涨一波实战经验了!

营销系统是一个动态的、有机地结合的系统,常常会随着业务的不断变化发生调整,所以从事这一业务的开发可以让我头疼了。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的使用及其原理

相关文章
相关标签/搜索