出行行业计价模块的设计模式实践

业务场景介绍

在出行行业中,价格的配置随随着业务的增加而增加,而刺激出行的促销活动更是频繁。在价格的计算中,通常的流程如下:

  1. 根据一组价格配置,计算订单基础的价格。
  2. 根据用户拥有的优惠对象,例如优惠券,积分,会员级别等,计算出优惠以后的价格。

计价的业务详细流程图如下:

分析上述的业务流程,得知:

  1. 计算价格的流程就是对原始价格的一种包装和修饰,例如,使用优惠券对优惠价格;基于用户对价格进行优惠,注册新人首单减免优惠,vip会员折扣优惠;重大节假日打折优惠,在价格计算的顶层可以使用装饰模式
  2. 各类价格的计算,满足固定值,乘法规则,价格区间等不同规则,对于优惠计算方式包括抵扣规格和打折规则。这类根据不同规则实现的业务,使用策略模式实现。
  3. 在优惠计算时,由于是基于不同规则为切入点进行计算,针对不同的优惠业务对象,例如,优惠券,vip或新人,节假日等等,其获取优惠的计算的条件规则不一致。可以使用工厂类模式实现优惠条件的获取。
  4. 在其余比较细致的业务中,例如,条件的校验使用责任链模式,乘法规则中,单价和数量的获取根据不同的价格使用组合模式获取。

根据以上的设计,新增一个价格,只需配置具体价格,和计算规则即可。新增一种优惠方式,只需新增价格的优惠装饰类,新增优惠条件对象的工厂类即可,实现了业务的低耦合,高扩展性。各个业务模块详细的设计模式详细如下:

  • 价格计算

定义价格计算接口PriceCalculation,代码如下:

public interface PriceCalculation {

    /**
     * 价格计算
     *
     * @param orderPriceDto 计价订单dto
     * @return
     */
    CalculatedPriceBo calculate(OrderPriceDto orderPriceDto);
}

价格计算接口实现类如下:

  1. 根据价格组计算基础实现类DefaultPriceCalculator
  2. 根据用户特性的价格优惠实现类PriceWithUserCalculator
  3. 根据优惠券的价格优惠装饰类PriceWithCouponCalculator

其代码部分如下:

 /**
  *默认价格计算
  */
public class DefaultPriceCalculator implements PriceCalculation {

    /**
     * 需要计算的价格列表
     */
    private final List<PriceBo> priceList;

    @Override
    public CalculatedPriceBo calculate(OrderPriceDto orderPriceDto) {

        if (CollectionUtils.isEmpty(priceList)) {
            return null;
        }
        CalculatedPriceBo calculatedPrice = new CalculatedPriceBo();
        // 根据计价列表计算基础价格
        return calculatedPrice;
    }
}

 /**
  *基于用户的价格计算
  */
public class PriceWithCouponCalculator implements PriceCalculation {
    /**
     * 使用的优惠券
     */
    private final CouponListVo usedCoupon;
    /**
     * 价格计算代理
     */
    private final PriceCalculation delegate;

    @Override
    public CalculatedPriceBo calculate(OrderPriceDto orderPriceDto) {
        // 原始的计算价格
        CalculatedPriceBo calculatedPriceBo = delegate.calculate(orderPriceDto);
        // TODO根据用户特性进行优惠计算
        return calculatedPriceBo;
    }
}
 /**
  *基于优惠券的价格计算
  */
public class PriceWithCouponCalculator implements PriceCalculation {
    /**
     * 使用的优惠券
     */
    private final CouponListVo usedCoupon;
    /**
     * 价格计算代理
     */
    private final PriceCalculation delegate;

    @Override
    public CalculatedPriceBo calculate(OrderPriceDto orderPriceDto) {
        // 原始的计算价格
        CalculatedPriceBo calculatedPriceBo = delegate.calculate(orderPriceDto);
        // 基于优惠券的优惠计价
        return calculatedPriceBo;
    }
}
  • 价格计算策略

定义计价策略接口PricingStrategy,代码如下:

public interface PricingStrategy {

    /**
     * 计价价格
     */
    BigDecimal doPricing(PriceBo priceBo, OrderPriceDto orderPriceDto);
}

