设计模式详解--代理模式
1.委派模式的定义及应用场景
委派模式(Delegate Pattern)的基本作用就是负责任务的调用和分配任务,跟代理模式很像,可以看做是一种特殊情况下的静态代理的全权代理(因此委派模式不属于 GOF23 种设计模式中。),但是代理模式注重过程,而委派模式注重结果(代理模式有代理过程中增强功能,而委派模式更多像把任务分发下去)。
委派模式在 Spring 中应用非常多,大家常用的 DispatcherServlet 其实就是用到了委派模式。委派模式不属于 GOF23 种设计模式中。
现实生活中也常有委派的场景发生,例如:老板(Boss)给项目经理(Leader)下达任务,项目经理会根据实际情况给每个员工派发工作任务,待员工把工作任务完成之后,再由项目经理汇报工作进度和结果给老板。
我们用代码来模拟下这个业务场景,先来看一下类图:
1)创建一个员工接口,后面的实体类都要实现这个接口
public interface IEmployee {
public void doing(String command);
}
2)创建员工A和员工B,实现IEmployee接口
public class EmployeeA implements IEmployee {
@Override
public void doing(String command) {
System.out.println("我是员工A,我现在开始干" + command + "工作");
}
}
public class EmployeeB implements IEmployee {
@Override
public void doing(String command) {
System.out.println("我是员工B,我现在开始干" + command + "工作");
}
}
3)创建项目经理类,实现IEmployee接口
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);
}
}
4)创建BOSS类依旧实现IEmployee接口
public class Boss {
public void command(String command,Leader leader){
leader.doing(command);
}
}
5)测试类
public class DelegateTest {
public static void main(String[] args) {
//客户请求(Boss)、委派者(Leader)、被被委派者(Target)
//委派者要持有被委派者的引用
//代理模式注重的是过程, 委派模式注重的是结果
//策略模式注重是可扩展(外部扩展),委派模式注重内部的灵活和复用
//委派的核心:就是分发、调度、派遣
//委派模式:就是静态代理和策略模式一种特殊的组合
new Boss().command("登录",new Leader());
}
}
6)测试结果
7)总结
可以看到无论是真正干活的人还是委派命令的人都实现一个接口,没有做更多的操作,更像是把一个命令逐层下发,这就是委派模式
2.委派模式在源码中的体现
下面我们再来还原一下 SpringMVC 的 DispatcherServlet 是如何实现委派模式的(把多个http请求分发给不通的controller)。
创建业务类 MemberController:
public class MemberController {
public void getMemberById(String mid){
}
}
1)OrderController 类:
public class OrderController {
public void getOrderById(String mid){
}
}
SystemController 类:
public class SystemController {
public void logout(){
}
}
2)创建 DispatcherServlet 类(相当于项目经理的角色):
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();
}
}
}
3)配置web.xml文件
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:javaee="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
version="2.4">
<display-name> Web Application</display-name>
<servlet>
<servlet-name>delegateServlet</servlet-name>
<servlet-class>com.pattern.delegate.mvc.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>delegateServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
</web-app>
总结:一个完整的委派模式就实现出来了。当然,在 Spring 中运用到委派模式不仅于此,还有很多。小伙伴们可以通过命名就可以识别。在 Spring 源码中,只要以 Delegate 结尾的都是实现了委派模式。例如:BeanDefinitionParserDelegate 根据不同类型委派不同的逻辑解析 BeanDefinition。
策略模式:
策略模式(Strategy Pattern)是指定义了算法家族、分别封装起来,让它们之间可以互相替换,此模式让算法的变化不会影响到使用算法的用户。
策略模式的应用场景
1、假如系统中有很多类,而他们的区别仅仅在于他们的行为不同。
2、一个系统需要动态地在几种算法中选择一种。
用策略模式实现选择支付方式的业务场景
商场经常会有优惠活动,优惠策略会有很多种可能
如:领取优惠券抵扣、返现促销、拼团优惠。下面我们用代码来模拟。
1)首先我们创建一个促销策略的抽象 PromotionStrategy:
/**
* 促销策略抽象
*/
public interface PromotionStrategy {
void doPromotion();
}
2)然后分别创建几个策略类
优惠券类策略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("无促销活动");
}
}
3)然后创建促销活动方案 PromotionActivity 类:
/**
* 优惠活动
*/
public class PromotionActivity {
private PromotionStrategy promotionStrategy;
public PromotionActivity(PromotionStrategy promotionStrategy) {
this.promotionStrategy = promotionStrategy;
}
public void execute(){
promotionStrategy.doPromotion();
}
}
4)测试
public static void main(String[] args) {
PromotionActivity activity618 = new PromotionActivity(new CouponStrategy());
PromotionActivity activity1111 = new PromotionActivity(new CashbackStrategy());
activity618.execute();
activity1111.execute();
}
此时,小伙伴们会发现,如果把上面这段测试代码放到实际的业务场景其实并不实用。因为我们做活动时候往往是要根据不同的需求对促销策略进行动态选择的,并不会一次性执行多种优惠。所以,我们的代码通常会这样写:
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 类:
/**
* 促销策略工厂
*/
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";
}
}
这样,我们测试类就可以这样写了
public static void main(String[] args) {
String promotionKey = "GROUPBUY";
PromotionActivity promotionActivity = new
PromotionActivity(PromotionStrategyFactory.getPromotionStrategy(promotionKey));
promotionActivity.execute();
}
代码优化之后,是不是我们程序猿小哥哥的维护工作就轻松了?每次上新活动,不影响原来的代码逻辑。为了加深对策略模式的理解,我们再来举一个案例。相信小伙伴们都用过支付宝、微信支付、银联支付以及京东白条。一个常见的应用场景就是大家在下单支付时会提示选择支付方式,如果用户未选,系统也会默认好推荐的支付方式进行结算。
1)来看一下类图,下面我们用策略模式来模拟此业务场景:
2)创建 消息封装类MsgResult
/**
* 支付完成以后的状态
*/
public class MsgResult {
private int code;
private Object data;
private String msg;
public MsgResult(int code, String msg, Object data) {
this.code = code;
this.data = data;
this.msg = msg;
}
public String toString(){
return ("支付状态:[" + code + "]," + msg + ",交易详情:" + data);
}
}
3)创建 Payment 抽象类,定义支付规范和支付逻辑
/**
* 支付渠道
*/
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);
}
}
4)创建几种具体的支付方式阿里支付,京东白条,银联支付,微信支付
阿里支付宝AliPay
public class AliPay extends Payment {
public String getName() {
return "支付宝";
}
protected double queryBalance(String uid) {
return 900;
}
}
京东JDPay
public class JDPay extends Payment {
public String getName() {
return "京东白条";
}
protected double queryBalance(String uid) {
return 500;
}
}
银联支付
public class UnionPay extends Payment {
public String getName() {
return "银联支付";
}
protected double queryBalance(String uid) {
return 120;
}
}
微信支付
public class WechatPay extends Payment {
public String getName() {
return "微信支付";
}
protected double queryBalance(String uid) {
return 256;
}
}
5)创建支付策略管理类:
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 WECHAT_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(WECHAT_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);
}
}
6)创建订单Order
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 MsgResult pay(){
return pay(PayStrategy.DEFAULT_PAY);
}
public MsgResult pay(String payKey){
Payment payment = PayStrategy.get(payKey);
System.out.println("欢迎使用" + payment.getName());
System.out.println("本次交易金额为:" + amount + ",开始扣款...");
return payment.pay(uid,amount);
}
}
7)编写测试类
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));
}
}
8)运行结果
策略模式的优缺点
优点:
1、策略模式符合开闭原则。
2、避免使用多重条件转移语句,如 if...else...语句、switch 语句
3、使用策略模式可以提高算法的保密性和安全性。
缺点:
1、客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
2、代码中会产生非常多策略类,增加维护难度。