Spring源码学习笔记:经典设计模式之策略模式

1、博客内容均出自于咕泡学院架构师第三期
2、架构师系列内容:架构师学习笔记(持续更新)

0、策略模式(Strategy pattern)

指定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的用户,可以避免多重分支的if…else…和switch语句。

适用场景:

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

优缺点:
优点:

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

缺点:

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

1、用策略模式实现多种优惠活动的业务场景

业务场景:比如某个app有很多的优惠活动,优惠策略会有很多种可能,如:领取优惠劵抵扣,返现促销,拼团优惠等等。
用策略模式来实现这种业务场景

首先创建一个促销策略的接口 PromotionStrategy:

package com.jarvisy.demo.pattern.strategy.promotion;


/**
* @author :Jarvisy
* @date :Created in 2020/9/18 23:56
* @description :
*/
public interface PromotionStrategy {


    void doPromotion();


}

然后分别创建优惠抵扣策略 CouponStrategy 类、返现促销策略 CashbackStrategy类、拼团优惠策略 GroupbuyStrategy 类和无优惠策略 EmptyStrategy 类:

package com.jarvisy.demo.pattern.strategy.promotion;


/**
* @author :Jarvisy
* @date :Created in 2020/9/18 23:58
* @description :
*/
public class CashbackStrategy implements PromotionStrategy{


    @Override
    public void doPromotion() {
        System.out.println("返现促销,返回的金额转到支付宝账号");
    }
}


package com.jarvisy.demo.pattern.strategy.promotion;


/**
* @author :Jarvisy
* @date :Created in 2020/9/18 23:58
* @description :
*/
public class CouponStrategy implements PromotionStrategy{


    @Override
    public void doPromotion() {
        System.out.println("领取优惠劵,课程的价格直接减去优惠卷面值抵扣");
    }
}
package com.jarvisy.demo.pattern.strategy.promotion;


/**
* @author :Jarvisy
* @date :Created in 2020/9/18 23:57
* @description :
*/
public class EmptyStrategy implements PromotionStrategy{
    @Override
    public void doPromotion() {
        System.out.println("无促销活动");
    }
}
package com.jarvisy.demo.pattern.strategy.promotion;


/**
* @author :Jarvisy
* @date :Created in 2020/9/18 23:58
* @description :
*/
public class GroupBuyStrategy implements PromotionStrategy{


    @Override
    public void doPromotion() {
        System.out.println("拼团,满20人成团,全团享受团购价");
    }
}

然后创建促销活动方案 PromotionActivity 类:

package com.jarvisy.demo.pattern.strategy.promotion;


/**
* @author :Jarvisy
* @date :Created in 2020/9/19 0:02
* @description :
*/
public class PromotionActivity {


    PromotionStrategy promotionStrategy;


    public PromotionActivity(PromotionStrategy promotionStrategy) {
        this.promotionStrategy = promotionStrategy;
    }


    public void execute() {
        this.promotionStrategy.doPromotion();
    }


}

创建测试类:

package com.jarvisy.demo.pattern.strategy.promotion;

/**
* @author :Jarvisy
* @date :Created in 2020/9/19 0:02
* @description :
*/
public class PromotionActivityTest {


    public static void main(String[] args) {
        PromotionActivity promotionActivity = null;
        String promotionKey = "COUPON";
        if ("COUPON".equals(promotionKey)) {
            promotionActivity = new PromotionActivity(new CouponStrategy());
        } else if ("CASHBACK".equals(promotionKey)) {
            promotionActivity = new PromotionActivity(new CashbackStrategy());
        } else {
            promotionActivity = new PromotionActivity(new EmptyStrategy());
        }
        promotionActivity.execute();
    }


}

客户可以根据自己的需求选择不同的优惠策略。但是经过一段时间的业务积累,我们的促销活动会越来越多。于是,就需要去更改if判断逻辑,而且要重复测试,判断逻辑也可能会变得越来越复杂。这个时候就需要考虑代码是否需要重构?
这个时候我们就可以结合其他的设计模式来优化这个代码。
这里我们可以结合单例模式和工厂模式。
创建 PromotionStrategyFactory 类:

package com.jarvisy.demo.pattern.strategy.promotion;


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


/**
* @author :Jarvisy
* @date :Created in 2020/9/19 0:13
* @description :用到了注册式单例,工厂模式,策略模式
*/
public class PromotionStrategyFactory {
    private PromotionStrategyFactory() {
    }


    private static Map<String, PromotionStrategy> PROMOTION_STRATEGY_MAP = new HashMap<>();


