Java常用设计模式-策略模式

策略模式是一个非常实用的设计模式,指定义了一类算法并将其封装起来,并使得它们之间可以灵活地切换,并且不影响客户端。

1,从一个例子开始

我们常常会在网上买东西,很多购物平台都会有着各种各样的优惠策略供你选择例如满减优惠、返现优惠等等。

假设现在要开发一个商城系统,并要开发优惠策略,需要实现不使用优惠满减优惠返现优惠三个策略。

这一步很简单,我们将优惠进行抽象,创建一个优惠接口Promotion如下:

package fun.swsk33site.strategy.promotion;

import fun.swsk33site.strategy.model.Order;

/**
 * 促销活动抽象接口
 */
public interface Promotion {

   /**
    * 执行促销策略
    *
    * @param order 传入订单进行相应折扣
    */
   void doPromotion(Order order);

}
复制代码

然后创建该接口的实现类NoPromotionFullDiscountPromotionCashBackPromotion分别代表不使用优惠、满减优惠和返现优惠:

package fun.swsk33site.strategy.promotion.impl;

import fun.swsk33site.strategy.model.Order;
import fun.swsk33site.strategy.promotion.Promotion;

/**
 * 不使用优惠
 */
public class NoPromotion implements Promotion {

   @Override
   public void doPromotion(Order order) {
      System.out.println("不使用任何优惠");
   }

}
复制代码
package fun.swsk33site.strategy.promotion.impl;

import fun.swsk33site.strategy.model.Order;
import fun.swsk33site.strategy.promotion.Promotion;

/**
 * 满减促销
 */
public class FullDiscountPromotion implements Promotion {

   @Override
   public void doPromotion(Order order) {
      // 满200减20
      if (order.getPrice() > 200) {
         order.setPrice(order.getPrice() - 20);
         System.out.println("使用了满200减20优惠");
      }
   }

}
复制代码
package fun.swsk33site.strategy.promotion.impl;

import fun.swsk33site.strategy.model.Order;
import fun.swsk33site.strategy.promotion.Promotion;

/**
 * 返现促销
 */
public class CashBackPromotion implements Promotion {

   @Override
   public void doPromotion(Order order) {
      // 满200返现20
      if (order.getPrice() > 200) {
         // 返现20...
         System.out.println("使用了满200返现20到支付宝账户优惠");
      }
   }

}
复制代码

最后创建客户端类执行支付逻辑:

Order order1 = new Order();
order1.setName("xxx");
order1.setPrice(123);
String strategy = "fulldiscount";
Promotion promotion = null;
if (strategy.equals("no")) {
   promotion = new NoPromotion();
} else if (strategy.equals("fulldiscount")) {
   promotion = new FullDiscountPromotion();
} else if (strategy.equals("cashback")) {
   promotion = new CashBackPromotion();
}
promotion.doPromotion(order1);
复制代码

很显然这样写是完全不实用的,如果说促销活动越来越多,那么客户端的代码将会越来越复杂,越来越臃肿。

2,使用策略模式改造

我们可以单独创建一个类,这个类专门用于来根据传入参数选择不同的策略。

首先我们来创建一个枚举类型PromotionStrategy,用于作为选择优惠策略的参数:

package fun.swsk33site.strategy.promotion;

/**
 * 优惠策略枚举
 */
public enum PromotionStrategy {
   /**
    * 不使用优惠
    */
   NO,
   /**
    * 满减优惠
    */
   FULLDISCOUNT,
   /**
    * 返现优惠
    */
   CASHBACK
}
复制代码

然后创建类PromotionContext,用于传入参数后选择相应的优惠策略,这个类就是我们策略模式的核心了:

package fun.swsk33site.strategy.promotion;

import fun.swsk33site.strategy.model.Order;
import fun.swsk33site.strategy.promotion.impl.CashBackPromotion;
import fun.swsk33site.strategy.promotion.impl.FullDiscountPromotion;
import fun.swsk33site.strategy.promotion.impl.NoPromotion;

import java.util.HashMap;
import java.util.Map;

