当前位置: 首页 > article >正文

设计模式在项目中有用过吗?怎么用的?

有朋友问这个项目中有没有用到什么设计模式,刚好,我们这个项目中需要计算优惠金额,所以,想到了策略模式。

关于优惠券系统的设计与实现,已经完结;

ff48a7511148704677e006a79063aa2c.png

可能大家更多是关注做完项目,如何应对面试,我一共整理了10多个面试题,后期如有朋友反馈,会继续添加。

bcc527084c3ad018cf0156fe30b8a14a.png

需要的话直接扫描下方二维码,可以免费获取设计文档和源码。

d028572b86af27a7099def929b2e9e40.png

业务场景

我们回顾一下优惠券使用类型有:现金抵扣、满减以及折扣。

假设当前支付总金额是200.99元。此时,用到了一张优惠券,优惠券使用类型是折扣,优惠券面值是1(表示打1者)。前面的实现中,计算优惠金额是在支付代码中实现的。

实际上来说,肯定是需要优惠券功能模块里提供一个计算工具类,支付模块直接使用工具类就只能知道最终支付金额了。

关于业务,我们就说这么多,我们呢来看策略模式。

策略模式

策略模式(strategy pattern)的原始定义是:

定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。

经典话语:条条大路通北京

我们可以这么理解:每一条路都是一个策略

下面我们来看一下java中策略模式该如何实现。

// 策略接口
interface Strategy {
    void execute();
}
 
// 具体策略A
class ConcreteStrategyA implements Strategy {
    public void execute() {
        System.out.println("策略 A 执行。");
    }
}
 
// 具体策略B
class ConcreteStrategyB implements Strategy {
    public void execute() {
        System.out.println("策略 B 执行。");
    }
}
 
// 环境类
class Context {
    private Strategy strategy;
 
    public Context(Strategy strategy) {
        this.strategy = strategy;
    }
 
    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }
 
    public void execute() {
        strategy.execute();
    }
}
 
// 客户端
public class StrategyPatternExample {
    public static void main(String[] args) {
        // 使用策略A
        Context context = new Context(new ConcreteStrategyA());
        context.execute();
 
        // 切换到策略B
        context.setStrategy(new ConcreteStrategyB());
        context.execute();
    }
}

策略模式的主要角色如下:

  • 抽象策略(Strategy)类 :这是一个抽象角色,通常由一个接口或抽象类实现。此角色给出所有的具体策略类所需的接口。

  • 具体策略(Concrete Strategy)类 :实现了抽象策略定义的接口,提供具体的算法实现或行为。

  • 环境或上下文(Context)类 :是使用算法的角色, 持有一个策略类的引用,最终给客户端调用。

我们在来看我们前面说的优惠金额计算的实现。

业务场景实现

先定义一个优惠券使用类型枚举类。

@Getter
public enum CouponUseTypeEnum {
    /**
     * 现金
     */
    CASH(1, "现金"),
    /**
     * 折扣
     */
    DISCOUNT(2, "折扣"),
    /**
     * 满减
     */
    FULL_REDUCE(3, "满减");
    private final int type;
    private final String desc;

    private static final Map<Integer, CouponUseTypeEnum> couponUseTypeEnumMap = new HashMap<>();

    static {
        for (CouponUseTypeEnum value : CouponUseTypeEnum.values()) {
            couponUseTypeEnumMap.put(value.getType(), value);
        }
    }

    CouponUseTypeEnum(int type, String desc) {
        this.type = type;
        this.desc = desc;
    }

    public static String getDescByType(int type) {
        return couponUseTypeEnumMap.get(type).desc;
    }
}

这个枚举没什么好说的,简单的不能再简单了。

定义计算接口
public interface CalculateDiscountAmountService {
    BigDecimal calculateDiscountAmount(BigDecimal totalAmount, BigDecimal couponFaceAmount);
}

两个参数:

  • 总金额

  • 优惠券面值

实现类

折扣类型的实现

@Service
public class CalculateDiscountAmount4DiscountServiceImpl implements CalculateDiscountAmountService {

    @Override
    public BigDecimal calculateDiscountAmount(BigDecimal totalAmount, BigDecimal couponFaceAmount) {
        //比例值 couponFaceAmount=1 表示一折,乘以10后的比例值为10% 0.1
        BigDecimal proportion = couponFaceAmount.divide(new BigDecimal("10"), 2, RoundingMode.HALF_UP);
        //优惠金额=总金额*比例值/100
        BigDecimal discountAmount = totalAmount.multiply(proportion).setScale(3, RoundingMode.HALF_UP);
        //实际支付金额=总金额-优惠金额
        return totalAmount.subtract(discountAmount).setScale(2, RoundingMode.HALF_UP);
    }
}

另外两个也类似:

