首页 > 编程语言 >Java 设计模式实战系列—策略模式

Java 设计模式实战系列—策略模式

时间:2023-06-25 09:22:32浏览次数:49  
标签:实战 Java BigDecimal 代码 amount 设计模式 type public 策略

从优惠打折活动说起

电商平台为了增加销量经常搞一些活动,比如 618、双十一,还有一些节假日活动,根据销量的变化又经常更新不同的活动。最开始为了增加销量,全场都六折:

// 打六折
public BigDecimal sixDiscount(BigDecimal amount) {
    BigDecimal discount = BigDecimal.valueOf(0.6);
    return amount.multiply(discount);
}

促销了几天之后,发现销量上去了,但是利润又减少了,又改成打八折了:

// 打八折
public BigDecimal eightDiscount(BigDecimal amount) {
    BigDecimal discount = BigDecimal.valueOf(0.8);
    return amount.multiply(discount);
}

再过几个月,活动每周都可能会改变,类似这周六折,下周八折,每次修改活动,就要改代码,就比较繁琐。后面就根据参数来选择不同的活动:

// 根据不同的type,选择不同的打折方案
public BigDecimal simpleDiscount(int type,BigDecimal amount) {
    if (type == 1) {
        amount = amount.multiply(BigDecimal.valueOf(0.6));
    } else if (type == 2) {
        amount = amount.multiply(BigDecimal.valueOf(0.8));
    }
    return amount;
}

后面不增加活动方案的前提下,改变方案只需要前端选择对应的方案即可。但如果每次新增代码,还是需要更新代码,还需要更多繁琐的 if-else 判断:

// 根据不同的type,选择不同的打折方案
public BigDecimal discount(int type,BigDecimal amount) {
    if (type == 1) {
        amount = amount.multiply(BigDecimal.valueOf(0.6));
    } else if (type == 2) {
        amount = amount.multiply(BigDecimal.valueOf(0.8));
    } else if (type == 3) {
        // 满100减20,满200减50
    } else if (type == 4) {
        // 满500减70
    } else if (type == 5) {
        // 满1000减两百
    }
    // 更多的方案 .....
    return amount;
}

上面的方法只是一些简单的打折方案,实际上的打折方案更复杂,一个打折方案代码至少要十多行,而上面十多个方案,代码显得非常的臃肿。有如下几个缺点:

  • 每次增加方案,只能在方面里面添加,导致方案的代码越来越多,一个方法有几百行代码。耦合度非常高。
  • 臃肿的代码每次都要从头看到尾,可读性比较差。
  • 使用繁琐的 if-else 或者 switch 分支判断,可读性比较差。

解决上面的几个问题一个比较好的方案就是使用策略模式,策略模式可以避免繁琐的 if-else 分支判断,同时降低代码的耦合度,增强代码的可读性。

策略模式的定义和实现

定义一系列的算法,把每个算法封装起来, 并且使它们可相互替换。根据调用者传参来调用具体的算法。将策略的定义、创建和使用三个部分解耦。下面就上面的打折方案的方法使用策略模式进行改造。

策略的定义

策略的定义包含一个策略接口和一组实现这个接口的策略类,所有的策略类都实现相同的接口。

接口和实现类如下:

// 创建一个接口
public interface Strategy {
    BigDecimal discount(BigDecimal amount);
}

// 打六折
public class SixDiscountStrategy implements Strategy{
    @Override
    public BigDecimal discount(BigDecimal amount) {
        return amount = amount.multiply(BigDecimal.valueOf(0.6));
    }
}

// 打八折
public class EightDiscountStrategy  implements Strategy{
    @Override
    public BigDecimal discount(BigDecimal amount) {
        return amount = amount.multiply(BigDecimal.valueOf(0.8));
    }
}

// 满100减20,满200减50
public class FirstDiscountStrategy implements Strategy{
    @Override
    public BigDecimal discount(BigDecimal amount) {
        // 满100减20,满200减50
        // 省略具体算法....
        return amount;
    }
}

将上面一个几百行的方法,拆分成一个一个小的类。类的数量变多了,但是代码也更加简洁了。

策略创建和使用

