委派模式和策略模式
委派模式
概述
委派模式(Delegate Pattern)的基本目的就是负责任务的调用和分配,和代理模式很像,可以看成是一个特殊的静态代理的全权代理,但是代理模式注重过程,委派模式注重结果。委派模式不是GOF23种设计模式。现实生活中也时常有委派的场景发生,比如老板(Boss)下达命令,部门经理(Manager)把具体任务分配给员工(Employee),待员工完成工作再由经理汇报进度和情况给老板。我们用代码模拟一下这个场景
员工接口
public interface IEmployee { void doing(String command); }
员工A
public class EmployeeA implements IEmployee { @Override public void doing(String command) { System.out.println("我是员工A,我正在做"+ command); } }
员工B
public class EmployeeB implements IEmployee { @Override public void doing(String command) { System.out.println("我是员工B,我正在做"+ command); } }
经理分发任务给员工
public class Manager { private Map<String,IEmployee> map = new HashMap<String, IEmployee>(); public Manager() { map.put("登录",new EmployeeA()); map.put("加密",new EmployeeB()); } void doing(String command) { IEmployee employee = map.get(command); employee.doing(command); } }
老板下达命令
public class Boss { void command(String command, Manager manager) { manager.doing(command); } }
可以看出经理分配任务生动体现了委派模式
下面我们来看一下在SpringMVC的DispatcherServlet中如何体现委派模式
业务类MemberController
public class MemberController { public void getMemberById(String mid) { } }
OrderController
public class OrderController { public void getOrderById(String oid) { } }
SystemController
public class SystemController { public void login() { } }
DispatcherServlet
public class DispatcherServlet extends HttpServlet {
private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String uri = req.getRequestURI();
String id = req.getParameter("id");
if("getMemberById".equals(uri))
{
new MemberController().getMemberById(id);
}
else if ("getOrderById".equals(uri))
{
new OrderController().getOrderById(id);
}
else if("login".equals(uri))
{
new SystemController().login();
}
else
{
resp.getWriter().write("404 Not Found!!");
}
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doDispatch(req,resp);
}
}
web.xml
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app> <display-name>Archetype Created Web Application</display-name> <servlet> <servlet-name>delegateServlet</servlet-name> <servlet-class>com.stu.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>
一个完整的委派模式就实现出来了
策略模式
策略模式(Strategy Pattern)定义了一系列算法,并且把每一个算法封装起来,使得每一个算法可以相互替代,把算法策略与客户端分离开来相互独立。
举个例子:去商场买东西碰到促销活动,那么活动有很多种比如优惠券抵扣活动、返现活动、拼团活动,下面是不使用策略模式的代码实现
促销管理类
public class PromotionManager { public void doPromotion(String sType) { if("coupon".equals(sType)) { System.out.println("领取优惠券直接抵扣"); } else if ("cashBack".equals(sType)) { System.out.println("返现促销,返现金额到支付宝账号"); } else if ("groupBuy".equals(sType)) { System.out.println("团购促销,满20人参加团购活动"); } } }
客户端调用
public class Test { public static void main(String[] args) { PromotionManager promotionManager = new PromotionManager(); promotionManager.doPromotion("groupBuy"); } }
测试发现上面的代码可以实现这样一个场景功能,可是代码是有问题的,我们发现促销管理类太过庞大和臃肿,当我们如果有一个别的促销增加时要修改促销管理类违背开闭原则
使用策略模式实现上面的促销
促销策略接口
public interface IPromotionStrategy { void doPromotion(); }
优惠券促销策略
public class CouponStrategy implements IPromotionStrategy { @Override public void doPromotion() { System.out.println("领取优惠券直接抵扣"); } }
返现促销策略
public class CashBackStrategy implements IPromotionStrategy { @Override public void doPromotion() { System.out.println("返现促销,返现金额到支付宝账号"); } }
团购优惠策略
public class GroupBuyStrategy implements IPromotionStrategy { @Override public void doPromotion() { System.out.println("团购促销,满20人参加团购活动"); } }
促销活动方案接收具体策略
public class PromotionActivity { private IPromotionStrategy promotionStrategy; public PromotionActivity(IPromotionStrategy promotionStrategy) { this.promotionStrategy = promotionStrategy; } public void doPromotion() { this.promotionStrategy.doPromotion(); } }
测试
public class PromotionStrategyTest { public static void main(String[] args) { PromotionActivity promotionActivity = new PromotionActivity(new CashBackStrategy()); promotionActivity.doPromotion(); } }
可以发现我们客户端需要确定使用哪一个算法即可调用促销活动具体的策略,当我们策略新增时我们只需要增加策略类即可比如我们现在要有一个打折策略
public class DiscountStrategy implements IPromotionStrategy { @Override public void doPromotion() { System.out.println("打折活动,折扣8折"); } }
我们无需修改已经写好的策略,只需要新增类就好
策略模式理解
策略模式的作用:把具体的算法实现从业务逻辑中剥离出来,成为一系列独立算法类,使得它们可以相互替换。
策略模式的着重点:不是如何来实现算法,而是如何组织和调用这些算法,从而让我们的程序结构更加的灵活、可扩展。
可以发现我们不用策略模式会有很多的if和else if,这些逻辑其实是对立的是平等的每次只会走一个逻辑,这与策略模式不谋而合
策略模式的应用场景
- 系统中有很多类,他们的区别仅仅是行为不同
- 一个系统需要动态的在几种算法中选择一个
为了加深对策略模式的印象,我们在举个例子,我们都用过支付宝、微信支付、银联支付和京东支付。一个常见的应用场景就是大家在下单支付时会选择支付方式,如果用户未选择则会有一个默认的支付方式
抽象类支付策略
public abstract class Payment { //支付类型 public abstract String getName(); //查询余额 public 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); } }
返回结果类
public class MsgResult { private int code; private Object data; private String msg; public MsgResult(int code, Object data, String msg) { this.code = code; this.data = data; this.msg = msg; } @Override public String toString() { return "支付状态:["+ code +"]," + msg + ",交易详情:"+data; } }
策略子类-Alipay
public class AliPay extends Payment { @Override public String getName() { return "支付宝"; } @Override public double queryBalance(String uid) { return 500; } }
策略子类-JDPay
public class JDPay extends Payment { @Override public String getName() { return "京东白条"; } @Override public double queryBalance(String uid) { return 500; } }
策略子类-UnionPay
public class UnionPay extends Payment { @Override public String getName() { return "银联支付"; } @Override public double queryBalance(String uid) { return 2000; } }
策略子类-Alipay
public class WeChatPay extends Payment { @Override public String getName() { return "微信支付"; } @Override public double queryBalance(String uid) { return 1000; } }
管理策略的类
public class PayStrategyManager { 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 UNION_PAY = "UnionPay"; public static final String DEFAULT_PAY = ALI_PAY; private static Map<String,Payment> payStrategies = new HashMap<>(); static { payStrategies.put(ALI_PAY,new AliPay()); payStrategies.put(JD_PAY,new JDPay()); payStrategies.put(WECHAT_PAY,new WeChatPay()); payStrategies.put(UNION_PAY,new UnionPay()); } public static Payment get(String key) { if (!payStrategies.containsKey(key)) return payStrategies.get(DEFAULT_PAY); return payStrategies.get(key); } }
订单类
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; } public MsgResult pay(String key) { Payment payment= PayStrategyManager.get(key); System.out.println("欢迎使用" + payment.getName()); System.out.println("本次交易金额是:" + amount + "开始扣款"); return payment.pay(uid,amount); } public MsgResult pay() { return pay(PayStrategyManager.DEFAULT_PAY); } }
客户端测试
public class PayTest { public static void main(String[] args) { Order order = new Order("1","201801020558",500); System.out.println(order.pay(PayStrategyManager.ALI_PAY)); } }
测试结果
看下类图
通过不同的支付策略实现不同方式的支付
策略在JDK中的使用
说一个比较常用的比较器接口Comparator,我们常用的compare()方法
public interface Comparator<T> { int compare(T o1, T o2); ... }
Comparator 抽象下面有非常多的实现类,我们经常会把 Comparator 作为参数传入作为排序策略,例如 Arrays 类的 parallelSort 方法等:
public static <T> void parallelSort(T[] a, int fromIndex, int toIndex, Comparator<? super T> cmp) { .... }
传入不同的排序策略,得到不同的结果
策略模式的优缺点
优点
- 符合开闭原则
- 避免使用多重if、else语句和switch
- 提高算法的保密性和安全性
缺点
- 客户端必须知道所有的策略,自己决定使用哪一种策略
- 代码中会出现很多的策略类,增加系统维护难度