Java设计模式:四、行为型模式-08:策略模式

文章目录

  • 一、定义:策略模式
  • 二、模拟场景:策略模式
  • 三、违背方案:策略模式
    • [3.0 引入依赖](#3.0 引入依赖)
    • [3.1 工程结构](#3.1 工程结构)
    • [3.2 优惠券折扣计算类](#3.2 优惠券折扣计算类)
    • [3.3 单元测试](#3.3 单元测试)
  • 四、改善代码:策略模式
    • [4.1 工程结构](#4.1 工程结构)
    • [4.2 策略模式结构图](#4.2 策略模式结构图)
    • [4.3 优惠券折扣实现](#4.3 优惠券折扣实现)
      • [4.3.1 定义优惠券接口](#4.3.1 定义优惠券接口)
      • [4.3.2 满减优惠券接口实现](#4.3.2 满减优惠券接口实现)
      • [4.3.3 直减优惠券接口实现](#4.3.3 直减优惠券接口实现)
      • [4.3.4 折扣优惠券接口实现](#4.3.4 折扣优惠券接口实现)
      • [4.3.5 n元购优惠券接口实现](#4.3.5 n元购优惠券接口实现)
      • [4.3.6 策略控制类](#4.3.6 策略控制类)
    • [4.4 单元测试](#4.4 单元测试)
      • [4.4.1 直减券测试](#4.4.1 直减券测试)
      • [4.4.2 满减券测试](#4.4.2 满减券测试)
      • [4.4.3 折扣券测试](#4.4.3 折扣券测试)
      • [4.4.4 n元购测试](#4.4.4 n元购测试)
  • 五、总结:策略模式

一、定义:策略模式

  • 策略模式是具有同类可替代的行为逻辑算法场景 。比如:
    • 不同类型的交易方式(信用卡、支付宝、微信)。
    • 生成唯一 ID 策略( UUIDDB自增DB+Redis雪花算法Leaf算法)等。

二、模拟场景:策略模式

  • 模拟在购买商品时使用的各种类型优惠券(满减、直减、折扣、m元)。
  • 这个场景几乎也是大家的一个日常购物省钱渠道,购买商品的时候都希望找一些优惠券,让购买的商品更加实惠。而且到了大促的时候就会有更多的优惠券需要计算那些商品一起购买更加优惠。
  • 这样的场景有时候用户用起来还是蛮爽的,但是最初这样功能的设定以及产品的不断迭代,对于程序员开发还是不容易的。
    • 因为这里包括了很多的规则和优惠逻辑,所以我们模拟其中的一个计算优惠的方式,使用策略模式来实现。

三、违背方案:策略模式

📖 对于优惠券的设计最初可能非常简单,就是一个金额的折扣,也没有现在这么多种类型。

所以如果没有这样场景的经验,往往设计上也是非常简单的。

但随着产品功能的不断迭代,如果程序最初设计的不具备很好的扩展性,那么往后就会越来越混乱。

3.0 引入依赖

xml 复制代码
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.62</version>
    </dependency>
    <!-- LOGGING begin -->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.5</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.5</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.0.9</version>
        <exclusions>
            <exclusion>
                <artifactId>slf4j-api</artifactId>
                <groupId>org.slf4j</groupId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

3.1 工程结构

bash 复制代码
design-21.0-1
|------src
	|------main
		|--java
			|--com.lino.design
				|-CouponDiscountService.java
		|--test
			|--com.lino.design.test
				|-ApiTest.java

3.2 优惠券折扣计算类

CouponDiscountService.java

java 复制代码
package com.lino.design;

/**
 * @description: 优惠券折扣计算接口
 */
public class CouponDiscountService {

    /**
     * 计算优惠券折扣
     *
     * @param type        优惠券类型:1-直减券,2-满减券,3-折扣券,4-n元购
     * @param typeContent 折扣价格
     * @param skuPrice    商品价格
     * @param typeExt     满减价格
     * @return 折扣后的价格
     */
    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 语句。

3.3 单元测试

ApiTest.java

java 复制代码
package com.lino.design.test;

import com.lino.design.CouponDiscountService;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @description: 单元测试
 */
public class ApiTest {

    private Logger logger = LoggerFactory.getLogger(ApiTest.class);

    @Test
    public void test() {
        CouponDiscountService couponDiscountService = new CouponDiscountService();
        double result1 = couponDiscountService.discountAmount(1, 10D, 100D, 0D);
        logger.info("测试结果:直减优惠后金额:{}", result1);

        double result2 = couponDiscountService.discountAmount(2, 10D, 100D, 0D);
        logger.info("测试结果:满减优惠后金额:{}", result2);

        double result3 = couponDiscountService.discountAmount(3, 0.9D, 100D, 0D);
        logger.info("测试结果:折扣优惠后金额:{}", result3);

        double result4 = couponDiscountService.discountAmount(4, 90D, 100D, 0D);
        logger.info("测试结果:n元购金额:{}", result4);
    }
}

测试结果

java 复制代码
17:05:07.040 [main] INFO  com.lino.design.test.ApiTest - 测试结果:直减优惠后金额:90.0
17:05:07.049 [main] INFO  com.lino.design.test.ApiTest - 测试结果:满减优惠后金额:90.0
17:05:07.049 [main] INFO  com.lino.design.test.ApiTest - 测试结果:折扣优惠后金额:90.0
17:05:07.049 [main] INFO  com.lino.design.test.ApiTest - 测试结果:n元购金额:90.0

四、改善代码:策略模式

💡 重构使用策略模式,优化代码结构,增强整体的扩展性。

4.1 工程结构

jsx 复制代码
design-21.0-2
|------src
	|------main
		|--java
			|--com.lino.design
				|--impl
				|		|--MJCouponDiscount.java
				|		|--NYGCouponDiscount.java
				|		|--ZJCouponDiscount.java
				|		|--ZKCouponDiscount.java
				|-Context.java
				|-ICouponDiscount.java
		|--test
			|--com.lino.design.test
				|-ApiTest.java

4.2 策略模式结构图

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

4.3 优惠券折扣实现

4.3.1 定义优惠券接口

ICouponDiscount.java

java 复制代码
package com.lino.design;

import java.math.BigDecimal;

/**
 * @description: 优惠券折扣计算接口
 */
public interface ICouponDiscount<T> {

    /**
     * 计算优惠券折扣
     *
     * @param couponInfo 优惠券信息泛型
     * @param skuPrice   商品价格
     * @return 优惠券折扣后的价格
     */
    BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice);
}
  • 定义了优惠券折扣接口,也增加了泛型用于不同类型的接口可以传递不同的类型参数。
  • 接口中包括商品金额以及出参返回最终折扣后的金额。

4.3.2 满减优惠券接口实现

MJCouponDiscount.java

java 复制代码
package com.lino.design.impl;

import com.lino.design.ICouponDiscount;
import java.math.BigDecimal;
import java.util.Map;

/**
 * @description: 满减券
 */
public class MJCouponDiscount implements ICouponDiscount<Map<String, String>> {

    /**
     * 满减计算
     * 1.判断满足x元后-n元,否则不减
     * 2.最低 支付金额1元
     *
     * @param couponInfo 优惠券信息泛型
     * @param skuPrice   商品价格
     * @return 优惠后价格
     */
    @Override
    public BigDecimal discountAmount(Map<String, String> couponInfo, BigDecimal skuPrice) {
        String x = couponInfo.get("x");
        String n = couponInfo.get("n");

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

4.3.3 直减优惠券接口实现

ZJCouponDiscount.java

java 复制代码
package com.lino.design.impl;

import com.lino.design.ICouponDiscount;
import java.math.BigDecimal;

/**
 * @description: 直减券
 */
public class ZJCouponDiscount implements ICouponDiscount<Double> {

    /**
     * 直减计算
     * 1.使用商品价格减去优惠价格
     * 2.最低支付金额1元
     *
     * @param couponInfo 优惠券信息泛型
     * @param skuPrice   商品价格
     * @return 优惠后价格
     */
    @Override
    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;
    }
}

4.3.4 折扣优惠券接口实现

ZKCouponDiscount.java

java 复制代码
package com.lino.design.impl;

import com.lino.design.ICouponDiscount;
import java.math.BigDecimal;

/**
 * @description: 折扣券
 */
public class ZKCouponDiscount implements ICouponDiscount<Double> {

    /**
     * 折扣计算
     * 1.使用商品价格乘以折扣比例,为最后支付金额
     * 2.保留两位小数
     * 3.最低支付金额1元
     *
     * @param couponInfo 优惠券信息泛型
     * @param skuPrice   商品价格
     * @return 优惠后价格
     */
    @Override
    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;
    }
}

4.3.5 n元购优惠券接口实现

NYGCouponDiscount.java

java 复制代码
package com.lino.design.impl;

import com.lino.design.ICouponDiscount;
import java.math.BigDecimal;
import java.util.Map;

/**
 * @description: n元购
 */
public class NYGCouponDiscount implements ICouponDiscount<Double> {

    /**
     * n元购
     * 1.无论原价多少钱都固定金额购买
     *
     * @param couponInfo 优惠券信息泛型
     * @param skuPrice   商品价格
     * @return 优惠后价格
     */
    @Override
    public BigDecimal discountAmount(Double couponInfo, BigDecimal skuPrice) {
        return new BigDecimal(couponInfo);
    }
}

4.3.6 策略控制类

Context.java

java 复制代码
package com.lino.design;

import java.math.BigDecimal;

/**
 * @description: 策略控制类
 */
public class Context<T> {

    private ICouponDiscount<T> couponDiscount;

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

    /**
     * 计算优惠券折扣
     *
     * @param couponInfo 优惠券信息泛型
     * @param skuPrice   商品价格
     * @return 优惠券折扣后的价格
     */
    public BigDecimal discountAmount(T couponInfo, BigDecimal skuPrice) {
        return couponDiscount.discountAmount(couponInfo, skuPrice);
    }
}
  • 策略模式的控制类主要是外部可以传递不同的策略实现,再通过统一的方法执行优惠策略计算。
  • 另外这里也可以包装出 map 结构,让外部只需要对应的泛型类型即可使用相应的服务。

4.4 单元测试

4.4.1 直减券测试

ApiTest.java

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

测试结果

java 复制代码
17:16:00.390 [main] INFO  com.lino.design.test.ApiTest - 测试结果:直减优惠后金额:90

4.4.2 满减券测试

ApiTest.java

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

测试结果

java 复制代码
17:16:35.300 [main] INFO  com.lino.design.test.ApiTest - 测试结果:满减优惠后金额:90

4.4.3 折扣券测试

ApiTest.java

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

测试结果

java 复制代码
17:17:06.907 [main] INFO  com.lino.design.test.ApiTest - 测试结果:折扣9折后金额:90.00

4.4.4 n元购测试

ApiTest.java

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

测试结果

java 复制代码
17:17:35.616 [main] INFO  com.lino.design.test.ApiTest - 测试结果:n元购优惠后金额:90

💡 以上四个测试分别验证了不同类型优惠券的优惠策略,测试结果是满足我们的预期。

这里四种优惠券最终都是再原价 100 元上折扣 10 元,最终支付 90 元。

五、总结:策略模式

  • 策略模式案例相对来说并不复杂,主要的逻辑都是体现在关于不同类型优惠券的计算折扣策略上。
    • 结构相对来说也比较简单,在实际的开发中这样的设计模式也是非常常用的。
    • 另外策略模式与命令模式、适配器模式结构相似,但是思路是有差异的。
  • 通过策略模式的使用可以把我们方法中的 if 语句优化掉,大量的 if 语句使用会让代码难为扩展,也不好维护,同时在后期遇到各种问题也很难维护。
  • 在使用策略模式可以很好的满足隔离性和扩展性,对于不断新增的需求也非常方便承接。
  • 策略模式适配器模式组合模式 等,在一些结构上是比较相似的。但是每一个模式都有自己的逻辑特点,在使用的过程中最佳的方式是经过较多的实践来吸取经验,为后续的研发设计提供更好的技术输出。
相关推荐
陈大爷(有低保)6 分钟前
UDP Socket聊天室(Java)
java·网络协议·udp
nakyoooooo9 分钟前
【设计模式】工厂模式、单例模式、观察者模式、发布订阅模式
观察者模式·单例模式·设计模式
kinlon.liu19 分钟前
零信任安全架构--持续验证
java·安全·安全架构·mfa·持续验证
王哲晓40 分钟前
Linux通过yum安装Docker
java·linux·docker
java6666688881 小时前
如何在Java中实现高效的对象映射:Dozer与MapStruct的比较与优化
java·开发语言
Violet永存1 小时前
源码分析:LinkedList
java·开发语言
执键行天涯1 小时前
【经验帖】JAVA中同方法,两次调用Mybatis,一次更新,一次查询,同一事务,第一次修改对第二次的可见性如何
java·数据库·mybatis
Jarlen1 小时前
将本地离线Jar包上传到Maven远程私库上,供项目编译使用
java·maven·jar
蓑 羽1 小时前
力扣438 找到字符串中所有字母异位词 Java版本
java·算法·leetcode
Reese_Cool1 小时前
【C语言二级考试】循环结构设计
android·java·c语言·开发语言