重学 Java 设计模式:实战策略模式「模拟多种营销类型优惠券,折扣金额计算策略场景」


作者:小傅哥
博客:https://bugstack.cn - 原创系列专题文章

沉淀、分享、成长,让自己和他人都能有所收获!😄

一、前言

文无第一,武无第二

不同方向但同样努力的人,都有自身的价值和亮点,也都是可以互相学习的。不要太过于用自己手里的矛去攻击别人的盾🛡,哪怕一时争辩过了也多半可能是你被安放的角色不同。取别人之强补自己之弱,矛与盾的结合可能就是坦克。

能把复杂的知识讲的简单很重要

在学习的过程中我们看过很多资料、视频、文档等,因为现在资料视频都较多所以往往一个知识点会有多种多样的视频形式讲解。除了推广营销以外,确实有很多人的视频讲解非常优秀,例如李永乐老师的短视频课,可以在一个黑板上把那么复杂的知识,讲解的那么容易理解,那么透彻。而我们学习编程的人也是,不只是要学会把知识点讲明白,也要写明白。

🙉提升自己的眼界交往更多同好

有时候圈子很重要,就像上学期间大家都会发现班里有这样一类学生👩‍🎓不怎么听课,但是就是学习好。那假如让他回家呆着,不能在课堂里呢?类似的圈子还有;图书馆、网吧、车友群、技术群等等,都可以给你带来同类爱好的人所分享出来的技能或者大家一起烘托出的氛围帮你成长。

二、开发环境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三个,可以通过关注公众号bugstack虫洞栈,回复源码下载获取(打开获取的链接,找到序号18)
工程 描述
itstack-demo-design-20-01 使用一坨代码实现业务需求
itstack-demo-design-20-02 通过设计模式优化改造代码,产生对比性从而学习

三、策略模式介绍

策略模式,图片来自 refactoringguru.cn

策略模式是一种行为模式,也是替代大量ifelse的利器。它所能帮你解决的是场景,一般是具有同类可替代的行为逻辑算法场景。比如;不同类型的交易方式(信用卡、支付宝、微信)、生成唯一ID策略(UUID、DB自增、DB+Redis、雪花算法、Leaf算法)等,都可以使用策略模式进行行为包装,供给外部使用。

诸葛亮锦囊妙计

策略模式也有点像三国演义中诸葛亮给刘关张的锦囊;

  • 第一个锦囊:见乔国老,并把刘备娶亲的事情du搞得东吴人尽皆知。
  • 第二个锦囊:用谎言(曹操打荆州)骗泡在温柔乡里的刘备回去。
  • 第三个锦囊:让孙夫人摆平东吴的追兵,她是孙权妹妹,东吴将领惧她三分。

四、案例场景模拟

场景模拟;商品支付使用营销优惠券

在本案例中我们模拟在购买商品时候使用的各种类型优惠券(满减、直减、折扣、n元购)

这个场景几乎也是大家的一个日常购物省钱渠道,购买商品的时候都希望找一些优惠券,让购买的商品更加实惠。而且到了大促的时候就会有更多的优惠券需要计算那些商品一起购买更加优惠!!!

这样的场景有时候用户用起来还是蛮爽的,但是最初这样功能的设定以及产品的不断迭代,对于程序员👨‍💻‍开发还是不太容易的。因为这里包括了很多的规则和优惠逻辑,所以我们模拟其中的一个计算优惠的方式,使用策略模式来实现。

五、用一坨坨代码实现

这里我们先使用最粗暴的方式来实现功能

对于优惠券的设计最初可能非常简单,就是一个金额的抵扣,也没有现在这么多种类型。所以如果没有这样场景的经验话,往往设计上也是非常简单的。但随着产品功能的不断迭代,如果程序最初设计的不具备很好的扩展性,那么往后就会越来越混乱。

1. 工程结构

itstack-demo-design-20-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── CouponDiscountService.java
  • 一坨坨工程的结构很简单,也是最直接的面向过程开发方式。

2. 代码实现

/**
 * 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
 * 公众号:bugstack虫洞栈
 * Create by 小傅哥(fustack) @2020
 * 优惠券折扣计算接口
 * <p>
 * 优惠券类型;
 * 1. 直减券
 * 2. 满减券
 * 3. 折扣券
 * 4. n元购
 */
public class CouponDiscountService {

