0%

十三、委派模式和策略模式

委派模式

委派模式的定义及应用场景

委派模式(Delegate Pattern)的基本作用就是 负责任务的调用和分配任务,跟代理模式很像,可以看做是一种特殊情况下的静态代理 的全权代理,但是代理模式注重过程,而委派模式注重结果。

委派模式在 Spring 中应用 非常多,大家常用的DispatcherServlet其实就是用到了委派模式。现实生活中也常有委派的场景发生,例如:老板(Boss)给项目经理(Leader)下达任务,项目经理会根据 实际情况给每个员工派发工作任务,待员工把工作任务完成之后,再由项目经理汇报工 作进度和结果给老板。我们用代码来模拟下这个业务场景,先来看一下类图:
48271322

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* boss - 发送命令
* @author 陈添明
* @date 2019/4/13
*/
public class Boss {
private Leader leader;
public Boss(Leader leader) {
this.leader = leader;
}
public void doing(String command) {
leader.doing(command);
}
}

public class Leader {
private Map<String, IEmployee> register = new HashMap<>();
public Leader() {
register.put("加密", new EmployeeA());
register.put("架构", new EmployeeB());
}
/**
* leader根据不同的命令,将任务分发给不同的员工
* @param command
*/
public void doing(String command) {
register.get(command).doing(command);
}
}

public interface IEmployee {
void doing(String command);
}

委派模式在源码中的体现

下面我们再来还原一下SpringMVCDispatcherServlet是如何实现委派模式的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* DispatcherServlet:
* 所有的请求统一委派给DispatcherServlet进行处理
* @author 陈添明
* @date 2019/4/13
*/
public class DispatcherServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) {
doDispatch(req, resp);
}
/**
* 请求分发,根据不同的url,执行不同的逻辑
* @param req
* @param resp
*/
@SneakyThrows
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
String uri = req.getRequestURI();
String mid = req.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 {
resp.getWriter().write("404 Not Found!!");
}
}
}

策略模式

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

策略模式的应用场景

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

以优惠活动为例

以优惠活动为例,优惠策略会有很多种可能 如:领取优惠券抵扣、返现促销、拼团优惠。下面我们用代码来模拟,首先我们创建一个促销策略的抽象PromotionStrategy:

1
2
3
4
5
6
public interface PromotionStrategy {
/**
* 执行优惠方法
*/
void doPromotion();
}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CouponStrategy implements PromotionStrategy {
/**
* 执行优惠方法
*/
@Override
public void doPromotion() {
System.out.println("使用优惠券抵扣!!!");
}
}

public class CashbackStrategy implements PromotionStrategy {
/**
* 执行优惠方法
*/
@Override
public void doPromotion() {
System.out.println("返现优惠!!!");
}
}

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

1
2
3
4
5
6
7
8
9
10
11
12
public class PromotionActivity {
PromotionStrategy promotionStrategy;
public PromotionActivity(PromotionStrategy promotionStrategy) {
this.promotionStrategy = promotionStrategy;
}
/**
* 优惠活动开始
*/
public void execute() {
promotionStrategy.doPromotion();
}
}

我们做活动时候往往是要根据不同的需求对促销策略进行动态选择的 ,编写客户端代码进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class PromotionActivityTest {
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();
}
}

这样改造之后,满足了业务需求,客户可根据自己的需求选择不同的优惠策略了。但是, 经过一段时间的业务积累,我们的促销活动会越来越多。但是每次上活动之前都要改代码,而且要做重复测试,判断逻辑可能也变得 越来越复杂。这时候,我们是不需要思考代码是不是应该重构了?回顾我们之前学过的 设计模式应该如何来优化这段代码呢?其实,我们可以结合单例模式和工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class PromotionStrategyFactory {
/**
* 注册是单例
*/
private static Map<String, PromotionStrategy> mapping = new HashMap<>();
static {
mapping.put(PromotionKey.COUPON, new CouponStrategy());
mapping.put(PromotionKey.CASHBACK, new CashbackStrategy());
mapping.put(PromotionKey.GROUPBUY, new GroupbuyStrategy());
}
/**
* 根据优惠标识获取对应的优惠策略对象
* @param promotionKey
* @return
*/
public static PromotionStrategy getPromotionStrategy(String promotionKey) {
return mapping.get(promotionKey);
}
private interface PromotionKey {
String COUPON = "COUPON";
String CASHBACK = "CASHBACK";
String GROUPBUY = "GROUPBUY";
}
}

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

