【设计模式】行为型:策略模式

设计模式 专栏收录该内容
37 篇文章 0 订阅

策略模式(Strategy Pattern)是指定义了算法家族、分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的用户。

我们知道,工厂模式是解耦对象的创建和使用,观察者模式是解耦观察者和被观察者。策略模式跟两者类似,也能起到解耦的作用,不过,它解耦的是策略的定义、创建、使用这三部分。

策略模式的应用场景:

  • 假如系统中有很多类,而他们的区别仅仅在于他们的行为不同
  • 一个系统需要动态地在几种算法中选择一种

1.代码示例

为了加深对策略模式的理解,我们来举一个案例。相信小伙伴们都用过支付宝、微信支付、银联支付以及京东白条。一个常见的应用场景就是大家在下单支付时会提示选择支付方式,如果用户未选,系统也会默认好推荐的支付方式进行结算。 下面我们用策略模式来模拟此业务场景

1)定义策略

/**
 * 支付渠道
 */
public abstract class Payment {

    // 支付类型
    public abstract String getName();

    // 查询余额
    protected abstract double queryBalance(String uid);

    // 扣款支付
    public MsgResult pay(String uid, double amount) {
        if(queryBalance(uid) < amount){
            return new MsgResult(500,"支付失败","余额不足");
        }
        return new MsgResult(200,"支付成功","支付金额:" + amount);
    }

}

分别创建具体的支付方式,支付宝 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 256;
    }

}

2)创建策略

因为策略模式会包含一组策略,在使用它们的时候,一般会通过类型(type)来判断创建哪个策略来使用。为了封装创建逻辑,我们需要对客户端代码屏蔽创建细节。我们可以把根据 type 创建策略的逻辑抽离出来,放到工厂类中。而不是通过 if/else…else if 来进行分支判断。

创建支付策略管理类:

/**
 * 支付策略管理
 */
public class PayStrategy {

	// key
    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 DEFAULT_PAY = ALI_PAY;
	
	// 通过 Map 保存所有策略
    private static Map<String,Payment> payStrategy = new HashMap<String,Payment>();
    static {
        payStrategy.put(ALI_PAY,new AliPay());
        payStrategy.put(WECHAT_PAY,new WechatPay());
        payStrategy.put(UNION_PAY,new UnionPay());
        payStrategy.put(JD_PAY,new JDPay());
    }
	
	// 根据 type 获取具体策略
    public static Payment get(String payKey){
        if(!payStrategy.containsKey(payKey)){
            return payStrategy.get(DEFAULT_PAY);
        }
        return payStrategy.get(payKey);
    }
}

3)使用策略

创建订单 Order 类:

public class Order {
    private String uid;
    private String orderId;
    private double amount;
	
	// 构造订单的入参:商品 id,订单id,具体金额
    public Order(String uid,String orderId,double amount){
        this.uid = uid;
        this.orderId = orderId;
        this.amount = amount;
    }
	
	// 默认支付方式
    // 完美地解决了switch的过程,不需要在代码逻辑中写switch了
    // 更不需要写 if    else if
    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 PayStrategyTest {

    public static void main(String[] args) {

        // 省略把商品添加到购物车,再从购物车下单
        // 直接从点单开始
        Order order = new Order("1","20180311001000009",324.45);

        // 开始支付,选择微信支付、支付宝、京东白条
        // 每个渠道它支付的具体算法是不一样的
        // 基本算法固定的

        // 这个值是在支付的时候才决定用哪个值
        System.out.println(order.pay(PayStrategy.ALI_PAY));
    }

}

2.源码应用

首先来看一个比较常用的比较器 Comparator 接口,我们看到的一个大家常用的 compare()方法,就是一个策略抽象实现

public interface Comparator<T> {
    int compare(T o1, T o2);
    ...
}

Comparator 抽象下面有非常多的实现类,我们经常会把 Comparator 作为参数传入作为排序策略,例如 Arrays 类的 parallelSort 方法等

public class Arrays {
    ...
    public static <T> void parallelSort(T[] a, int fromIndex, int toIndex,
        Comparator<? super T> cmp) {
        ...
    }
    ...
}

还有 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 源码中的应用,Spring 的初始化也采用了策略模式,不同的类型的类采用不同的初始化策略。首先有一个 InstantiationStrategy 接口,我们来看一下源码:

public interface InstantiationStrategy {

    Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3) throws
    BeansException;
    
    Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3,Constructor<?> var4, @Nullable Object... var5) throws BeansException;
    
    Object instantiate(RootBeanDefinition var1, @Nullable String var2, BeanFactory var3, @Nullable Object var4, Method var5, @Nullable Object... var6) throws BeansException;
}

