委派模式
委派模式不属于 GOF23 种设计模式中。委派模式(Delegate Pattern)的基本作用就是 负责任务的调用和分配任务,跟代理模式很像,可以看做是一种特殊情况下的静态代理 的全权代理,但是
代理模式注重过程,而委派模式注重结果
。委派模式在 Spring 中应用 非常多,大家常用的
DispatcherServlet
其实就是用到了委派模式。
例如:老板(Boss)给项目经理(Leader)下达任务,项目经理会根据 实际情况给每个员工派发工作任务,待员工把工作任务完成之后,再由项目经理汇报工 作进度和结果给老板。我们用代码来模拟下这个业务场景,先来看一下类图:
创建 IEmployee 员工接口:
public interface IEmployee {
public void doing(String command);
}
创建员工 EmployeeA 类:
public class EmployeeA implements IEmployee {
@Override
public void doing(String command) {
System.out.println("我是员工A,我现在开始干" + command + "工作");
}
}
创建员工 EmployeeB 类:
public class EmployeeB implements IEmployee {
@Override
public void doing(String command) {
System.out.println("我是员工B,我现在开始干" + command + "工作");
}
}
创建项目经理 Leader 类:
public class Leader implements IEmployee {
private Map<String,IEmployee> targets = new HashMap<String,IEmployee>();
public Leader() {
targets.put("加密",new EmployeeA());
targets.put("登录",new EmployeeB());
}
//项目经理自己不干活
public void doing(String command){
targets.get(command).doing(command);
}
}
创建 Boss 类下达命令:
public class Boss {
public void command(String command,Leader leader){
leader.doing(command);
}
}
public class DelegateTest {
public static void main(String[] args) {
//客户请求(Boss)、委派者(Leader)、被被委派者(Target)
//委派者要持有被委派者的引用
//代理模式注重的是过程, 委派模式注重的是结果
//策略模式注重是可扩展(外部扩展),委派模式注重内部的灵活和复用
//委派的核心:就是分发、调度、派遣
//委派模式:就是静态代理和策略模式一种特殊的组合
new Boss().command("登录",new Leader());
}
}
通过上面的代码,生动地还原了项目经理分配工作的业务场景,也是委派模式的生动体 现。
委派模式在源码中的体现
下面我们再来还原一下 SpringMVC 的 DispatcherServlet 是如何实现委派模式的。创建 业务类 MemberController:
public class MemberController {
public void getMemberById(String mid){
}
}
订单OrderController 类:
public class OrderController {
public void getOrderById(String mid){
}
}
SystemController 类:
public class SystemController {
public void logout(){
}
}
创建 DispatcherServlet 类:
package com.gupaoedu.vip.pattern.delegate.mvc;
import com.gupaoedu.vip.pattern.delegate.mvc.controllers.MemberController;
import com.gupaoedu.vip.pattern.delegate.mvc.controllers.OrderController;
import com.gupaoedu.vip.pattern.delegate.mvc.controllers.SystemController;
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;
/**
* 相当于是项目经理的角色
* Created by Tom.
*/
public class DispatcherServlet extends HttpServlet{
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!!");
}
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
doDispatch(req,resp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
策略模式
策略模式(Strategy Pattern)是指定义了算法家族、分别封装起来,让它们之间可以互 相替换,此模式让算法的变化不会影响到使用算法的用户。
策略模式的应用场景
1、假如系统中有很多类,而他们的区别仅仅在于他们的行为不同。
2、一个系统需要动态地在几种算法中选择一种。
用策略模式实现选择支付方式的业务场景
如:领取优惠券抵扣、返现促销、拼团优惠。下面我们用代码来模拟,首先我们创建一
个促销策略的抽象 PromotionStrategy:
public interface PromotionStrategy {
void doPromotion();
}
然后分别创建优惠券抵扣策略 CouponStrategy 类、返现促销策略 CashbackStrategy 类、拼团优惠策略 GroupbuyStrategy 类和无优惠策略 EmptyStrategy 类:
CouponStrategy 类:
public class CouponStrategy implements PromotionStrategy {
/**
* 优惠券
*/
public void doPromotion() {
System.out.println("领取优惠券,课程的价格直接减优惠券面值抵扣");
}
}
CashbackStrategy 类:
public class CashbackStrategy implements PromotionStrategy {
/**
* 返现活动
*/
public void doPromotion() {
System.out.println("返现促销,返回的金额转到支付宝账号");
}
}
GroupbuyStrategy 类
/**
* 拼团优惠
*/
public class GroupbuyStrategy implements PromotionStrategy{
public void doPromotion() {
System.out.println("拼团,满20人成团,全团享受团购价");
}
}
EmptyStrategy 类:
/**
* 无优惠
*/
public class EmptyStrategy implements PromotionStrategy {
public void doPromotion() {
System.out.println("无促销活动");
}
}
然后创建促销活动方案 PromotionActivity 类:
/**
* 优惠活动
*/
public class PromotionActivity {
private PromotionStrategy promotionStrategy;
public PromotionActivity(PromotionStrategy promotionStrategy) {
this.promotionStrategy = promotionStrategy;
}
public void execute(){
promotionStrategy.doPromotion();
}
}
编写客户端测试类:
/**
* 促销活动
*/
public class PromotionActivityTest {
public static void main(String[] args) {
PromotionActivity activity618 = new PromotionActivity(new CouponStrategy());
PromotionActivity activity1111 = new PromotionActivity(new CashbackStrategy());
activity618.execute();
activity1111.execute();
}
}
此时,如果把上面这段测试代码放到实际的业务场景其实并不实用。 因为我们做活动时候往往是要根据不同的需求对促销策略进行动态选择的,并不会一次 性执行多种优惠。所以,我们的代码通常会这样写:
package com.gupaoedu.vip.pattern.strategy.promotion;
import org.apache.commons.lang3.StringUtils;
/**
* 促销活动
* Created by Tom
*/
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();
}
}
这样改造之后,满足了业务需求,客户可根据自己的需求选择不同的优惠策略了。但是, 经过一段时间的业务积累,我们的促销活动会越来越多。于是,我们的程序猿小哥哥就 忙不赢了,每次上活动之前都要通宵改代码,而且要做重复测试,判断逻辑可能也变得 越来越复杂。这时候,我们是不需要思考代码是不是应该重构了?回顾我们之前学过的 设计模式应该如何来优化这段代码呢?其实,我们可以结合单例模式和工厂模式。创建PromotionStrategyFactory 类:
package com.gupaoedu.vip.pattern.strategy.promotion;
import java.util.HashMap;
import java.util.Map;
/**
* 促销策略工厂
* Created by Tom
*/
public class PromotionStrategyFactory {
private static Map<String,PromotionStrategy> PROMOTION_STRATEGY_MAP = new HashMap<String, PromotionStrategy>();
static {
PROMOTION_STRATEGY_MAP.put(PromotionKey.COUPON,new CouponStrategy());
PROMOTION_STRATEGY_MAP.put(PromotionKey.CASHBACK,new CashbackStrategy());
PROMOTION_STRATEGY_MAP.put(PromotionKey.GROUPBUY,new GroupbuyStrategy());
}
private static final PromotionStrategy NON_PROMOTION = new EmptyStrategy();
private PromotionStrategyFactory(){}
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 GROUPBUY = "GROUPBUY";
}
}
这时候我们客户端代码就应该这样写了:
package com.gupaoedu.vip.pattern.strategy.promotion;
import org.apache.commons.lang3.StringUtils;
/**
* 促销活动
* Created by Tom
*/
public class PromotionActivityTest {
public static void main(String[] args) {
String promotionKey = "GROUPBUY";
PromotionActivity promotionActivity = new PromotionActivity(PromotionStrategyFactory.getPromotionStrategy(promotionKey));
promotionActivity.execute();
}
}
代码优化之后,是不是我们程序猿小哥哥的维护工作就轻松了?每次上新活动,不影响原来的代码逻辑。为了加深对策略模式的理解,我们再来举一个案例
相信小伙伴们都 用过支付宝、微信支付、银联支付以及京东白条。一个常见的应用场景就是大家在下单 支付时会提示选择支付方式,如果用户未选,系统也会默认好推荐的支付方式进行结算。 来看一下类图,下面我们用策略模式来模拟此业务场景
策略模式的优缺点
优点:
1、策略模式符合开闭原则。
2、避免使用多重条件转移语句,如 if...else...语句、switch 语句
3、使用策略模式可以提高算法的保密性和安全性。
缺点:
1、客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
2、代码中会产生非常多策略类,增加维护难度
委派模式与策略模式综合应用
在上面的代码中我们列举了非常几个业务场景,相信小伙伴对委派模式和策略模式有了 非常深刻的理解了。现在,我们再来回顾一下,DispatcherServlet 的委派逻辑,这样的代码扩展性不太优雅,也不现实,因为我们实际项目中一定不止这几个 Controller,往往是成千上万个 Controller,显然,我们不能写成千上万个 if...else... 。那么我们如何来改造呢?小伙伴们一定首先就想到了策略模式,来看一下我是怎么优化的
package com.gupaoedu.vip.pattern.delegate.mvc;
import com.gupaoedu.vip.pattern.delegate.mvc.controllers.MemberController;
import com.gupaoedu.vip.pattern.delegate.mvc.controllers.OrderController;
import com.gupaoedu.vip.pattern.delegate.mvc.controllers.SystemController;
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;
/**
* 相当于是项目经理的角色
* Created by Tom.
*/
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 源码时还会讲到 DispatcherServlet 的相关内容。