    static {
        PROMOTION_STRATEGY_MAP.put(PromotionKey.COUPON, new CouponStrategy());
        PROMOTION_STRATEGY_MAP.put(PromotionKey.CASHBACK, new CashbackStrategy());
        PROMOTION_STRATEGY_MAP.put(PromotionKey.GROUP_BUY, new GroupBuyStrategy());
    }


    private static final PromotionStrategy NON_PROMOTION = new EmptyStrategy();


    public static PromotionStrategy getPromotionStrategy(String promotionKey) {
        PromotionStrategy promotionStrategy = PROMOTION_STRATEGY_MAP.get(promotionKey);
        return promotionStrategy == null ? NON_PROMOTION : promotionStrategy;
    }


    private interface PromotionKey {
        String COUPON = "COUPON";
        String CASHBACK = "CASHBACK";
        String GROUP_BUY = "GROUP_BUY";
    }
}

修改测试代码:

package com.jarvisy.demo.pattern.strategy.promotion;


/**
* @author :Jarvisy
* @date :Created in 2020/9/19 0:02
* @description :
*/
public class PromotionActivityTest {

    public static void main(String[] args) {
        String promotionKey = "COUPON";
        PromotionActivity promotionActivity = new PromotionActivity(PromotionStrategyFactory.getPromotionStrategy(promotionKey));
        promotionActivity.execute();
    }

}

每当上新活动的时候,我们只要在新建优惠策略类就可以了,不影响原来的代码逻辑。

附上:类图
在这里插入图片描述

2、用策略模式解决多种支付业务

生活中使用支付宝、微信支付、银联支付以及京东白条非常常见。一个常见的应用场景就是大家在下单
支付时会提示选择支付方式,如果用户未选,系统也会默认好推荐的支付方式进行结算。

首先创建Payment抽象类,定义支付规范和支付逻辑:

package com.jarvisy.demo.pattern.strategy.pay.payport;

import com.jarvisy.demo.pattern.strategy.pay.MsgResult;

/**
* @author :Jarvisy
* @date :Created in 2020/9/19 1:02
* @description :
*/
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);
    }
}

然后分别创建具体的支付方式:

package com.jarvisy.demo.pattern.strategy.pay.payport;

/**
* @author :Jarvisy
* @date :Created in 2020/9/19 1:02
* @description :
*/
public class AliPay extends Payment {

    public String getName() {
        return "支付宝";
    }

    protected double queryBalance(String uid) {
        return 900;
    }

}
package com.jarvisy.demo.pattern.strategy.pay.payport;

/**
* @author :Jarvisy
* @date :Created in 2020/9/19 1:02
* @description :
*/
public class JDPay extends Payment {

    public String getName() {
        return "京东白条";
    }

    protected double queryBalance(String uid) {
        return 500;
    }
}
package com.jarvisy.demo.pattern.strategy.pay.payport;

/**
* @author :Jarvisy
* @date :Created in 2020/9/19 1:02
* @description :
*/
public class WeChatPay extends Payment {

    public String getName() {
        return "微信支付";
    }

    protected double queryBalance(String uid) {
        return 256;
    }

}

支付状态的包装类:

package com.jarvisy.demo.pattern.strategy.pay;


/**
* @author :Jarvisy
* @date :Created in 2020/9/19 1:01
* @description :支付完成以后的状态
*/
public class PayState {
    private int code;
    private Object data;
    private String msg;


    public PayState(int code, String msg, Object data) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }


    public String toString() {
        return ("支付状态:[" + code + "]," + msg + ",交易详情:" + data);
    }
}

创建支付策略管理类:

package com.jarvisy.demo.pattern.strategy.pay.payport;


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


/**
* @author :Jarvisy
* @date :Created in 2020/9/19 1:02
* @description :
*/
public class PayStrategy {
    public static final String ALI_PAY = "AliPay";
    public static final String JD_PAY = "JdPay";
    public static final String UNION_PAY = "UnionPay";
    public static final String WE_CHAT_PAY = "WeChatPay";
    public static final String DEFAULT_PAY = ALI_PAY;


    private static Map<String, Payment> payStrategy = new HashMap<String, Payment>();


    static {
        payStrategy.put(ALI_PAY, new AliPay());
        payStrategy.put(WE_CHAT_PAY, new WeChatPay());
        payStrategy.put(UNION_PAY, new UnionPay());
        payStrategy.put(JD_PAY, new JDPay());
    }


    public static Payment get(String payKey) {
        if (!payStrategy.containsKey(payKey)) {
            return payStrategy.get(DEFAULT_PAY);
        }
        return payStrategy.get(payKey);
    }
}

创建Order类:

package com.jarvisy.demo.pattern.strategy.pay;


import com.jarvisy.demo.pattern.strategy.pay.payport.PayStrategy;
import com.jarvisy.demo.pattern.strategy.pay.payport.Payment;


