ID
策略( UUID
、DB自增
、DB+Redis
、雪花算法
、Leaf算法
)等。对于优惠券的设计最初可能非常简单,就是一个金额的折扣,也没有现在这么多种类型。
所以如果没有这样场景的经验,往往设计上也是非常简单的。
但随着产品功能的不断迭代,如果程序最初设计的不具备很好的扩展性,那么往后就会越来越混乱。
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
<dependency>
<groupId>com.alibabagroupId>
<artifactId>fastjsonartifactId>
<version>1.2.62version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.5version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jcl-over-slf4jartifactId>
<version>1.7.5version>
dependency>
<dependency>
<groupId>ch.qos.logbackgroupId>
<artifactId>logback-classicartifactId>
<version>1.0.9version>
<exclusions>
<exclusion>
<artifactId>slf4j-apiartifactId>
<groupId>org.slf4jgroupId>
exclusion>
exclusions>
dependency>
dependencies>
design-21.0-1
|——src
|——main
|--java
|--com.lino.design
|-CouponDiscountService.java
|--test
|--com.lino.design.test
|-ApiTest.java
CouponDiscountService.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
语句。ApiTest.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);
}
}
测试结果
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
重构使用策略模式,优化代码结构,增强整体的扩展性。
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
ICouponDiscount
),以及四种优惠券类型的实现方式。ICouponDiscount.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);
}
MJCouponDiscount.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;
}
}
ZJCouponDiscount.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;
}
}
ZKCouponDiscount.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;
}
}
NYGCouponDiscount.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);
}
}
Context.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
结构,让外部只需要对应的泛型类型即可使用相应的服务。ApiTest.java
@Test
public void test_zj() {
Context<Double> context = new Context<>(new ZJCouponDiscount());
BigDecimal discountAmount = context.discountAmount(10D, new BigDecimal(100));
logger.info("测试结果:直减优惠后金额:{}", discountAmount);
}
测试结果
17:16:00.390 [main] INFO com.lino.design.test.ApiTest - 测试结果:直减优惠后金额:90
ApiTest.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);
}
测试结果
17:16:35.300 [main] INFO com.lino.design.test.ApiTest - 测试结果:满减优惠后金额:90
ApiTest.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);
}
测试结果
17:17:06.907 [main] INFO com.lino.design.test.ApiTest - 测试结果:折扣9折后金额:90.00
ApiTest.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);
}
测试结果
17:17:35.616 [main] INFO com.lino.design.test.ApiTest - 测试结果:n元购优惠后金额:90
以上四个测试分别验证了不同类型优惠券的优惠策略,测试结果是满足我们的预期。
这里四种优惠券最终都是再原价100
元上折扣10
元,最终支付90
元。
if
语句优化掉,大量的 if
语句使用会让代码难为扩展,也不好维护,同时在后期遇到各种问题也很难维护。策略模式
、适配器模式
、组合模式
等,在一些结构上是比较相似的。但是每一个模式都有自己的逻辑特点,在使用的过程中最佳的方式是经过较多的实践来吸取经验,为后续的研发设计提供更好的技术输出。