策略模式包含一组策略,一般通过类型 type 来选择创建哪个策略类。将创建策略类的代码封装成一个工具类,通过 type 直接调用:

// 策略工具类
public class Context {
  public static Strategy operation(int type) {
      if (type == 1) {
          return new SixDiscountStrategy();
      } else if (type == 2) {
          return new EightDiscountStrategy();
      } else if (type == 3) {
          return new FirstDiscountStrategy();
      }
      throw new IllegalArgumentException("not fond strategy");
  }
}

以上根据不同的 type 调用不同的策略类,在执行对应的方法。调用策略方法就简单多了,为了方便测试,直接使用 mian 方法调用:

public static void main(String[] args) {
    int type = 1;
    Strategy strategy = Context.operation(type);
    BigDecimal sixDiscount = strategy.discount(BigDecimal.valueOf(100));
    System.out.println("六折优惠价格:" + sixDiscount);
}

可能细心的同学发现,还是有很多 if-else 的代码,每次新增一个活动,还需要在 Context 多写一个判断。如果策略类是无状态的,可以被共享,那就不需要每次调用都创建一个新的策略对象,实现将创建好的对象缓存到 map 集合中,调用的时候直接,同时也不需要写 if-else 判断条件,优化代码如下:

private static Map<Integer,Strategy> map = new HashMap<>();

static {
    map.put(1,new SixDiscountStrategy());
    map.put(2,new EightDiscountStrategy());
    map.put(3,new FirstDiscountStrategy());
}

public static Strategy operation(int type) {
    Strategy strategy = map.get(type);
    if (strategy == null) {
        throw new IllegalArgumentException("not fond strategy");
    }
    return strategy;
}

public static void main(String[] args) {
    // 六折优惠
    int type = 1;
    Strategy strategy = Context.operation(type);
    BigDecimal sixDiscount = strategy.discount(BigDecimal.valueOf(100));
    System.out.println("六折优惠价格:" + sixDiscount);
}

重构之后的代码就没有 if-else 分支语句了,主要是利用了策略模式和 Map 集合,通过 type 直接获取 Map 集合上对应的策略类,从而很好的规避了 if-else 判断。通过查表法代替 if-else 判断。

将代码重构之后,代码也不会显得臃肿和复杂,有如下几个好处:

  • 每个策略都有自己的对应的类,查看策略只需要查看自己对应的类即可,代码的可读性也大大增加。
  • 代码拆分到不同的策略类上,更加的简洁。
  • 后续新增策略类,只需要添加对应的策略接口实现以及 Map 集合,代码整体改动量比较小。

总结

本文先从电商项目的一个复杂多变的优惠券活动上讲起,优惠券的活动复杂多变,经常要搞不同的活动,可能每天也是不同的活动。针对复杂多变的需求,代码数量比较庞大和复杂,代码可读性差,耦合度高。所以就需要使用一个设计模式简化代码,减少代码改动量,增加代码的可读性。策略模式就应允而生。

  • 策略模式包含一个策略接口和一组实现这个接口的策略类,所有的策略类都实现这个接口。策略类可以替换。
  • 策略模式用来解耦策略的定义、创建以及使用,完整的策略模式有这三个部分组成:
    • 策略类的定义包含一个接口和一组实现类,后续增加策略只需要添加对应的实现类即可。
    • 策略的创建由工具类,或者工厂方法来完成。
    • 策略的使用通过传入参数来执行使用哪个策略,编译就确定好的状态只需要将不同的策略存储在 Map 集合中查表调用。如果需要运行时动态确定就需要返回一个实例对象,运行时动态是比较典型的应用场景。
  • 使用策略模式之后,后面代码需要添加新的分支,改动量比较小,代码也比较清晰。
  • 策略模式最主要的作用是解耦策略的定义、创建和使用,控制代码的复杂度,让代码量不多过多,代码逻辑不会过于复杂。对于复杂的代码来讲,策略模式还能再添加新的策略时,能最小化改动代码。
  • 如果代码量比较少,逻辑不太复杂的代码,就不太需要引入策略模式,不然增加系统的复杂性。

