1、博客内容均出自于咕泡学院架构师第三期
2、架构师系列内容:架构师学习笔记(持续更新)
0、策略模式(Strategy pattern)
指定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的用户,可以避免多重分支的if…else…和switch语句。
适用场景:
- 假如系统中有很多类,而它们的区别仅仅在于他们的行为不同。
- 一个系统需要动态的在几种算法中选择一种。
优缺点:
优点:
- 策略模式符合开闭原则。
- 避免使用多重条件转移语句,如 if…else…语句、switch 语句。
- 使用策略模式可以提高算法的保密性和安全性。
缺点:
- 客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
- 代码中会产生非常多策略类,增加维护难度。
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;
}
}
}
上代码结合了策略模式、工厂模式、单例模式。