/**
* @author :Jarvisy
* @date :Created in 2020/9/19 0:59
* @description :
*/
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;
    }


    //完美地解决了switch的过程,不需要在代码逻辑中写switch了
    //更不需要写if    else if
    public PayState pay() {
        return pay(PayStrategy.DEFAULT_PAY);
    }


    public PayState pay(String payKey) {
        Payment payment = PayStrategy.get(payKey);
        System.out.println("欢迎使用" + payment.getName());
        System.out.println("本次交易金额为:" + amount + ",开始扣款...");
        return payment.pay(uid, amount);
    }


}

测试:

package com.jarvisy.demo.pattern.strategy.pay;

import com.jarvisy.demo.pattern.strategy.pay.payport.PayStrategy;

/**
* @author :Jarvisy
* @date :Created in 2020/9/19 1:09
* @description :
*/
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));
    }
}

类图:
在这里插入图片描述

3、策略模式在JDK源码中的体现

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

@FunctionalInterface
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) {
    rangeCheck(a.length, fromIndex, toIndex);
    if (cmp == null)
        cmp = NaturalOrder.INSTANCE;
    int n = toIndex - fromIndex, p, g;
    if (n <= MIN_ARRAY_SORT_GRAN ||
        (p = ForkJoinPool.getCommonPoolParallelism()) == 1)
        TimSort.sort(a, fromIndex, toIndex, cmp, null, 0, 0);
    else
        new ArraysParallelSortHelpers.FJObject.Sorter<T>
            (null, a,
             (T[])Array.newInstance(a.getClass().getComponentType(), n),
             fromIndex, n, 0, ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ?
             MIN_ARRAY_SORT_GRAN : g, cmp).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 this.exists();
    }


    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 类,但是我们经常使用它的子类,例如:
在这里插入图片描述
还有一个非常典型的场景,Spring 的初始化也采用了策略模式,不同的类型的类采用不同的初始化策略。首先有一个 InstantiationStrategy 接口,我们来看一下源码:


package org.springframework.beans.factory.support;


import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.lang.Nullable;


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, Object... var5) throws BeansException;


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

顶层的策略抽象非常简单,但是它下面有两种策略 SimpleInstantiationStrategy 和CglibSubclassingInstantiationStrategy,我们看一下类图:
在这里插入图片描述
打开类图我们还发现 CglibSubclassingInstantiationStrategy 策略类还继承了SimpleInstantiationStrategy 类,说明在实际应用中多种策略之间还可以继承使用。

4、委派模式与策略模式综合应用

重新改造一下委派模式中 DispatcherServlet的代码:
委派模式博客:https://blog.csdn.net/weixin_38024782/article/details/108675963

package com.jarvisy.demo.pattern.delegate.mvc;




import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;




/**
* @author :Jarvisy
* @date :Created in 2020/9/18 21:48
* @description :相当于Leader的职能
*/
public class DispatcherServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //完成调度
        doDispatch(req,resp);
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
        String uri =req.getRequestURI();
        String mid = req.getParameter("mid");
        //此段代码可以用策略模式进行优化  后续在策略模式学习中优化
        if ("getMemberById".equals(uri)){
            new MemberController().getMemberById(mid);
        }else if ("getMemberById".equals(uri)){
            new OrderController().getOrderById(mid);
        }else if ("getMemberById".equals(uri)){
            new SystemController().logout();
        }else {
            resp.getWriter().write("404 Not Found !!!");
        }
    }
}

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

package com.jarvisy.demo.pattern.delegate.mvc;


import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;


/**
* @author :Jarvisy
* @date :Created in 2020/9/19 1:43
* @description :
*/
public class DispatcherServlet extends HttpServlet {


    private List<Handler> handlerMapping = new ArrayList<Handler>();


    public void init() throws ServletException {
        try {
            Class<?> memberControllerClass = MemberController.class;
            handlerMapping.add(new Handler()
                    .setController(memberControllerClass.newInstance())
                    .setMethod(memberControllerClass.getMethod("getMemberById", new Class[]{String.class}))
                    .setUrl("/web/getMemberById.json"));
        } catch (Exception e) {


        }
    }

//    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!!");
//        }
//
//    }

    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();


    }

    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        try {
            doDispatch(req, resp);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    class Handler {


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


        public Object getController() {
            return controller;
        }


        public Handler setController(Object controller) {
            this.controller = controller;
            return this;
        }


        public Method getMethod() {
            return method;
        }


        public Handler setMethod(Method method) {
            this.method = method;
            return this;
        }


        public String getUrl() {
            return url;
        }


        public Handler setUrl(String url) {
            this.url = url;
            return this;
        }
    }

}

上代码结合了策略模式、工厂模式、单例模式。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值