标签:实战,Java,BigDecimal,代码,amount,设计模式,type,public,策略
From: https://www.cnblogs.com/jeremylai7/p/17502126.html

相关文章

  • 学习笔记-Java动态代理的简单使用
    代理模式一种设计模式简单地说,在代理模式中存在三个角色用户代理被代理的对象用户调用代理,代理去调用被代理的对象以此来实现功能的增强动态代理在java中有两种实现方法JDK中的Proxy类CGLIBJDK中的Proxy类步骤实现InvocationHandler接口......
  • 强化学习从基础到进阶-案例与实践[4.1]:深度Q网络-DQN项目实战CartPole-v0
    强化学习从基础到进阶-案例与实践[4.1]:深度Q网络-DQN项目实战CartPole-v01、定义算法相比于Qlearning,DQN本质上是为了适应更为复杂的环境,并且经过不断的改良迭代,到了NatureDQN(即VolodymyrMnih发表的Nature论文)这里才算是基本完善。DQN主要改动的点有三个:使用深度神经网络替......
  • 深入理解 Java 中的 ThreadLocal
    1.什么是ThreadLocal在Java多线程编程中,我们经常会遇到共享变量的并发访问问题。为了解决这个问题,Java提供了ThreadLocal类,它允许我们在每个线程中存储和访问线程局部变量,而不会影响其他线程的数据。2.使用ThreadLocal使用ThreadLocal很简单,我们只需要创建一个Thre......
  • [java] 利用反射,将对象A中与对象B中字段名相同的属性值赋予对象B
    前言:最近开发遇到了这样一个需求,前端提交的表单对应类是origin,但后端数据库表对应类是target,两者中有重合字段,origin类中有待处理字段(例如String[]ids),我想到的解决方案是将origin对象中与target对象的同名字段值赋予target,再将待处理字段拆分后赋予target进行存储。首先想到的就......
  • TensorFlow10.4 卷积神经网络-ResNet与DenseNet及ResNet实战
    1ResNet我们是实验发现在我们堆叠更多的网络结构的时候,我们并不能又一个很好的结果,就是它网络层次变多了之后他会产生一个多层的loss的堆叠,使得梯度爆炸,或者梯度弥散。然后我们想了一个办法,就是我们比如说设置了一个30层的神经网络,我们在差也不能比22层的差。就是我们设置了一......
  • java循环
    whilewhile(){}do{}while();for(;;){}增强for循环for(声明语句:表达式){}publicclasszqfor{  publicstaticvoidmain(String[]args){​    int[]a={10,20,30,40,50};    for(intx:a){      System.out.println(x);   ......
  • k8s 深入篇———— pod 实战[六]
    前言pod实战一下,主要是一些例子。正文例子一pod实例的选择:NodeSelector:是一个供用户将Pod与Node进行绑定的字段NodeName:一旦Pod的这个字段被赋值,Kubernetes项目就会被认为这个Pod已经经过了调度,调度的结果就是赋值的节点名字。所以,这个字段一般由调度器负责设......
  • GoLang图形用户界面编程实战(GUI编程)—fyne框架(三)
    fyne中文乱码的两种解决方法方法一(使用embed):embed是Go1.16新特性,以后会有专门的介绍。1、拷贝字体到项目目录项目根目录下新建resource目录,把字体文件拷贝到其中。2、在resource目录下新建resource_export.go文件。resource_export.go代码:packageresourceimport_"emb......
  • [java学习] Spring的分页插件的使用
    概述:SSM集成常会使用到分页,Spring中提供了方便实用的分页插件  第一步:在Mybatis配置文件(SqlMapConfig.xml)中配置插件组件:<?xmlversion="1.0"encoding="UTF-8"?><!DOCTYPEconfigurationPUBLIC"-//mybatis.org//DTDConfig3.0//EN""http://myb......
  • Java 一维数组的使用
    Java一维数组的使用1.一维数组的定义在不知道数组内容可以直接使用下面的定义方法:int[]arr=newint[数组个数];或intarr[]=newint[数组个数];在知道数组内容可以使用如下:int[]arr={data1,data2,data.....};2.数组的传递数组的传递与其他基本类型的值传递不同,......