十:策略模式详解
1.定义
策略模式 (Strategy Pattern) 又叫也叫政策摆式 (Policy Pattern) , 它是将定义的算法家族、分
别封装起来,让它们之间可以互相替换,从而让算法的变化不会影响到使用算法的用户。属千行为型模式。
策略摆式使用的就是面向对象的继承和多态机制,从而实现同—行为在不同场萦下具备不同实现。
2.应用场景
策略模式在生活场景中应用也非常多。比如—个入的交税比率与他的工资有关,不同的工资水平对
应不同的税率。再比如我们在互联网移动支付的大背景下,每次下单后付款前,需要选择支付方式。
策略模式可以解决在有多种算法相似的情况下,使用 if…else 或 switch… case 所带来的复杂性和臃肿性。在日常业务开发中,策略模式适用于以下场景 :
1 、针对同—类型问题,有多种处理方式,每—种都能独立解决问题 ;
2、 算法需要自由切换的场果 ;
3、需要屏蔽算法规则的场果。
3.促销优惠业务场景
优惠策略会有很多种可能如 : 领取优惠券抵扣、返现促销、拼团优惠。下面我们用代码来模拟,首先我们创建—个促销策略的抽象
PromotionStrategy :
/**
* 促销策略抽象
*/
public interface IPromotionStrategy {
void doPromotion();
}
然后分别创建优惠券抵扣策略 CouponStrategy 类、返现促销策略 CashbackStrategy 类、拼团优惠策略 GroupbuyStrategy 类和无优惠策略 EmptyStrategy 类 :
public class CouponStrategy implements IPromotionStrategy {
public void doPromotion() {
System.out.println("使用优惠券抵扣");
}
}
GroupbuyStrategy 类 :
public class GroupbuyStrategy implements IPromotionStrategy {
public void doPromotion() {
System.out.println("5人成团,可以优惠");
}
}
然后创建促销活动方案 PromotionActivity 类 :
public class PromotionActivity {
private IPromotionStrategy strategy;
public PromotionActivity(IPromotionStrategy strategy) {
this.strategy = strategy;
}
public void execute(){
strategy.doPromotion();
}
}
测试类
public class Test {
public static void main(String[] args) {
PromotionActivity activity1 = new PromotionActivity(new CouponStrategy());
PromotionActivity activity2= new PromotionActivity(new CashbackStrategy());
activity1.execute();
activity2.execute();
}
}
如果把上面这段则试代码放到实际的业务场景其实并不实用。因为我们做活动时候往往是要根据不同的需求对促销策略进行动态选择的,并不会一次性执行多种优惠。优化如下:
public class Test {
public static void main(String[] args) {
PromotionActivity promotionActivity = null;
String promotionKey = "COUPON";
if (StringUtils.equals(promotionKey, "COUPON")) {
promotionActivity = new PromotionActivity(new CouponStrategy());
} else if (StringUtils.equals(promotionKey, "CASHBACK")) {
promotionActivity = new PromotionActivity(new CashbackStrategy());
}
promotionActivity.execute();
}
}
改造之后,满足了业务需求,客户可根据自己的需求选择不同的优惠策略了。但是,经过—段
时间的业务积累,我们的促销活动会越来越多。判断逻辑可能也变得越来越复杂,我们是不需要思考
代码是不是应该重构了?回顾我们之前学过的设计模式应该如何来优化这段代码呢?其实,我们可以结
合单例模式和工厂摆式。创建 PromotionStrategyFactory 类 :
public class PromotionStrategyFacory {
private static final IPromotionStrategy EMPTY = new EmptyStrategy();
private static Map<String, IPromotionStrategy> PROMOTIONS = new HashMap<String, IPromotionStrategy>();
static {
PROMOTIONS.put(PromotionKey.COUPON, new CouponStrategy());
PROMOTIONS.put(PromotionKey.CASHBACK, new CashbackStrategy());
PROMOTIONS.put(PromotionKey.GROUPBUY, new GroupbuyStrategy());
}
private PromotionStrategyFacory() {
}
public static IPromotionStrategy getPromotionStrategy(String promotionKey) {
IPromotionStrategy strategy = PROMOTIONS.get(promotionKey);
return strategy == null ? EMPTY : strategy;
}
public static Set<String> getPromotionKeys() {
return PROMOTIONS.keySet();
}
private interface PromotionKey {
String COUPON = "COUPON";
String CASHBACK = "CASHBACK";
String GROUPBUY = "GROUPBUY";
}
}
测试:
public class Test {
public static void main(String[] args) {
PromotionActivity activity1 = new PromotionActivity(new CouponStrategy());
PromotionActivity activity2= new PromotionActivity(new CashbackStrategy());
activity1.execute();
activity2.execute();
}
}
4.用策略模式实现选择支付方式的业务场景
为了加深对策略模式的理解,我们再来举一个案例。 相信小伙伴们都用过支付宝、微信支付、银联
支付以及京东白条。 —个常见的应用场景就是大家在下单支付时会提示选择支付方式,如果用户未选,
系统也会默认好推荐的支付方式进行结算。来看一下类图,下面我们用策略模式来描拟此业务场罢 :
创建 Payment 抽象类,定义支付规范和支付逻辑,代码如下 :
public abstract class Payment { public abstract String getName(); //通用逻辑放到抽象类里面实现 public MsgResult pay(String uid, double amount) { //余额是否足够 if (queryBalance(uid) < amount) { return new MsgResult(500, "支付失败", "余额不足"); } return new MsgResult(200, "支付成功", "支付金额" + amount); } protected abstract double queryBalance(String uid); } 分别创建具体的支付方式,支付宝 AliPay类 :
public class AliPay extends Payment {
public String getName() {
return "支付宝";
}
protected double queryBalance(String uid) {
return 900;
}
}
京东白条JDPay 类 :
public class JDPay extends Payment {
public String getName() {
return "京东白条";
}
protected double queryBalance(String uid) {
return 500;
}
}
微信支付 WechatPay类 :
public class WechatPay extends Payment { public String getName() { return "微信支付"; } protected double queryBalance(String uid) { return 263; } }
银联支付 UnionPay 类 :
public class UnionPay extends Payment { public String getName() { return "银联支付"; } protected double queryBalance(String uid) { return 120; } } 创建支付状态的包装类 MsgResult:
public class MsgResult {
private int code;
private Object data;
private String msg;
public MsgResult(int code, String msg, Object data) {
this.code = code;
this.data = data;
this.msg = msg;
}
@Override
public String toString() {
return "MsgResult{" +
"code=" + code +
", data=" + data +
", msg='" + msg + '\'' +
'}';
}
}
创建支付策略管理类 :
public class PayStrategy {
public static final String ALI_PAY = "AliPay";
public static final String JD_PAY = "JdPay";
public static final String WECHAT_PAY = "WechatPay";
public static final String UNION_PAY = "UnionPay";
public static final String DEFAULT_PAY = ALI_PAY;
private static Map<String,Payment> strategy = new HashMap<String,Payment>();
static {
strategy.put(ALI_PAY,new AliPay());
strategy.put(JD_PAY,new JDPay());
strategy.put(WECHAT_PAY,new WechatPay());
strategy.put(UNION_PAY,new UnionPay());
}
public static Payment get(String payKey){
if(!strategy.containsKey(payKey)){
return strategy.get(DEFAULT_PAY);
}
return strategy.get(payKey);
}
}
创建订单 Order 类 :
public class Order { private String uid; private String orderId; private double amount; public Order(String uid, String orderId, double amount) { this.uid = uid; this.orderId = orderId; this.amount = amount; } public MsgResult pay(){ return pay(PayStrategy.DEFAULT_PAY); } public MsgResult pay(String payKey){ Payment payment = PayStrategy.get(payKey); System.out.println("欢迎使用" + payment.getName()); System.out.println("本次交易金额为" + amount + ",开始扣款"); return payment.pay(uid,amount); } } 测试
public class Test {
public static void main(String[] args) {
Order order = new Order("1","2020031401000323",324.5);
System.out.println(order.pay(PayStrategy.UNION_PAY));
}
}
结果:
欢迎使用银联支付
本次交易金额为324.5,开始扣款
MsgResult{code=500, data=余额不足, msg='支付失败'}
5.策略模式在框架源码中的体现
JDK 中—个比较常用的比较器 Comparator 接口,我们看到的—个大家常用的 compare()
方法,就是一个策略抽象实现 :
public interface Comparator<T> {
int compare(T o1, T o2);
}
Comparator抽象下面有非常多的实现类,我们经常会把 Comparator作为参数传入作为排序策略,
例如 Arrays 类的 parallelSort 方法等 :
public class Arrays {
public static void parallelSort(byte[] a) {
int n = a.length, p, g;
if (n <= MIN_ARRAY_SORT_GRAN ||
(p = ForkJoinPool.getCommonPoolParallelism()) == 1)
DualPivotQuicksort.sort(a, 0, n - 1);
else
new ArraysParallelSortHelpers.FJByte.Sorter
(null, a, new byte[n], 0, n, 0,
((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
MIN_ARRAY_SORT_GRAN : g).invoke();
}
}
还有 TreeMap 的构造方法:
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>, Cloneable, java.io.Serializable { public TreeMap(Comparator<? super K> comparator) { this.comparator = comparator; } }
这就是 Comparator 在JDK原码中的应用。那我们来看策略模式在 Spring源码中的应用,来看
Resource 类 :
public interface Resource extends InputStreamSource {
boolean exists();
default boolean isReadable() {
return true;
}
default boolean isOpen() {
return false;
}
default boolean isFile() {
return false;
}
URL getURL() throws IOException;
URI getURI() throws IOException;
File getFile() throws IOException;
default ReadableByteChannel readableChannel() throws IOException {
return Channels.newChannel(this.getInputStream());
}
long contentLength() throws IOException;
long lastModified() throws IOException;
Resource createRelative(String var1) throws IOException;
@Nullable
String getFilename();
String getDescription();
}
我们虽然没有直接使用 Resource 类,但是我们经常使用它的子类,例如 :
/* * @see WritableResource * @see ContextResource * @see UrlResource * @see ClassPathResource * @see FileSystemResource * @see PathResource * @see ByteArrayResource * @see InputStreamResource */
还有一个非常典型的场罢, Spring 的初始化也采用了策略模式,不同的类型的类采用不同的初始化
策略。首先有一个 lnstantiationStrategy 接口,我们来看一下涌码 :
public interface InstantiationStrategy {
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) throws BeansException;
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner, Constructor<?> ctor, @Nullable Object... args) throws BeansException;
Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner, @Nullable Object factoryBean, Method factoryMethod, @Nullable Object... args) throws BeansException;
}
6.策略模式的优缺点
优点 :
1 、策略模式符合开闭原则。
2、避免使用多重条件转移语句,如 if…else… 语句、 switch 语句
3、使用策略模式可以提高算法的保密性和安全性。
缺点 :
1 、客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
2、代码中会产生非常多策略类,增加维护难度。