顶层的策略抽象非常简单,但是它下面有两种策略 SimpleInstantiationStrategy 和 CglibSubclassingInstantiationStrategy,我们看一下类图:

在这里插入图片描述

3.总结

策略模式定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。

策略模式用来解耦策略的定义、创建、使用。实际上,一个完整的策略模式就是由这三个部分组成的

  • 策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类
  • 策略的创建由工厂类来完成,封装策略创建的细节
  • 策略模式包含一组策略可选,客户端代码如何选择使用哪个策略,有两种确定方法:编译时静态确定和运行时动态确定。其中,“运行时动态确定”才是策略模式最典型的应用场景。

除此之外,我们还可以通过策略模式来移除 if-else 分支判断。实际上,这得益于策略工厂类,更本质上点讲,是借助“查表法”,根据 type 查表替代根据 type 分支判断。

优点:

  • 策略模式符合开闭原则
  • 避免使用多重条件转移语句,如 if…else…语句、switch 语句
  • 使用策略模式可以提高算法的保密性和安全性

缺点:

  • 客户端必须知道所有的策略,并且自行决定使用哪一个策略类
  • 代码中会产生非常多策略类,增加维护难度

4.实际应用场景示例

前面的文章介绍委派模式时,我们说了 SpringMVC 的 DispatchServlet 使用了委派模式,当有请求来到时,并不是 DispatchServlet 自己处理,而是委派给相应的 servlet

public class DispatcherServlet extends HttpServle {
    
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req,resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    private void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception{

        String uri = request.getRequestURI();

        String mid = request.getParameter("mid");
		
		// 委派
        if("getMemberById".equals(uri)){
            new MemberController().getMemberById(mid);
        }else if("getOrderById".equals(uri)){
            new OrderController().getOrderById(mid);
        }else if("logout".equals(uri)){
            new SystemController().logout();
        }else {
            response.getWriter().write("404 Not Found!!");
        }

    }
}

这样的代码扩展性不太优雅,也不现实,因为我们实际项目中一定不止这几个 Controller, 往往是成千上万个 Controller,显然,我们不能写成千上万个 if…else… 。那么我们如何来改造呢?

这里我们就可以用到策略模式,来看一下具体是怎么优化的:

public class DispatcherServlet extends HttpServle {
    
    private void doDispatch(HttpServletRequest request, HttpServletResponse response){

        // 1.获取用户请求的url
        // 如果按照J2EE的标准、每个url对对应一个Serlvet,url由浏览器输入
       String uri = request.getRequestURI();

        // 2.Servlet拿到url以后,要做权衡(要做判断,要做选择)
        // 根据用户请求的URL,去找到这个url对应的某一个java类的方法

        // 3.通过拿到的URL去handlerMapping(我们把它认为是策略常量)
        Handler handle = null;
        for (Handler h: handlerMapping) {
            if(uri.equals(h.getUrl())){
                handle = h;
                break;
            }
        }

        // 4.将具体的任务分发给Method(通过反射去调用其对应的方法)
        Object object = null;
        try {
            object = handle.getMethod().invoke(handle.getController(),request.getParameter("mid"));
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

        // 5.获取到Method执行的结果,通过Response返回出去
	    //  response.getWriter().write();

    }
	
	// 可以理解成对策略的封装
    class Handler{

            private Object controller;
            private Method method;
            private String url;

        // getter/setter...
    }
}
  • 1
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

表情包
插入表情
评论将由博主筛选后显示,对所有人可见 | 还能输入1000个字符
相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值