@Service
public class CalculateDiscountAmount4FullReduceServiceImpl implements CalculateDiscountAmountService {
    @Override
    public BigDecimal calculateDiscountAmount(BigDecimal totalAmount, BigDecimal couponFaceAmount) {
        BigDecimal discountAmount= 计算优惠金额
        return discountAmount;
    }
}
@Service
public class CalculateDiscountAmount4DeductionServiceImpl implements CalculateDiscountAmountService {
    @Override
    public BigDecimal calculateDiscountAmount(BigDecimal totalAmount, BigDecimal couponFaceAmount) {
        BigDecimal discountAmount= 计算优惠金额
        return discountAmount;
    }
}

这样就实现了优惠金额的实际计算。

但是,对于调用者来说,只有一个使用类型,所以,我们需要把优惠券使用类型和实现类关联起来。

public class CalculateDiscountAmountStrategy {
    public final static Map<Integer, Class<? extends CalculateDiscountAmountService>> DISCOUNT_AMOUNT_MAP = new HashMap<>();

    static {
        DISCOUNT_AMOUNT_MAP.put(CouponUseTypeEnum.CASH.getType(), CalculateDiscountAmount4DeductionServiceImpl.class);
        DISCOUNT_AMOUNT_MAP.put(CouponUseTypeEnum.DISCOUNT.getType(), CalculateDiscountAmount4DiscountServiceImpl.class);
        DISCOUNT_AMOUNT_MAP.put(CouponUseTypeEnum.FULL_REDUCE.getType(), CalculateDiscountAmount4FullReduceServiceImpl.class);
    }
}

现在已经关联起来了,我们该如何用呢?

使用
@GetMapping("/test2")
    public String test2() {
     
        BigDecimal totalAmount= new BigDecimal("200.99");//200.99-20.099= 180.891
        BigDecimal faceAmount= new BigDecimal("1");
     
        int type=2;
     
        Class<? extends CalculateDiscountAmountService> serviceClass = CalculateDiscountAmountStrategy.DISCOUNT_AMOUNT_MAP.get(type);
        CalculateDiscountAmountService service = ApplicationContextFactory.getBean(serviceClass);
       
        BigDecimal bigDecimal = service.calculateDiscountAmount(totalAmount, faceAmount);
     
        System.out.println(bigDecimal);
        return "success";
    }

上面代码中:

  • 定义了totalAmount为200.99

  • 优惠券面值faceAmount=1 (表示打1折)

  • 优惠券使用类型是2(对应枚举中就是折扣类型)

  • 最后计算出优惠后总金额180.89=200.99-20.099 (因为人民币只到分,所以保留两位小数)。

总结

看得出来,如果优惠券使用类型增加,我们只需要在枚举中加一个类型,再创建一个具体实现类,最后在维护好优惠券使用类和计算实现类中再加入一个关系即可。

对于调用方,其实并不知道我们加了一个使用类型。同时,如果有多个地方需要计算优惠金额,我们也可以复用代码。

上面说的是好处,其实策略模式也是有缺点:导致创建很多类

另外,我们这个案例中,其实除了策略模式以外, 还用到了工厂模式。只是这个对象的生产是Spring帮我们做的。

科大讯飞面经,蛮简单的

优惠券项目的设计与实现,完结!

慢SQL优化,只会加索引?

背八股文 新技巧!都说很不错


http://www.kler.cn/a/406392.html

相关文章:

  • 创客匠人老蒋:个人IP如何获取有效流量?
  • tensorflow案例7--数据增强与测试集, 训练集, 验证集的构建
  • 如何给 Apache 新站点目录配置 SELinux ?
  • 【CSP CCF记录】201903-1第16次认证 小中大
  • sql注入报错分享(mssql+mysql)
  • 利用D3.js实现数据可视化的简单示例
  • 数据结构 (3)线性表的概念及其抽象数据类型定义
  • go项目中比较好的实践方案
  • 【qt版本概述】
  • js前端加密方案库Crypto-js之aes的使用
  • 速通前端篇 —— CSS
  • c++中操作数据库的常用函数
  • 前端vue调试样式方法
  • 前端 px、rpx、em、rem、vh、vw计量单位的区别
  • 【D3.js in Action 3 精译_040】4.4 D3 弧形图的绘制方法
  • 准备阶段 Statistics界面性能分析
  • uniapp H5上传图片前压缩
  • vue的class绑定,后边的类会覆盖前边类样式吗
  • 3-22 ElementPlus:表单
  • vue3 在哪些方便做了性能提升?
  • 【不墨迹系列】快速入门 XML 语言
  • SpringCloud OpenFeign用户转发在请求头中添加用户信息 微服务内部调用
  • STL-stack栈:P1981 [NOIP2013 普及组] 表达式求值
  • Cannal实现MySQL主从同步环境搭建
  • 量子神经网络
  • Java 创建不可变集合