根据价格的计算规则,使用策略模式,实现各类计算规则。如下:

  • 固定值策略-FixedPricingStrategy
  • 乘积策略-MulPricingStrategy
  • 固定值+乘积组合策略-FixedOrMulPricingStrategy
  • 价格组策略-GroupPricingStrategy
  • 价格计算策略上下文-PricingStrategyContext

根据不同的计算策略实现价格计算的PricingStrategyContext,代码如下:

扫描二维码关注公众号,回复: 10715366 查看本文章
public class PricingStrategyContext {

    private final Map<PricingStrategy.TypeEnum, PricingStrategy> pricingStrategyMap;

    private PricingStrategyContext() {

        ImmutableMap.Builder<PricingStrategy.TypeEnum, PricingStrategy> builder = ImmutableMap.builder();
        builder.put(PricingStrategy.TypeEnum.multiplication, new MulPricingStrategy());
        builder.put(PricingStrategy.TypeEnum.fixed, new FixedPricingStrategy());
        builder.put(PricingStrategy.TypeEnum.mulOrFixed, new FixedOrMulPricingStrategy());
        builder.put(PricingStrategy.TypeEnum.group, new GroupPricingStrategy());
        builder.put(PricingStrategy.TypeEnum.remote, new RemotePricingStrategy());
        pricingStrategyMap = builder.build();
    }

    /**
     * 根据计价类型计算价格
     *
     * @param pricingStrategyType 价格计算类型
     * @param orderPriceDto       计价参数
     * @return 价格金额
     */
    public static BigDecimal doPricing(PricingStrategy.TypeEnum pricingStrategyType, PriceBo priceBo, OrderPriceDto orderPriceDto) {

        PricingStrategy pricingStrategy;
        return Objects.nonNull(pricingStrategy = PricingStrategyContextInstance.INSTANCE.pricingStrategyMap.get(pricingStrategyType)) ?
                pricingStrategy.doPricing(priceBo, orderPriceDto) : null;
    }

    // 单例模式
    private static class PricingStrategyContextInstance {
        private static final PricingStrategyContext INSTANCE = new PricingStrategyContext();
    }
}
  • 优惠方式计算

定义优惠计算策略接口DiscountStrategy,代码如下:

public interface DiscountStrategy {

    /**
     * 打折计算
     *
     * @param priceDetail         计价明细
     * @param priceDiscountParams 打折参数
     * @return 打折后的金额
     */
    CalculatedPriceBo.PriceDetail doDiscount(CalculatedPriceBo.PriceDetail priceDetail, PriceDiscountParams priceDiscountParams);
}

优惠券和会员等级优惠的方式都包括对总价进行抵扣或打折。此时,也可以使用策略模式实现优惠方式的计算,如下:

  • 抵扣方式-DeductionDiscountStrategy
  • 打折方式-DiscountDiscountStrategy
  • 打折策略上下文-DiscountStrategyContext

打折策略上下文的源码如下:

public class DiscountStrategyContext {

    private final Map<DiscountStrategy.TypeEnum, DiscountStrategy> discountStrategyMap;

    private DiscountStrategyContext() {
        ImmutableMap.Builder<DiscountStrategy.TypeEnum, DiscountStrategy> builder = ImmutableMap.builder();
        builder.put(DiscountStrategy.TypeEnum.deduction, new DeductionDiscountStrategy());
        builder.put(DiscountStrategy.TypeEnum.discount, new DiscountDiscountStrategy());
        this.discountStrategyMap = builder.build();
    }

    public static CalculatedPriceBo.PriceDetail doDiscount(DiscountStrategy.TypeEnum type,
                                        CalculatedPriceBo.PriceDetail priceDetail, DiscountStrategy.PriceDiscountParams priceDiscountParams) {

        DiscountStrategy discountStrategy;
        return Objects.nonNull(discountStrategy = DiscountStrategyContextInstance.INSTANCE.discountStrategyMap.get(type))
                ? discountStrategy.doDiscount(priceDetail, priceDiscountParams) : null;
    }
    // 单例模式
    private static final class DiscountStrategyContextInstance {
        private static final DiscountStrategyContext INSTANCE = new DiscountStrategyContext();
    }
}
  • 优惠条件的设计

