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

2023-11-11

业务场景介绍

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

  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,代码如下:

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

出行行业计价模块的设计模式实践 的相关文章

  • 汇编语言——串操作指令

    若要处理连续内存单元中的一批数据 通常需要借助于循环 80x86CPU提供了一组串操作指令 可用来处理内存中的数据 字节 字 双字 串 这些指令包括MOVS LODS STOS SCAS CMPS 为了指出操作对象的长度 在助记符后加上B

随机推荐

  • 下载的文件被Windows 11 安全中心自动删除

    今天从CSDN上下载了自己曾经上传的文件 但是浏览器下载完之后文件被Windows安全中心自动删除 说是带病毒 实际是没有病毒的 再说了即便有病毒也不应该直接删除啊 至少给用户一个保留或删除的选项 研究了一番 可以暂时关闭安全中心的实时保护
  • 基于UDP实现简易聊天

    概述 UDP没有创建连接 数据包是一次收发一个 没有流的概念 但是在UDP编程中需要用到的是Socket 因为应用程序在使用UDP时必须指定网络接口 IP地址 和端口号 服务器端 在服务器端 使用UDP也需要监听指定的端口 Java提供了D
  • OTSU算法 (大津算法)理解&代码

    OTSU算法 对图像进行二值化的算法 介绍 OTSU算法是一种自适应的阈值确定的方法 又称大津阈值分割法 是最小二乘法意义下的最优分割 它是按图像的灰度特性 将图像分成背景和前景两部分 因方差是灰度分布均匀性的一种度量 背景和前景之间的类间
  • SpringBoot的yml文件中map,对象,list的嵌套使用

    ems ems real ip map 127 0 0 110 Huawei NCE OTN 127 0 0 33 Huawei NCE ROADM ems collect config map Huawei NCE OTN host 12
  • PTA 求最大、次大和第3大的值 (25 分)

    本题目要求读入n个整数 要求用最少的比较次数 输出它们的最大值 第2大的值和第3大的值 例如 对于13 13 1 10 34 10这6个数 最大值为34 第2大的值为13 第3大的值为10 输入格式 输入有两行 第一行为整数个数n 1 00
  • Redis多数据中心复制管理系统—— X-Pipe

    Redis多数据中心复制管理系统 X Pipe Redis 在携程内部得到了广泛的使用 根据客户端数据统计 整个携程全部 Redis 的读写请求在每秒 200W 其中写请求约 10W 很多业务甚至会将 Redis 当成内存数据库使用 这样
  • 基于51单片机实现继电器控制照明设备(Proteus仿真)

    wechat 嵌入式工程师成长日记 具体功能实现 当按下开关时 继电器闭合点亮照明设备 灯泡 使用器件 照明设备 LAMP 按键 AT89C51 若干电阻 PNP晶体管 二极管 继电器 RTE24005F Proteus仿真原理图 仿真 知
  • C++字符串【string】和【char []】操作全攻略

    异想之旅 本人原创博客完全手敲 绝对非搬运 全网不可能有重复 本人无团队 仅为技术爱好者进行分享 所有内容不牵扯广告 本人所有文章仅在CSDN 掘金和个人博客 一定是异想之旅域名 发布 除此之外全部是盗文 一 char 类型 1 定义与输入
  • Linux_18.04 Failed to load module "canberra-gtk-module"

    解决办法 sudo apt install libcanberra gtk module
  • openGL之API学习(二十六)glTexImage2D

    给2维纹理分配显存空间 也可以从内存向显存拷贝数据 void glTexImage2D GLenum target GLint level GLint internalformat GLsizei width GLsizei height
  • 你需要知道的 Selenium4 新特性

    前言 最近又用到了Selneium 发现已经来到了 4 9 版本了 本篇文章来介绍下它较比 Selenium3 的一些新特性 记录下 当是做笔记了 最令人惊喜的是 Selenium4 会自动回收浏览器资源 本文所使用的 Selenium 版
  • linux常用命令及解释大全(一)

    目录 一 系统信息 二 关机 重启及登出 三 文件和目录 3 1 导航命令 3 2 查看命令 3 3 创建和删除命令 3 4 复制和链接命令 3 5 其他命令 四 文件搜索 五 挂载文件系统 六 磁盘空间 七 用户和群组 总结 前言 Lin
  • js 拦截alert对话框

  • 若依框架前后端分离版——导入功能

    引言 主要是记录自己使用若依框架并增加其导入功能的实现过程 前端部分 在相应的index vue中添加以下代码 1 数据导入的按钮 v hasPermi 是权限相关的配置
  • Git版本回退并提交远程

    1 进入远程git 在提交纪录中找到需要回退的版本 复制版本号 2 终端进入项目 并执行git reset hard 23a50a1fXXX41XXXXX0227 3 把修改推送至远程 执行已下指令 git push f u origin
  • thrift开发问题总结

    作为目前最流行的RPC框架 thrift不仅提供了通信协议 同时提供了网络框架 解脱了程序员的生产力 thrift也是阿帕奇Hadoop系列的RPC实现工具 本文主要聚焦在实现的thrift系统中 遇到的各种问题 但是thrift在隐藏一些
  • matlab 矩阵增加行,MATLAB 中 如何在矩阵中插入1行

    点击查看MATLAB 中 如何在矩阵中插入1行具体信息 答 举例来说吧假如你已有矩阵A如下 A 2 3 5 3 4 1 0 9 7 这是一个3 3矩阵 那么当你想插入一行r 1 2 3 时 那么可以这样做 A A r 这样A就变成了4 3的
  • Gin框架(学习笔记)

    目录 学习地址 gin 路由 routes group gin 中间件 Cookie 重定向 同步异步 日志文件 学习地址 https www topgoer com gin E6 A1 86 E6 9E B6 gin 路由 package
  • windows操作系统蓝屏错误对照表

    windows操作系统蓝屏错误对照表
  • 出行行业计价模块的设计模式实践

    业务场景介绍 在出行行业中 价格的配置随随着业务的增加而增加 而刺激出行的促销活动更是频繁 在价格的计算中 通常的流程如下 根据一组价格配置 计算订单基础的价格 根据用户拥有的优惠对象 例如优惠券 积分 会员级别等 计算出优惠以后的价格 计