/**
 * 优惠策略选择上下文,用于选择优惠策略
 */
public class PromotionContext {

   // 用一个Map作为容器储存各个优惠策略的类,以枚举为参数取出
   private static Map<PromotionStrategy, Promotion> promotionMap = new HashMap<>();

   // 静态块用于初始化各个优惠策略实例
   static {
      promotionMap.put(PromotionStrategy.NO, new NoPromotion());
      promotionMap.put(PromotionStrategy.FULLDISCOUNT, new FullDiscountPromotion());
      promotionMap.put(PromotionStrategy.CASHBACK, new CashBackPromotion());
   }

   /**
    * 使用优惠
    *
    * @param order    传入要使用优惠的订单
    * @param strategy 传入优惠策略
    */
   public static void usePromotion(Order order, PromotionStrategy strategy) {
      Promotion getPromotion = promotionMap.get(strategy);
      getPromotion.doPromotion(order);
   }

}
复制代码

可见,首先这个类中会把所有的优惠策略都实例化一遍并存入到一个Map中,每个优惠策略对应我们一个策略枚举值作为key,然后利用usePromotion方法,可以接受从客户端传来的优惠策略参数,然后选择相应的优惠策略。

可见,策略模式就是将选择策略的逻辑抽离到一个专门的类中,客户端就可以通过传参的形式更加灵活方便地选择策略。

我们来试一下子:

package fun.swsk33site.strategy;

import fun.swsk33site.strategy.model.Order;
import fun.swsk33site.strategy.promotion.Promotion;
import fun.swsk33site.strategy.promotion.PromotionContext;
import fun.swsk33site.strategy.promotion.PromotionStrategy;
import fun.swsk33site.strategy.promotion.impl.CashBackPromotion;
import fun.swsk33site.strategy.promotion.impl.FullDiscountPromotion;
import fun.swsk33site.strategy.promotion.impl.NoPromotion;

public class Client {

   /**
    * 客户端进行支付
    *
    * @param order    待支付账单
    * @param strategy 要使用的优惠策略
    */
   private static void doPayment(Order order, PromotionStrategy strategy) {
      System.out.println("账单:" + order.getName() + "准备支付");
      System.out.println("准备使用优惠");
      PromotionContext.usePromotion(order, strategy);
      System.out.println("支付完成:" + order.getPrice() + "元");
   }

   public static void main(String[] args) {
      // 模拟买东西
      Order order = new Order();
      order.setName("守望时空33购买的辛鹿咖啡豆:曼特宁拼配 深度烘焙1kg,意式极深炭烧 极深烘焙1kg,云南阿拉比卡庄园豆 深度烘焙454g");
      order.setPrice(202);
      // 进行支付,使用满减优惠策略
      doPayment(order, PromotionStrategy.FULLDISCOUNT);
   }

}
复制代码

结果:

image.png

只需在进行支付的时候,调用策略选择类,利用枚举值传参,根据传参使用不同的优惠策略。

其实也可以不使用枚举传参,直接使用字符串或者常量等等方式都可以,不过枚举传参我认为可以降低错误的可能,实用一些。

3,总结

可见策略模式,在多策略的场景下是非常实用的。我们可以把策略模式的实现总结为以下几步:

  1. 抽象出策略接口,并实现不同的策略类
  2. 编写出策略选择类,其中根据不同的传入参数,选择或者执行对应的策略
  3. 客户端调用策略选择类,传入参数执行对应策略

策略模式增加了系统的可维护性,通常用在以下场景:

  • 一个系统需要在一类算法中动态地选择其中一种
  • 系统中有很多类,但是仅仅是行为不一样,需要根据情况使用其中的类

策略模式的优缺点也是很明显的:

  • 优点:
    • 符合开闭原则(尽量去扩展系统的功能而非去改写)
    • 避免了大量的if...else if...或者switch语句
  • 缺点:
    • 客户端需要知道所有的策略

最后附上整个示例的类图:

image.png

猜你喜欢

转载自juejin.im/post/7042969791472074766