在优惠打折时,会根据使用到的优惠对象,例如vip用户,优惠券,节假日等等进行计算。这时,优惠计算的条件就会从不同的优惠对象中获取。定义优惠对象接口PriceDiscountParams,接口定义如下:

/**
     * 价格打折条件
     */
    interface PriceDiscountParams {
        /**
         * 获取抵扣的金额
         */
        BigDecimal getDeductionAmount();

        /**
         * 获取满足抵扣条件的金额
         */
        BigDecimal getMatchedDeductionAmount();

        /**
         * 获取打折的值(例如 七折就是0.7)
         */
        BigDecimal getDiscountValue();

        /**
         * 获取最高抵扣金额
         */
        BigDecimal getMaxDiscountAmount();

        /**
         * 获取使用打折的对象
         */
        Map<String, Object> getUsedDiscount();
    }

这时,使用工厂类模式根据不同的优惠对象生成优惠参数,定义优惠条件工厂类接口PriceDiscountParamsFactory

,代码如下:

public interface PriceDiscountParamsFactory {

    /**
     * @return 生成打折参数
     */
    DiscountStrategy.PriceDiscountParams create();
}

其子类工厂实现类如下:

  • CouponPriceDiscountParamsFactory,根据优惠券生成优惠条件
  • UserPriceDiscountParamsFactory,根据用户生成游湖条件
public class CouponPriceDiscountParamsFactory implements PriceDiscountParamsFactory {

    /**
     * 打折使用的优惠券
     */
    private final CouponListVo usedCoupon;

    public CouponPriceDiscountParamsFactory(CouponListVo usedCoupon) {
        this.usedCoupon = usedCoupon;
    }

    @Override
    public DiscountStrategy.PriceDiscountParams create() {
    // 根据优惠券创建参数
    return new DiscountStrategy.PriceDiscountParams() ;
   }
  
 }



public class UserPriceDiscountParamsFactoryimplements PriceDiscountParamsFactory {

    private final UserInfo user;

    public UserPriceDiscountParamsFactory(UserInfo user) {
        this.user= user;
    }

    @Override
    public DiscountStrategy.PriceDiscountParams create() {
        // 根据vip用户创建参数
        return new DiscountStrategy.PriceDiscountParams() {
            @Override
            public BigDecimal getDeductionAmount();
    }
}

  • 优惠条件的匹配规则

在优惠券使用规则的筛选认证流程中,使用责任链模式,使以后可以动态扩展各类筛选条件。定义优惠券过滤接口CouponFilter,过滤器责任链接口CouponFilterChain,和组合链式接口CouponFilterPipeline。设计的过滤条件如下:

  • 城市规则过滤-CityCouponFilter
  • 车辆类型过滤-VehicleTypeCouponFilter
  • 默认的责任链实现-DefaultCouponFilterPipeline,其代码如下:
public class DefaultCouponFilterPipeline implements CouponFilterPipeline {

    private final List<CouponFilter> couponFilterList;

    public DefaultCouponFilterPipeline() {
        this.couponFilterList = Lists.newArrayList(new CityCouponFilter(), new VehicleTypeCouponFilter());
    }

    @Override
    public boolean doFilter(CouponListVo couponBo, OrderPriceDto orderPriceDto) {
        return new DefaultCouponFilterChain(couponFilterList).doFilter(couponBo, orderPriceDto);
    }

    private static class DefaultCouponFilterChain implements CouponFilterChain {

        private final List<CouponFilter> couponFilterList;
        private int currentIndex;

        private DefaultCouponFilterChain(List<CouponFilter> couponFilterList) {
            this.couponFilterList = couponFilterList;
            this.currentIndex = 0;
        }

        @Override
        public boolean doFilter(CouponListVo couponBo, OrderPriceDto orderPriceDto) {

            CouponFilter couponFilter;
            if (CollectionUtils.isEmpty(couponFilterList)
                    || currentIndex >= couponFilterList.size()
                    || Objects.isNull(couponFilter = couponFilterList.get(currentIndex++))) {
                return true;
            }
            return couponFilter.doFilter(couponBo, orderPriceDto, this);
        }
    }
}

源码可以参考https://github.com/alldays/spring-cloud-mall.git

发布了37 篇原创文章 · 获赞 2 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/new_com/article/details/105038803