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{@Overridepublic 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{@Overridepublic 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{@Overridepublic 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{@Overridepublic 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 ifpublic 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);elsenew 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;@NullableString 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 {@Overrideprotected 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;}}}

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

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

  1. 结合Spring源码学习单例设计模式

    之前我学习了 Spring Ioc,明白了 Spring IoC 容器是一个管理Bean的容器,在Spring的定义中,它要求所有的IoC容器都需要实现接口 BeanFactory ,它是一个顶级容器 ...

  2. Spring源码学习笔记:经典设计模式之代理模式

    1.博客内容均出自于咕泡学院架构师第三期 2.架构师系列内容:架构师学习笔记(持续更新) 0.代理模式(Proxy Pattern) 指为其他对象提供一种代理,以控制对这个对象的访问.代理对象在客户端 ...

  3. Spring源码学习笔记:经典设计模式之观察者模式

    1.博客内容均出自于咕泡学院架构师第三期 2.架构师系列内容:架构师学习笔记(持续更新) 0.观察者模式(Observer Pattern) 观察者模式也叫发布订阅模式.定义了对象之间的一对多依赖,让 ...

  4. Spring源码学习笔记:经典设计模式之装饰者模式

    1.博客内容均出自于咕泡学院架构师第三期 2.架构师系列内容:架构师学习笔记(持续更新) 0.装饰者模式(Decorator Pattern) 指在不改变原有对象的基础之上,将功能附加到对象上,提供了 ...

  5. Spring源码学习笔记:Spring设计模式对比和Spring的OOB,BOP,AOP,IOC,DI/DL

    1.博客内容均出自于咕泡学院架构师第三期 2.架构师系列内容:架构师学习笔记(持续更新) 1.GOF 23总设计模式归纳 分类 设计模式 创建型 工厂方法模式(Factory Method).抽象工厂 ...

  6. Spring源码学习笔记:起源发展和核心模块主要职能

    1.博客内容均出自于咕泡学院架构师第三期 2.架构师系列内容:架构师学习笔记(持续更新) 1.Spring 的前世今生 早在 2007 年,一个基于 Java语言的开源框架正式发布,取了一个非常有活力 ...

  7. Spring源码学习笔记1

    1.Spring中最核心的两个类 1)DefaultListableBeanFactory XmlBeanFactory继承自DefaultListableBeanFactory,DefaultLis ...

  8. 源码解析:Spring源码解析笔记(五)接口设计总览

    本文由colodoo(纸伞)整理 QQ 425343603 Java学习交流群(717726984) Spring解析笔记 启动过程部分已经完成,对启动过程源码有兴趣的朋友可以作为参考文章. 源码解析 ...

  9. spring源码深度解析 第2版 pdf_吹爆!阿里爆款Spring源码高级笔记,原来看懂源码如此简单...

    Spring的影响力想必无需与大家多说,如果你用spring,那么读读源码有助于对你最重要的工具的理解,好的框架源码也可以帮助我们理解什么是好代码. 刚参加工作那会,没想过去读源码,更没想过去改框架的 ...

最新文章

  1. 国际顶级学术会议SIGIR 2020开幕在即,重量级嘉宾带你窥探信息检索前沿
  2. mysql注入漏洞语句_mysql注入sleep语句引发的拒绝服务
  3. TRUNCATE TABLE和PURGE_TABLE的区别
  4. bzoj 2535: [Noi2010]Plane 航空管制2【拓扑排序+堆】
  5. React优化性能的经验教训
  6. 结对开发 随机产生数组并求最大子数组的和
  7. 动态二维数组外圈元素值的和_C语言 | 用指向元素的指针变量输出二维数组元素的值...
  8. 微信公布6月朋友圈十大谣言 包括不打疫苗不让上飞机高铁等
  9. Ubuntu18.04安装TIM、微信
  10. JSP+JSTL+EL表达式,实现web页面的页面跳转功能(上一页下一页首页末页页面跳转)
  11. 骑士进化论RPG游戏实现
  12. 计算球体的表面积和体积
  13. 天猫双十一自动刷喵币
  14. react 中的闭包陷阱
  15. 微服务中自定义gateway网关过滤器
  16. 编译器工具链(二)——交叉编译
  17. 阿里云(三) Ubuntu系统下mysql卸载
  18. AcWing 1025. 开餐馆 (线性dp)
  19. 车削加工振刀打刀的现象如何处理?
  20. 产品8D报告是指哪8D步骤

热门文章

  1. git 创建webpack项目_近期总结:手动搭建react项目,将项目从自己的库引入到新的项目中使用...
  2. 笔刷怎么做_零基础怎么学板绘?板绘小白必备基础知识
  3. Fragment试手
  4. 修改Android中的文件权限
  5. Postman 如何处理上一个接口返回值作为下一个接口入参?
  6. 查看数据库中存在触发器的表
  7. iOS WKWebView和JS交互的两种方式
  8. 用友u8数据库表结构
  9. Java几款性能分析工具的对比
  10. [MVC4]初识.NET MVC4