58553017

用策略模式实现选择支付方式的业务场景

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

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    /**
    * 定义支付规范和支付逻辑
    */
    public abstract class Payment {
    /**
    * 支付类型名称
    * @return
    */
    public abstract String getName();
    /**
    * 查询余额
    * @param uid
    * @return
    */
    protected abstract double queryBalance(String uid);
    /**
    * 扣款支付
    * @param uid
    * @param amount
    * @return
    */
    public PayState pay(String uid, double amount) {
    if (queryBalance(uid) < amount) {
    return new PayState(500, "支付失败", "余额不足");
    }
    return new PayState(200, "支付成功", "支付金额:" + amount);
    }
    }
  • 分别创建具体的支付方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class AliPay extends Payment {
    @Override
    public String getName() {
    return "支付宝";
    }
    @Override
    protected double queryBalance(String uid) {
    return 900;
    }
    }

    public class WeChatPay extends Payment {
    @Override
    public String getName() {
    return "微信支付";
    }
    @Override
    protected double queryBalance(String uid) {
    return 100;
    }
    }
  • 创建支付方式管理类:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class PaymentManager {
    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;
    private static Map<String, Payment> mapping = new HashMap<>();
    static {
    mapping.put(ALI_PAY, new AliPay());
    mapping.put(JD_PAY, new JDPay());
    mapping.put(WECHAT_PAY, new WeChatPay());
    }
    /**
    * 根据payKey获取支付方式
    *
    * @param payKey
    * @return
    */
    public static Payment getPayment(String payKey) {
    Payment payment = mapping.get(payKey);
    return payment == null ? mapping.get(DEFAULT_PAY) : payment;
    }
    }
  • 订单类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    @AllArgsConstructor
    public class Order {
    private String uid;
    private String orderId;
    private double amount;
    /**
    * 完美地解决了 switch 的过程,不需要在代码逻辑中写 switch 了
    * 也不用写 if else
    * @return
    */
    public PayState pay() {
    return pay(PaymentManager.DEFAULT_PAY);
    }
    public PayState pay(String payKey) {
    Payment payment = PaymentManager.getPayment(payKey);
    System.out.println("欢迎使用" + payment.getName());
    System.out.println("本次交易金额为:" + amount + ",开始扣款...");
    return payment.pay(uid, amount);
    }
    }

61183186

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

首先来看一个比较常用的比较器Comparator接口,我们看到的一个大家常用的compare()方法,就是一个策略抽象实现:
61312317
Comparator抽象下面有非常多的实现类,我们经常会把Comparator作为参数传入作 为排序策略,例如Arrays类的parallelSort方法等:
61370759

策略模式的优缺点

优点:

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

缺点:

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

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

现在,我们再来回顾一下,DispatcherServlet的委派逻辑,代码如下:
61583586

这样的代码扩展性不太优雅,也不现实,因为我们实际项目中一定不止这几个 Controller, 往往是成千上万个 Controller,显然,我们不能写成千上万个 if…else… 。那么我们如何 来改造呢?小伙伴们一定首先就想到了策略模式,来看一下我是怎么优化的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class DispatcherServlet extends HttpServlet {
/**
* 处理器映射
*/
private List<Handler> handlerMapping = new ArrayList<>();
@Override
@SneakyThrows
public void init() {
// 初始化处理器映射
handlerMapping.add(new Handler()
.setController(new MemberController())
.setMethod(MemberController.class.getMethod("getMemberById", String.class))
.setUrl("/api/member/getMemberById"));
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) {
doDispatch(req, resp);
}
/**
* 请求分发,根据不同的url,执行不同的逻辑
* @param req
* @param resp
*/
@SneakyThrows
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
//1、获取用户请求的 url
// 如果按照 J2EE 的标准、每个 url 对对应一个 Serlvet,url 由浏览器输入
String uri = req.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;
}
}
if (handle == null) {
// 没有找到处理器,回404
resp.getWriter().write("404 not Found");
return;
}
// 4、将具体的任务分发给 Method(通过反射去调用其对应的方法)
Object result = handle.getMethod().invoke(handle.getController(), req.getParameter("mid"));
resp.getWriter().write(result.toString());
}
@Data
@Accessors(chain = true)
class Handler {
private Object controller;
private Method method;
private String url;
}
}

上面的代码我结合了策略模式、工厂模式、单例模式。
源码:https://github.com/chentianming11/design-pattern
delegate包!