    public double discountAmount(int type, double typeContent, double skuPrice, double typeExt) {
        // 1. 直减券
        if (1 == type) {
            return skuPrice - typeContent;
        }
        // 2. 满减券
        if (2 == type) {
            if (skuPrice < typeExt) return skuPrice;
            return skuPrice - typeContent;
        }
        // 3. 折扣券
        if (3 == type) {
            return skuPrice * typeContent;
        }
        // 4. n元购
        if (4 == type) {
            return typeContent;
        }
        return 0D;
    }

}
  • 以上是不同类型的优惠券计算折扣后的实际金额。
  • 入参包括;优惠券类型、优惠券金额、商品金额,因为有些优惠券是满多少减少多少,所以增加了typeExt类型。这也是方法的不好扩展性问题。
  • 最后是整个的方法体中对优惠券抵扣金额的实现,最开始可能是一个最简单的优惠券,后面随着产品功能的增加,不断的扩展if语句。实际的代码可能要比这个多很多。

六、策略模式重构代码

接下来使用策略模式来进行代码优化,也算是一次很小的重构。

与上面面向流程式的开发这里会使用设计模式,优惠代码结构,增强整体的扩展性。

1. 工程结构

itstack-demo-design-20-02
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                ├── event
                │    └── MJCouponDiscount.java
                │    └── NYGCouponDiscount.java
                │    └── ZJCouponDiscount.java
                │    └── ZKCouponDiscount.java
                ├── Context.java
                └── ICouponDiscount.java

策略模式模型结构

策略模式模型结构

  • 整体的结构模式并不复杂,主要体现的不同类型的优惠券在计算优惠券方式的不同计算策略。
  • 这里包括一个借口类(ICouponDiscount)以及四种优惠券类型的实现方式。
  • 最后提供了策略模式的上下文控制类处理,整体的策略服务。

2. 代码实现

2.1 优惠券接口

public interface ICouponDiscount<T> {

    /**
     * 优惠券金额计算
     * @param couponInfo 券折扣信息;直减、满减、折扣、N元购
     * @param skuPrice   sku金额
     * @return           优惠后金额
     */
    BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice);

}

  • 定义了优惠券折扣接口,也增加了泛型用于不同类型的接口可以传递不同的类型参数。
  • 接口中包括商品金额以及出参返回最终折扣后的金额,这里在实际开发中会比现在的接口参数多一些,但核心逻辑是这些。

2.2 优惠券接口实现

满减

public class MJCouponDiscount implements ICouponDiscount<Map<String,String>>  {

    /**
     * 满减计算
     * 1. 判断满足x元后-n元,否则不减
     * 2. 最低支付金额1元
     */
    public BigDecimal discountAmount(Map<String,String> couponInfo, BigDecimal skuPrice) {
        String x = couponInfo.get("x");
        String o = couponInfo.get("n");

        // 小于商品金额条件的,直接返回商品原价
        if (skuPrice.compareTo(new BigDecimal(x)) < 0) return skuPrice;
        // 减去优惠金额判断
        BigDecimal discountAmount = skuPrice.subtract(new BigDecimal(o));
        if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE;

        return discountAmount;
    }
}

直减

public class ZJCouponDiscount implements ICouponDiscount<Double>  {

    /**
     * 直减计算
     * 1. 使用商品价格减去优惠价格
     * 2. 最低支付金额1元
     */
    public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
        BigDecimal discountAmount = skuPrice.subtract(new BigDecimal(couponInfo));
        if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE;
        return discountAmount;
    }

}

折扣

public class ZKCouponDiscount implements ICouponDiscount<Double> {


    /**
     * 折扣计算
     * 1. 使用商品价格乘以折扣比例,为最后支付金额
     * 2. 保留两位小数
     * 3. 最低支付金额1元
     */
    public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
        BigDecimal discountAmount = skuPrice.multiply(new BigDecimal(couponInfo)).setScale(2, BigDecimal.ROUND_HALF_UP);
        if (discountAmount.compareTo(BigDecimal.ZERO) < 1) return BigDecimal.ONE;
        return discountAmount;
    }

}

N元购

public class NYGCouponDiscount implements ICouponDiscount<Double> {

    /**
     * n元购购买
     * 1. 无论原价多少钱都固定金额购买
     */
    public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
        return new BigDecimal(couponInfo);
    }

}
  • 以上是四种不同类型的优惠券计算折扣金额的策略方式,可以从代码中看到每一种优惠方式的优惠金额。

2.3 策略控制类

public class Context<T> {

    private ICouponDiscount<T> couponDiscount;

    public Context(ICouponDiscount<T> couponDiscount) {
        this.couponDiscount = couponDiscount;
    }

    public BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice) {
        return couponDiscount.discountAmount(couponInfo, skuPrice);
    }

}
  • 策略模式的控制类主要是外部可以传递不同的策略实现,在通过统一的方法执行优惠策略计算。
  • 另外这里也可以包装成map结构,让外部只需要对应的泛型类型即可使用相应的服务。

