委派模式和策略模式
委派模式
什么是委派模式?
维基百科对委派模式的解释是:委派模式(delegation pattern)是软件设计模式中的一项基本技巧。在委派模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来代理。
委派模式的基本作用就是负责任务的调用和分配任务,跟代理模式很像,可以看做是一种特殊情况下的静态代理的全权代理,但是代理模式注重过程,而委派模式注重结果。委派模式在Spring中应用非常多,大家常用的DispatcherServlet其实就是用到了委派模式。现实生活中也常有委派的场景发生。例如:教室要进行大扫除,老师(Teacher)布置任务给班长(Monitor),班长再根据任务分配给学生(Student),我们以代码的形式进行模拟一下。
/**
* @author: Winston
* @createTime: 2021/6/24
*
* 老师
*/
public class Teacher {
/**
* 给班长下发命令
* @param command
* @param monitor
*/
public void command(String command, Monitor monitor){
monitor.doing(command);
}
}
/**
* @author: Winston
* @createTime: 2021/6/24
* 学生听指令的接口
*/
public interface IStudent {
/**
* 根据指令做事
* @param command
*/
void doing(String command);
}
/**
* @author: Winston
* @createTime: 2021/6/24
*
* 班长
*/
public class Monitor implements IStudent{
/**
* map用来存放接收指令的学生,key是指令 map 是学生
*/
private Map studentMap = new HashMap<>();
public Monitor(){
studentMap.put("扫地",new StudentA());
studentMap.put("擦黑板",new StudentB());
}
/**
* 班长不干活,根据指令给学生分派任务
* @param command
*/
@Override
public void doing(String command) {
studentMap.get(command).doing(command);
}
}
/**
* @author: Winston
* @createTime: 2021/6/24
*/
public class StudentA implements IStudent {
@Override
public void doing(String command) {
System.out.println("我是StudentA,我大扫除的职责是,"+command);
}
}
/**
* @author: Winston
* @createTime: 2021/6/24
*/
public class StudentB implements IStudent {
@Override
public void doing(String command) {
System.out.println("我是StudentB,我大扫除的职责是,"+command);
}
}
测试类
/**
* @author: Winston
* @createTime: 2021/6/24
*/
public class DelegateTest {
public static void main(String[] args) {
//客户请求(Teacher)、委派者(Monitor)、被被委派者(Student)
//委派者要持有被委派者的引用
//代理模式注重的是过程, 委派模式注重的是结果
//策略模式注重是可扩展(外部扩展),委派模式注重内部的灵活和复用
//委派的核心:就是分发、调度、派遣
//委派模式:就是静态代理和策略模式一种特殊的组合
new Teacher().command("扫地", new Monitor());
}
}
运行结果:我是StudentA,我大扫除的职责是,扫地
委派模式在SpringMVC源码中的体现
下面我们来简单模拟一下SpringMVC的DispatcherServlet是怎么实现委派模式的。先创建几个Controller
/**
* @author: Winston
* @createTime: 2021/6/24
*/
public class UserController {
public void findUserById(String id){
}
}
/**
* @author: Winston
* @createTime: 2021/6/24
*/
public class OrderController {
public void findOrderById(String id){
}
}
/**
* @author: Winston
* @createTime: 2021/6/24
*/
public class IndexController {
public void index(){
}
}
创建一个DispatcherServlet用于分发请求
/**
* 这个类就相当于是班长角色
* @author: Winston
* @createTime: 2021/6/24
*/
public class DispatcherServlet extends HttpServlet {
private void doDispatcher(HttpServletRequest request, HttpServletResponse response){
String requestURI = request.getRequestURI();
String id = request.getParameter("id");
// 根据uri判断分发请求到Controller
if("findUserById".equals(requestURI)){
new UserController().findUserById(id);
}else if("findOrderById".equals(requestURI)){
new OrderController().findOrderById(id);
}else if("index".equals(requestURI)){
new IndexController().index();
}else {
try {
response.getWriter().write("404 NOT FOUND");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
配置web.xml文件
Gupao Web Application
delegateServlet
com.gupaoedu.vip.pattern.delegate.mvc.DispatcherServlet
1
delegateServlet
/*
小结
一个模拟委派模式的简单DispatcherServlet就写完了。Spring中有许多地方都用到了委派模式,我们可以通过命名来识别,只要是以Delegate结尾的都是事先了委派模式。例如:BeanDefinitionParserDelegate 根据不同类型委派不同逻辑解析BeanDefinition。
策略模式
策略模式概念
首先了解一下策略模式的概念,在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
策略模式的应用场景
1、假如系统中有很多类,而他们的区别仅仅在于他们的行为不同。
2、一个系统需要动态地在几种算法中选择一种。
案例一
上面对策略模式的介绍都比较抽象不太好理解,举个生活中的小例子,在我们进行网上购物时如果遇到一些活动商家会给商品以某些形式降价,比如优惠券、拼团、返现。下面我们通过用代码来模拟一下。
首先创建一个促销活动Promotion接口,所有的降价形式都要实现这个接口。
/**
* @author: Winston
* @createTime: 2021/6/25
*
* 促销活动
*/
public interface Promotion {
/**
* 活动内容
*/
void activity();
}
无优惠活动的 EmptyActivity类
public class EmptyActivity implements Promotion{
@Override
public void activity() {
System.out.println("无促销活动");
}
}
优惠券活动
public class CouponActivity implements Promotion {
@Override
public void activity() {
System.out.println("优惠券立减20");
}
}
返现活动
public class CashbackActivity implements Promotion {
@Override
public void activity() {
System.out.println("购买立马返现15");
}
}
组团活动
public class GroupActivity implements Promotion {
@Override
public void activity() {
System.out.println("两人拼团立减25");
}
}
促销方案类PromotionActivity
/**
* @author: Winston
* @createTime: 2021/6/25
*
* 举办的促销活动
*/
public class PromotionActivity {
private Promotion promotion;
public PromotionActivity(Promotion promotion) {
this.promotion = promotion;
}
public void execute(){
promotion.activity();
}
}
测试类:
public class PromotionTest {
public static void main(String[] args) {
// 不同活动对应不同的优惠策略,默认策略是无优惠
PromotionActivity promotionActivity = null;
String promotionKey = "anniversary";
if ("doubleEleven".equals(promotionKey)) {
// 双十一活动,优惠券
promotionActivity = new PromotionActivity(new CouponActivity());
} else if ("activity618".equals(promotionKey)) {
// 618,返现
promotionActivity = new PromotionActivity((new CashbackActivity()));
} else if ("anniversary".equals(promotionKey)) {
// 店庆,组团
promotionActivity = new PromotionActivity((new GroupActivity()));
} else {
promotionActivity = new PromotionActivity(new EmptyActivity());
}
promotionActivity.execute();
}
}
从上面的测试代码可以看出,如果又增加了一个促销活动,那么我们就又要加上新活动,增加if else,相当于又修改了业务代码,那么又要进行测试,如此一来工作量难免是重复且耗时的,下面我们结合工厂模式和单例模式对其进行改造,在这里说一下,设计模式一般都不是单独出现的,而是结合使用。
创建一个存放活动策略key的枚举,PromotionEnum
/**
* @author: Winston
* @createTime: 2021/6/25|
*
* 促销活动策略的enum
*/
public enum PromotionEnum {
COUPON_ACTIVITY("优惠券活动","COUPON_ACTIVITY"),
CASHBACK_ACTIVITY("返现活动","CASHBACK_ACTIVITY"),
GROUP_ACTIVITY("组团活动","GROUP_ACTIVITY")
;
/**
* 活动名称
*/
private String activityName;
/**
* 活动key
*/
private String promotionKey;
PromotionEnum(String activityName, String promotionKey) {
this.activityName = activityName;
this.promotionKey = promotionKey;
}
public String getActivityName() {
return activityName;
}
public void setActivityName(String activityName) {
this.activityName = activityName;
}
public String getPromotionKey() {
return promotionKey;
}
public void setPromotionKey(String promotionKey) {
this.promotionKey = promotionKey;
}
}
创建促销策略工厂PromotionFactory
/**
* @author: Winston
* @createTime: 2021/6/25
* 促销策略工厂
*/
public class PromotionFactory {
/**
* 活动
*/
private static Promotion promotion;
/**
* 存放活动策略的map,Key是活动名称,value是具体活动
*/
private static Map promotionMap = new HashMap<>();
/**
* 默认策略,无优惠
*/
private static final EmptyActivity emptyActivity = new EmptyActivity();
static {
promotionMap.put(PromotionEnum.COUPON_ACTIVITY.getPromotionKey(), new CouponActivity());
promotionMap.put(PromotionEnum.CASHBACK_ACTIVITY.getPromotionKey(), new CashbackActivity());
promotionMap.put(PromotionEnum.GROUP_ACTIVITY.getPromotionKey(), new GroupActivity());
}
/**
* 根据活动key拿到对应的活动
* @param key
* @return
*/
public static Promotion getPromotionByKey(String key) {
promotion = promotionMap.get(key);
return promotion == null ? emptyActivity : promotion;
}
}
测试类:
public class OptimizePromotionTest {
public static void main(String[] args) {
Promotion promotion = PromotionFactory.getPromotionByKey(PromotionEnum.COUPON_ACTIVITY.getPromotionKey());
promotion.activity();
}
}
案例二
通过第一个案例,我们对策略模式有了一点了解,下面再举一个例子来加深对策略模式的印象。在我们点外卖进行付款时,往往有多种支付方式,比如微信支付、支付宝支付、银行卡支付、QQ支付等,这些支付方式的选择其实也是策略模式的一种体现,下面我们通过代码来进行模拟。
创建一个Payment抽象类,定义支付规范和支付逻辑:
/**
* @author: Winston
* @createTime: 2021/6/25
*
* 支付工具中所具有的方法
*/
public abstract class Payment {
/**
* 支付类型
*
* @return
*/
public abstract String getName();
/**
* 余额查询
*/
protected abstract Double queryBalance();
/**
* 支付方法
*
* @param amount 支付金额
* @param balance 余额
*/
public PayState pay(Double amount, Double balance) {
if (balance >= amount) {
return new PayState(200, "支付成功");
} else {
return new PayState(500, "支付失败,余额不足");
}
}
}
创建几种支付方式,微信支付、支付宝支付、qq支付、银行卡支付
/**
* @author: Winston
* @createTime: 2021/6/25
*/
public class WechatPay extends Payment {
@Override
public String getName() {
return "微信支付";
}
@Override
protected Double queryBalance() {
return 14.43D;
}
}
/**
* @author: Winston
* @createTime: 2021/6/25
*/
public class AliPay extends Payment {
@Override
public String getName() {
return "支付宝支付";
}
@Override
protected Double queryBalance() {
return 561.24D;
}
}
/**
* @author: Winston
* @createTime: 2021/6/25
*/
public class QqPay extends Payment {
@Override
public String getName() {
return "qq支付";
}
@Override
protected Double queryBalance() {
return 5.43D;
}
}
/**
* @author: Winston
* @createTime: 2021/6/25
*/
public class CardPay extends Payment {
@Override
public String getName() {
return "银行卡支付";
}
@Override
protected Double queryBalance() {
return 20051.15D;
}
}
创建一个支付状态的包装类PayState
/**
* @author: Winston
* @createTime: 2021/6/25
*/
public class PayState {
/**
* 状态码
*/
private int code;
/**
* 状态信息
*/
private String msg;
public PayState(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
@Override
public String toString() {
return "PayState{" +
"code=" + code +
", msg='" + msg + '\'' +
'}';
}
}
创建支付策略管理类:
/**
* @author: Winston
* @createTime: 2021/6/25
* 支付策略管理
*/
public class PayStrategy {
public static final String ALI_PAY= "ALI_PAY";
public static final String WECHAT_PAY= "WECHAT_PAY";
public static final String CARD_PAY= "CARD_PAY";
public static final String QQ_PAY= "QQ_PAY";
public static final String DEFAULT_PAY= ALI_PAY;
// 存放支付策略的map
private static Map paymentMap = new HashMap<>();
static {
paymentMap.put(ALI_PAY,new AliPay());
paymentMap.put(WECHAT_PAY,new WechatPay());
paymentMap.put(CARD_PAY,new CardPay());
paymentMap.put(QQ_PAY,new QqPay());
}
public static Payment get(String payKey){
if(!paymentMap.containsKey(payKey)){
return paymentMap.get(DEFAULT_PAY);
}
return paymentMap.get(payKey);
}
}
创建订单类,模拟下单支付时的操作
/**
* @author: Winston
* @createTime: 2021/6/25
* 订单类
*/
public class Order {
/**
* 订单id
*/
private String id;
/**
* 订单金额
*/
private Double amount;
public Order(String id, Double amount) {
this.id = id;
this.amount = amount;
}
public void pay() {
pay(PayStrategy.DEFAULT_PAY);
}
/**
* 使用指定支付方式支付
*
* @param payKey
*/
public void pay(String payKey) {
System.out.println("订单编号是:" + this.id);
System.out.println("支付金额为:" + this.amount);
Payment payment = PayStrategy.get(payKey);
System.out.println("支付方式:" + payment.getName());
PayState pay = payment.pay(this.amount, payment.queryBalance());
System.out.println(pay);
}
}
测试类:
public class PaymentTest {
public static void main(String[] args) {
System.out.println("开始下单点外卖:");
Order order = new Order("10001", 21.31D);
order.pay(PayStrategy.WECHAT_PAY);
}
}
运行结果:
开始下单点外卖:
订单编号是:10001
支付金额为:21.31
支付方式:微信支付
PayState{code=500, msg='支付失败,余额不足'}
策略模式在JDK源码中的体现
Comparator接口中的compare()方法,就是一个策略抽象实现。
int compare(T o1, T o2);
Comparator 抽象下面有非常多的实现类,我们经常会把 Comparator 作为参数传入作
为排序策略,例如 Arrays 类的 parallelSort 方法等:
public class Arrays {
...
public static void parallelSort(T[] a, int fromIndex, int toIndex,
Comparator cmp) {
...
}
...
}
还有TreeMap的构造方法:
public class TreeMap
extends AbstractMap
implements NavigableMap, Cloneable, java.io.Serializable
{
...
public TreeMap(Comparator comparator) {
this.comparator = comparator;
}
...
}
策略模式的优缺点
优点:
- 策略模式符合开闭原则
- 避免使用多重条件转移语句,如if...else...、switch
- 使用策略模式可以提高算法的保密性和安全性
缺点:
- 客户端必须知道所有的策略,并且自行决定使用哪一个策略类
- 代码中会产生非常多的策略类,增加维护难度
委派模式与策略模式综合应用
在委派模式中我们写了一个例子模仿了SpringMVC的DispatcherServlet,在那个例子中我们用了很多的if...else语句,在实际项目中Controller数量往往很大,如果都用if..else来写的话,那么不仅工作量大,而且出错的可能性也很大,下面我们使用委派模式和策略模式将它进行改造。
创建一个Handler类,用于存Method和url对应的关系
/**
* @author: Winston
* @createTime: 2021/6/25
*
* 保存Controller的一些信息,将url和method的关系绑定
*/
public class Handler {
/**
* controller的实例
*/
private Object controller;
/**
* 存放方法
*/
private Method method;
/**
* 对应映射方法的url
*/
private String url;
public Handler(Object controller, Method method, String url) {
this.controller = controller;
this.method = method;
this.url = url;
}
public Object getController() {
return controller;
}
public void setController(Object controller) {
this.controller = controller;
}
public Method getMethod() {
return method;
}
public void setMethod(Method method) {
this.method = method;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
之前写过的三个controller
public class UserController {
public void findUserById(String id){
}
}
public class OrderController {
public void findOrderById(String id){
}
}
public class IndexController {
public void index(){
}
}
改造的servlet,重写init方法用于模拟在启动时将各个controller信息保存到Handler中去,代码如下:
/**
* 这个类就相当于是班长角色
*
* @author: Winston
* @createTime: 2021/6/24
*/
public class DispatcherServlet extends HttpServlet {
// 初始化所有controller的信息存到handlerList中
public static List handlerList = new ArrayList<>();
/**
* 模拟controller初始化时将信息存到handlerList中
*/
@Override
public void init() throws ServletException {
Class userControllerClass = UserController.class;
Class orderControllerClass = OrderController.class;
Class indexControllerClass = IndexController.class;
try {
Handler userHandler = new Handler(userControllerClass.newInstance(),
userControllerClass.getMethod("findUserById", new Class[]{String.class}), "/web/findUserById");
Handler orderHandler = new Handler(orderControllerClass.newInstance(),
orderControllerClass.getMethod("findOrderById", new Class[]{String.class}), "/web/findOrderById");
Handler indexHandler = new Handler(indexControllerClass.newInstance(),
indexControllerClass.getMethod("index", null), "/web/index");
// 将handler添加到list中
handlerList.add(userHandler);
handlerList.add(orderHandler);
handlerList.add(indexHandler);
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doDispatcher(req, resp);
}
private void doDispatcher(HttpServletRequest request, HttpServletResponse response) {
// 获取url
String requestURI = request.getRequestURI();
// 要执行的handler
Handler handler = null;
// 根据url获取handler
for (Handler h : handlerList) {
if(h.getUrl().equals(requestURI)){
handler = h;
break;
}
}
if(handler == null){
try {
response.getWriter().write("Not found 404!");
} catch (IOException e) {
e.printStackTrace();
}
return;
}
// 执行方法的结果
Object result = null;
try {
// 根据获取到的执行器利用反射执行方法,实际没那么简单,这里简单模拟不用深究
result = handler.getMethod().invoke(handler.getController(),request.getParameter("id"));
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
// 将结果返回
try {
response.getWriter().write(result.toString());
} catch (IOException e) {
e.printStackTrace();
}
}
}