文章目录
- 一、策略模式定义
- 二、策略模式的结构和说明
- 三、策略模式示例
- 四、Java8重构策略模式
- 五、策略模式的优缺点
- 六、策略模式的应用场景
一、策略模式定义
Define a family of algorithms,encapsulate each one,and make them interchangeable.
定义一系列的算法,把它们一个个封装起来,并且使它们可相互替换。
二、策略模式的结构和说明
- Strategy 策略接口,用来约束一些列具体的策略算法。Context使用这个接口来调用具体的策略实现定义的算法。
- ConcreteStratery 策略实现类,实现具体的算法。
- Context 上下文,负责和具体的策略类交互。通常上下文会持有一个真正的策略实现,上下文还可以让具体的策略类来获取上下文的数据,甚至让具体的策略类来回调上下文的方法。
三、策略模式示例
假设在我们的商城系统中,需要实现一个优惠促销功能。其中优惠促销有以下几种规则。
- 新客户:首单立减10元。
- 满减券:满200,减30。
- 折扣券:所有商品打9折。
- … 后续会有更多的优惠规则。
对于这样的一个需求,我们该怎么实现呢?有同学可能会说了,加上几个if else
就好了,接着噼里啪啦一顿操作,代码如下就出来了:
/**
* 购物车
*/
public class ShoppingCart {
// 购物车中的商品
private List<Goods> list;
/**
* 购物车中商品的总金额
*/
private BigDecimal amount;
/**
* 提交订单主流程
* @param discountType 选择的优惠券类型
*/
public void submitOrder(String discountType){
// 计算优惠减免
amount = calculate(amount, discountType);
// 保存订单并付款
// 送货到家
}
/**
* 计算优惠减免后的金额
* @param amount 优惠前金额
* @param discountType 优惠券类型
* @return 优惠减免后的金额
*/
public BigDecimal calculate(BigDecimal amount, String discountType){
if("NEW".equals(discountType)){
// 计算新用户优惠后的金额
amount = amount.subtract(BigDecimal.valueOf(10));
}else if("FULL".equals(discountType)){
// 计算满减优惠后的金额
amount = amount.subtract(BigDecimal.valueOf(30));
}else if("PERCENTAGE".equals(discountType)){
// 计算折扣优惠后的金额
amount = amount.multiply(BigDecimal.valueOf(0.9));
}
return amount;
}
}
这种写法是很简单,但仔细想想,这样写可能会有如下方面的问题。
- 如果优惠规则多了,类和方法会变得非常庞大,难以维护。而且维护和扩展都需要修改已有的代码,这样违反了开闭原则。
- 在不同时期,同样条件下的优惠力度可能也不一样。如在平常时期,新注册用户首单立减10元,在周年庆期间,新注册用户首单立减15元,过了周年庆,又变回首单立减10元。通过上边代码来实现这样的效果,需要频繁的修改
if else
中的代码,这样的切换方式会非常麻烦。
那么,不卖关子,我们直接来看看,用策略模式是如何解决上边的问题的。
Discount (优惠折扣计算接口),对应模式中的 Strategy (策略接口)
/**
* 优惠折扣计算接口
*/
public interface Discount {
/**
* 计算优惠后的金额
* @param amount
* @return 优惠后的金额
*/
BigDecimal calculate(BigDecimal amount);
}
NewCustomerDiscount(新客户优惠计算类)、FullReductionDiscount(满减券优惠计算类),对应模式中的 ConcreteStratery(策略具体实现类)
/**
* 新客户优惠,首单立减10元
*/
public class NewCustomerDiscount implements Discount{
@Override
public BigDecimal calculate(BigDecimal amount) {
if(BigDecimal.valueOf(10).compareTo(amount) >= 0){
return BigDecimal.ZERO;
}
return amount.subtract(BigDecimal.valueOf(10));
}
}
/**
* 满减优惠,满200减30
*/
public class FullReductionDiscount implements Discount{
@Override
public BigDecimal calculate(BigDecimal amount) {
if(amount.compareTo(BigDecimal.valueOf(200)) >= 0){
amount = amount.subtract(BigDecimal.valueOf(30));
}
return amount;
}
}
ShoppingCart (购物车类),对应策略模式中的 Context(上下文)
/**
* 购物车
*/
public class ShoppingCart {
/**
* 优惠折扣计算类
* 这里先演示只能选择一种优惠类型情况,
* 如果想要支持多种优惠同时使用,可以定义为List<Discount>
*/
private Discount discount;
/**
* 购物车中商品的总金额
*/
private BigDecimal amount;
/**
* 提交订单主流程
*/
public void submitOrder(){
// 计算优惠减免
if(discount != null){
System.out.println("优惠前总金额:"+amount);
amount = discount.calculate(amount);
System.out.println("优惠后总金额:"+amount);
}
// 保存订单并付款
// 送货到家
}
/**
* 设置具体的优惠策略
* @param discount 优惠策略对象
*/
public void setDiscount(Discount discount) {
this.discount = discount;
}
/**
* 模拟输入购物车中物品金额
* @param amount
*/
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
}
以上,我们就通过策略模式,实现了一个带有优惠券的购物车功能,我们来模拟一下提交购买。
public class Client {
public static void main(String[] args) {
// 创建购物车实例
ShoppingCart shoppingCart = new ShoppingCart();
// 模拟往购物车中加入总金额为200的商品
shoppingCart.setAmount(BigDecimal.valueOf(200));
// 这里创建具体策略实现类我们简单示意一下,一般开发中是需要前端传过来类型,再通过简单工厂等方式获取具体对象
// 我们要使用的优惠券种类是新客户首单购买优惠券
Discount discount = new NewCustomerDiscount();
// 设置优惠策略
shoppingCart.setDiscount(discount);
// 提交订单
shoppingCart.submitOrder();
}
}
执行main方法后,得到下边的输出:
优惠前总金额:200
优惠后总金额:190
要是想要使用满减优惠,只需要选择满减的优惠策略实现就可以了
Discount discount = new NewCustomerDiscount();
Discount discount = new FullReductionDiscount();
新客户首单优惠和满减优惠已经实现了,那么,如果我们想要扩展优惠策略,例如需要加上折扣券优惠,我们怎么来操作呢?来,看好了哈。
新增 PercentageDiscount (折扣券优惠类)
/**
* 折扣券优惠,所有商品打9折
*/
public class PercentageDiscount implements Discount{
@Override
public BigDecimal calculate(BigDecimal amount) {
return amount.multiply(BigDecimal.valueOf(0.9));
}
}
好了,就是这么简单。我们再来模拟一下选用折扣券优惠类型提交购买。
public class Client {
public static void main(String[] args) {
// 创建购物车实例
ShoppingCart shoppingCart = new ShoppingCart();
// 模拟往购物车中加入了总金额为200的商品
shoppingCart.setAmount(BigDecimal.valueOf(200));
// 这里创建具体策略实现类我们简单示意一下,一般开发中是需要前端传过来类型,再通过简单工厂等方式获取具体对象
// 我们要使用的优惠券种类是折扣券优惠券
Discount discount = new PercentageDiscount();
// 设置优惠策略
shoppingCart.setDiscount(discount);
// 提交订单
shoppingCart.submitOrder();
}
}
执行main方法后,得到下边的输出:
优惠前总金额:200
优惠后总金额:180.0
这样一来,既避免了类或方法变得庞大,又实现了策略的动态切换。维护和扩展起来是不是也方便多了呀。
上边,我们已经演示了扩展新优惠策略的实现方式。那么,如果我们又需要扩展一种较特殊的优惠策略,我们不在金额上做减免,而是给客户的账号里累积积分,客户可以通过积分换购商品,这样该怎么实现呢?来,继续看着哈。
新增 IntegralDiscount (购买商品累积积分类)
/**
* 购买商品,可以累积同金额的积分,客户可以通过积分换购商品
*/
public class IntegralDiscount implements Discount{
public IntegralDiscount(String account) {
this.account = account;
}
/**
* 客户账号
*/
private String account;
@Override
public BigDecimal calculate(BigDecimal amount) {
System.out.println(String.format("给账号[%s]中加[%d]积分",account,amount.intValue()));
return amount;
}
}
再来模拟一下选用累积积分优惠类型提交购买。
public class Client {
public static void main(String[] args) {
// 创建购物车实例
ShoppingCart shoppingCart = new ShoppingCart();
// 模拟往购物车中加入了总金额为200的商品
shoppingCart.setAmount(BigDecimal.valueOf(200));
// 这里创建具体策略实现类我们简单示意一下,一般开发中是需要前端传过来类型,再通过简单工厂等方式获取具体对象
// 我们要使用的优惠券种类是累积积分类型
Discount discount = new IntegralDiscount("2020000001");
// 设置优惠策略
shoppingCart.setDiscount(discount);
// 提交订单
shoppingCart.submitOrder();
}
}
执行main方法后,得到下边的输出:
优惠前总金额:200
给账号[2020000001]中加[200]积分
优惠后总金额:200
只有这个优惠策略中需要用到客户账号,那就单独在这个策略实现类中添加这个字段好啦。
四、Java8重构策略模式
看到标题,我们很多小伙伴,应该已经想到了什么。对!就是我们Java8新特性之函数式接口和Lambda表达式。好了,忘掉我们上边那么多代码,重新来过。再来定义 ShoppingCart (购物车类)
/**
* 购物车
*/
public class ShoppingCart {
/**
* 优惠折扣计算类,输入一个BigDecimal类型值,返回一个BigDecimal类型值
* 这里先演示只能选择一种优惠类型情况
* 如果想要支持多种优惠同时使用,可以定义为LIst<Function<BigDecimal,BigDecimal>>
*/
private Function<BigDecimal,BigDecimal> discount;
/**
* 购物车中商品的总金额
*/
private BigDecimal amount;
/**
* 提交订单主流程
*/
public void submitOrder(){
// 计算优惠减免
if(discount != null){
System.out.println("优惠前总金额:"+amount);
amount = discount.apply(amount);
System.out.println("优惠后总金额:"+amount);
}
// 保存订单并付款
// 送货到家
}
/**
* 设置具体的优惠策略
* @param discount 优惠策略对象
*/
public void setDiscount(Function<BigDecimal,BigDecimal> discount) {
this.discount = discount;
}
/**
* 模拟输入购物车中物品金额
* @param amount
*/
public void setAmount(BigDecimal amount) {
this.amount = amount;
}
}
好啦,让我们在来模拟一下选用新客户首单立减10元优惠类型提交购买。
public class Client {
public static void main(String[] args) {
// 创建购物车实例
ShoppingCart shoppingCart = new ShoppingCart();
// 模拟往购物车中加入了总金额为200的商品
shoppingCart.setAmount(BigDecimal.valueOf(200));
// 我们要使用的优惠券种类是新客户首单立减10元
Function<BigDecimal,BigDecimal> discount
= amount -> BigDecimal.valueOf(10).compareTo(amount) >= 0?
BigDecimal.ZERO:amount.subtract(BigDecimal.valueOf(10));
// 设置优惠策略
shoppingCart.setDiscount(discount);
// 提交订单
shoppingCart.submitOrder();
}
}
执行main方法后,得到下边的输出:
优惠前总金额:200
优惠后总金额:190
从上边代码可以看出,使用Java8新特性实现策略模式,我们就不需要定义具体的策略实现类,通过Lambda表达式避免了采用策略模式时僵化的模板代码。
五、策略模式的优缺点
优点:
- 可以消除大量的条件语句。
- 扩展性好,增加一个策略只需实现接口即可。
- 符合开-闭原则。
- 算法可以自由切换。
缺点:
- 策略类需要对外暴露,客户端必须知道所有的策略类,并自行决定使用哪一个策略。
- 增加了对象数目。
六、策略模式的应用场景
- 多个类只在算法或行为上稍有不同的场景。
- 算法需要自由切换的场景。
- 通过条件语句,在多分支中选取一种。