3. 测试验证

3.1 编写测试类(直减优惠)

@Test
public void test_zj() {
    // 直减;100-10,商品100元
    Context<Double> context = new Context<Double>(new ZJCouponDiscount());
    BigDecimal discountAmount = context.discountAmount(10D, new BigDecimal(100));
    logger.info("测试结果:直减优惠后金额 {}", discountAmount);
}

测试结果

15:43:22.035 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:直减优惠后金额 90

Process finished with exit code 0

3.2 编写测试类(满减优惠)

@Test
public void test_mj() {
    // 满100减10,商品100元
    Context<Map<String,String>> context = new Context<Map<String,String>>(new MJCouponDiscount());
    Map<String,String> mapReq = new HashMap<String, String>();
    mapReq.put("x","100");
    mapReq.put("n","10");
    BigDecimal discountAmount = context.discountAmount(mapReq, new BigDecimal(100));
    logger.info("测试结果:满减优惠后金额 {}", discountAmount);
}

测试结果

15:43:42.695 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:满减优惠后金额 90

Process finished with exit code 0

3.3 编写测试类(折扣优惠)

@Test
public void test_zk() {
    // 折扣9折,商品100元
    Context<Double> context = new Context<Double>(new ZKCouponDiscount());
    BigDecimal discountAmount = context.discountAmount(0.9D, new BigDecimal(100));
    logger.info("测试结果:折扣9折后金额 {}", discountAmount);
}

测试结果

15:44:05.602 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:折扣9折后金额 90.00

Process finished with exit code 0

3.4 编写测试类(n元购优惠)

