从优惠打折活动说起

电商平台为了增加销量经常搞一些活动,比如 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 设计模式实战系列—策略模式的更多相关文章

  1. Java设计模式之十一 ---- 策略模式和模板方法模式

    前言 在上一篇中我们学习了行为型模式的访问者模式(Visitor Pattern)和中介者模式(Mediator Pattern).本篇则来学习下行为型模式的两个模式,策略模式(Strategy Pa ...

  2. 《JAVA设计模式》之策略模式(Strategy)

    在阎宏博士的<JAVA与模式>一书中开头是这样描述策略(Strategy)模式的: 策略模式属于对象的行为模式.其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它 ...

  3. Java设计模式6:策略模式

    策略模式 策略模式的用意是针对一组算法,将每一个算法封装到具有共同接口的独立类中,从而使得它们可以相互替换.策略模式使得算法可以在不影响到客户端的情况下发生变化. 策略模式的结构 策略模式是对算法的包 ...

  4. JAVA设计模式之【策略模式】

    策略模式 定义一些独立的类来封装不同的算法 类似于common方法或者引用类 角色 环境类Context 抽象策略Strategy 具体策略ConcreteStrategy 重构伴随着设计模式 重构类 ...

  5. Java设计模式学习记录-策略模式

    策略模式 策略模式的定义是:定义了一系列的算法,把它们一个个的封装起来,并且使它们可相互替换,让算法可以独立于使用它的客户而变化. 设计原则是:把一个类中经常改变或者将来可能会经常改变的部分提取出来作 ...

  6. java设计模式-----8、策略模式

    Strategy模式也叫策略模式是行为模式之一,它对一系列的算法加以封装,为所有算法定义一个抽象的算法接口,并通过继承该抽象算法接口对所有的算法加以封装和实现,具体的算法选择交由客户端决定(策略).S ...

  7. 《Head First 设计模式》[01] 策略模式

    <Head First 设计模式>(点击查看详情) 1.写在前面的话 之前在列书单的时候,看网友对于设计模式的推荐里说,设计模式的书类别都大同小异,于是自己就选择了Head First系列 ...

  8. java设计模式4--建造者模式(Builder)

    本文地址:http://www.cnblogs.com/archimedes/p/java-builder-pattern.html,转载请注明源地址. 建造者模式 将一个复杂对象的构建与它的表示分离 ...

  9. 【转】设计模式 ( 十八 ) 策略模式Strategy(对象行为型)

    设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成 ...

  10. 设计模式 ( 十八 ) 策略模式Strategy(对象行为型)

    设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也经常遇到类似的情况,实现某一个功能有多种算法或者策略,我们能够依据环境或者条件的不同选择不同的算法或者策略来完毕 ...

随机推荐

  1. pysimplegui之第一个程序,包括回调函数,事件,阻塞等待内容

    自定义窗口 API 调用(您的第一个窗口) 总结一下:我遇到的坑, 比如拿输入框的内容的时候可以直接通过value[key] 几种窗口模式就是什么时候用timeout这个参数 关闭窗口可以的一边形式 ...

  2. 用 Go 剑指 Offer 12. 矩阵中的路径

    给定一个 m x n 二维字符网格 board 和一个字符串单词 word .如果 word 存在于网格中,返回 true :否则,返回 false . 单词必须按照字母顺序,通过相邻的单元格内的字母 ...

  3. Python property、setter、deleter

    面向对象封装特点之一就是通过实现好的方法来访问,限制对数据的不合理访问,把对象状态私有化,仅供类的内部进行操作 下方示例,Test方法的number属性类实例的时候传递1,number是一个公开属性, ...

  4. Solon2 常用注解之 @Component 与 @ProxyComponent 的区别

    在 Solon 提倡"克制"的原则下,托管组件分为: 普通组件: 主要由内核提供的:@Configuration.@Component.@Controller.@Remoting ...

  5. C# 从0到实战 基本类型

    C#语言的基本类型 与大多数编程语言一样,C#也有自己的基本类型,也称为内置类型.下面的表格就简单阐述了这些类型. C# 类型关键字 .NET 类型 bool System.Boolean byte ...

  6. Cesium加载ArcGIS Server4490且orgin -400 400的切片服务

    Cesium在使用加载Cesium.ArcGisMapServerImageryProvider加载切片服务时,默认只支持wgs84的4326坐标系,不支持CGCS2000的4490坐标系. 如果是A ...

  7. 学习笔记——树形dp

    树形 dp 介绍 概念 树形 dp,顾名思义,就是在树上做 dp,将 dp 的思想建立在树状结构之上. 常见的树形 dp 有两种转移方向: 从叶节点向根节点转移,这种也是树形 dp 中较为常见的一种. ...

  8. 2023-03-12:mp3音频解码为pcm,代码用go语言编写,调用moonfdd/ffmpeg-go库。

    2023-03-12:mp3音频解码为pcm,代码用go语言编写,调用moonfdd/ffmpeg-go库. 答案2023-03-12: 用github/moonfdd/ffmpeg-go库. 命令如 ...

  9. 2020-12-26:mysql中,表person有字段id、name、age、sex,id是主键,name是普通索引,age和sex没有索引。select * from person where id=1 and name='james' and age=1 and sex=0。请问这条语句有几次回表?

    2020-12-26:mysql中,表person有字段id.name.age.sex,id是主键,name是普通索引,age和sex没有索引.select * from person where i ...

  10. Jenkins - 构建时运行Selenium打不开浏览器解决方法

    Jenkins-构建时运行Selenium打不开浏览器解决方法 前言 为了让Jenkins执行的Web自动化测试任务,能顺利调出浏览器页面,我们需要以命令行的方式启动Jenkins并执行脚本. 注:通 ...