@Test
public void test_nyg() {
    // n元购;100-10,商品100元
    Context<Double> context = new Context<Double>(new NYGCouponDiscount());
    BigDecimal discountAmount = context.discountAmount(90D, new BigDecimal(100));
    logger.info("测试结果:n元购优惠后金额 {}", discountAmount);

测试结果

15:44:24.700 [main] INFO  org.itstack.demo.design.test.ApiTest - 测试结果:n元购优惠后金额 90

Process finished with exit code 0
  • 以上四组测试分别验证了不同类型优惠券的优惠策略,测试结果是满足我们的预期。
  • 这里四种优惠券最终都是在原价100元上折扣10元,最终支付90元

七、总结

  • 以上的策略模式案例相对来说不并不复杂,主要的逻辑都是体现在关于不同种类优惠券的计算折扣策略上。结构相对来说也比较简单,在实际的开发中这样的设计模式也是非常常用的。另外这样的设计与命令模式、适配器模式结构相似,但是思路是有差异的。
  • 通过策略设计模式的使用可以把我们方法中的if语句优化掉,大量的if语句使用会让代码难以扩展,也不好维护,同时在后期遇到各种问题也很难维护。在使用这样的设计模式后可以很好的满足隔离性与和扩展性,对于不断新增的需求也非常方便承接。
  • 策略模式适配器模式组合模式等,在一些结构上是比较相似的,但是每一个模式是有自己的逻辑特点,在使用的过程中最佳的方式是经过较多的实践来吸取经验,为后续的研发设计提供更好的技术输出。

八、推荐阅读

小傅哥 CSDN认证博客专家 ASM 设计模式 bugstack
小傅哥多年从事一线互联网Java开发,旨在为大家提供一个清晰详细的学习教程,侧重点更倾向编写Java核心内容。如果能为您提供帮助,请给予支持(关注、点赞、分享)!在过去码文的一年里付出了所有的深夜和假期进行码文,创建了9个较大专题类文章;《用Java实现JVM》、《Netty4.x专题》、《中间件开发》、《领域驱动设计》、《全链路监控》等和近150篇原创,也整理了第一本关于字节码编程的PDF书籍,共计11万余字。同时建设了第一个关于:CodeGuide|程序员编码指南,的Git仓库,涵盖了;书籍、源码、案例、文章等各项学习拓展技能的资料。

java项目之oa办公管理系统(java毕业设计)

03-31
项目涉及技术:    1、前端:jsp、css、javascript、jQuery(js框架)、jQueryEasyUi(基于jQuery的前端框架)+BootStrap(前端框架)、dTree插件、富文本编辑器等  2、后台:SpringMvc、Spring、Hibernate(持久层框架)、JPA、MD5加密、Spring Boot、 log4J日志框架、jstl、jstl自定义分页标签等 3、项目管理工具:Maven 4、数据库:Mysql 5、服务器:Tomcat 项目开发涉及功能: 1、项目以及数据库搭建 2、用户异步登录、退出以及记住用户信息 3、自定义分页标签实现(24种样式随意切换) 4、拦截器功能实现 5、整合前端框架 EasyUi以及Bootstrap 6、用户模块信息添加、修改、删除、多条件分页查询、激活、预览、用户账号异步校验、异步加载部门以及职位信息 7、角色模块添加、删除、修改、分页查询、角色绑定以及解绑用户、角色绑定以及解绑操作 8、Spring事务处理机制讲解 9、模块管理增加、修改、删除、分页查询、整合dTree树形插件 10、项目实现权限控制 11、公告模块整合富文本编辑器 12、密码进行MD5加密 13、项目改版成Spring Boot实现 14、代码机器人使用等等功能 其他实战项目: java项目实战之电商系统全套(前台和后台)(java毕业设计ssm框架项目) https://edu.csdn.net/course/detail/25771 java项目之oa办公管理系统(java毕业设计) https://edu.csdn.net/course/detail/23008 java项目之hrm人事管理项目(java毕业设计) https://edu.csdn.net/course/detail/23007 JavaWeb项目实战之点餐系统前台 https://edu.csdn.net/course/detail/20543 JavaWeb项目实战之点餐系统后台 https://edu.csdn.net/course/detail/19572 JavaWeb项目实战之宿舍管理系统(Java毕业设计含源码) https://edu.csdn.net/course/detail/26721 JavaWeb项目实战之点餐系统全套(前台和后台) https://edu.csdn.net/course/detail/20610 java项目实战之电子商城后台(java毕业设计SSM框架项目) https://edu.csdn.net/course/detail/25770 java美妆商城项目|在线购书系统(java毕业设计项目ssm版) https://edu.csdn.net/course/detail/23989 系统学习课程: JavaSE基础全套视频(环境搭建 面向对象 正则表达式 IO流 多线程 网络编程 java10 https://edu.csdn.net/course/detail/26941 Java Web从入门到电商项目实战挑战万元高薪(javaweb教程) https://edu.csdn.net/course/detail/25976 其他素材版(毕业设计或课程设计)项目:点击老师头像进行相关课程学习

Springboot+MyBatis企业级RESTful API开发项目实战/java视频教程

01-30
课程简介 这是一门使用Java语言,SpringBoot框架,从0开发一个RESTful API应用,接近企业级的项目(我的云音乐),课程包含了基础内容,高级内容,项目封装,项目重构等知识,99%代码为手写;因为这是项目课程;所以不会深入到源码讲解某个知识点,以及原理,但会粗略的讲解下基础原理;主要是讲解如何使用系统功能,流行的第三方框架,第三方服务,完成接近企业级项目,目的是让大家,学到真正的企业级项目开发技术。 适用人群 刚刚毕业的学生 想提高职场竞争力 想学从零开发SpringBoot项目 想提升SpringBoot项目开发技术 想学习SpringBoot项目架构技术 想学习企业级项目开发技术 就是想学习SpringBoot开发 能学到什么 从0开发一个类似企业级项目 学会能做出市面上90%通用API 快速增加1到2年实际开发经验 刚毕业学完后能找到满意的工作 已经工作学完后最高涨薪30% 课程信息 全课程目前是82章,155小时,每节视频都经过精心剪辑。 在线学习分辨率最高1080P 课程知识点 1~11章:学习方法,项目架构,编码规范,Postman使用方法,Git和Github版本控制 12~16章:搭建开发环境,快速入门SpringBoot框架 17~20章:快速入门MySQL数据库 21~30章:MyBatis,登录注册,找回密码,发送短信,发送邮件,企业级接口配置 31~41章:实现歌单,歌单标签,音乐,列表分页,视频,评论,好友功能 42~48章:阿里云OSS,话题,MyBatis-plus,应用监控 49~53章:Redis使用,集成Redis,SpringCache,HTTP缓存 54~58章:Elasticsearch使用,集成Elasticsearch,使用ES搜索 59~61章:商城,集成支付宝SDK,支付宝支付 62~64章:常用哈希和加密算法,接口加密和签名 65~67章:实时挤掉用户,企业级项目测试环境,企业级接口文档 68~69章:SpringBoot全站HTTPS,自签证书,申请免费证书 70~73章:云MySQL数据库,云Redis数据库使用,轻量级应用部署环境,域名解析 74~80章:Docker使用,生产级Kubernetes集群,域名解析,集群全站HTTPS 81~82章:增强和重构项目,课程总结,后续学习计划
©️2020 CSDN 皮肤主题: 数字20 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值