16.策略模式
16.1.课程目标
1、 掌握策略模式和责任链模式的应用场景;
2、 通过学习策略模式来消除程序中大量的if...else...和 switch语 句 ;
3、 掌握策略模式和委派模式的结合使用;
4、 深刻理解责任链模式和建造者模式的结合应用
16.2.内容定位
1 、已经掌握建造者模式和委质模式的人群。
2、希望通过对策略模式的学习,来消除程序中大量的冗余代码和多重条件转移语句的人群。
3、希望通过学习责任链模式宜构校验逻辑的人群。
16.3.定义
策略模式 (Strategy Pattern) 又叫也叫政策摆式 (Policy Pattern) , 它是将定义的算法家族、分 别封装起来,让它们之间可以互相替换,从而让算法的变化不会影响到使用算法的用户。属千行为型模式。
原 文: Define a family of algorithms,encapsulate each one,and make them interchangeable.
策略摆式使用的就是面向对象的继承和多态机制,从而实现同—行为在不同场萦下具备不同实现。
16.4.应用场景
策略模式在生活场景中应用也非常多。比如—个入的交税比率与他的工资有关,不同的工资水平对 应不同的税率。再比如我们在互联网移动支付的大背景下,每次下单后付款前,需要选择支付方式。
![b0f09cba95892698a33ed40581c8030a.png](https://img-blog.csdnimg.cn/img_convert/b0f09cba95892698a33ed40581c8030a.png)
策略模式可以解决在有多种算法相似的情况下,使用 if…else 或 switch... case 所带来的复杂性和臃肿性。在日常业务开发中,策略模式适用于以下场景 :
1 、针对同—类型问题,有多种处理方式,每—种都能独立解决问题 ;
2、 算法需要自由切换的场果 ;
3、需要屏蔽算法规则的场果。
首先来看下策略模式的通用 UML 类图 :
![90e5d0c532246e00c7ff9f0d57c58b8f.png](https://img-blog.csdnimg.cn/img_convert/90e5d0c532246e00c7ff9f0d57c58b8f.png)
从 UML 类图中,我们可以看到,策略摆式主要包含三种角色 :
上下文角色 (Context) : 用来操作策略的上下文环境,屏蔽高层模块(客户端)对策略, 算法的 直接访问,封装可能存在的变化 ;
抽象策略角色 (Strategy) : 规定策略或算法的行为 ;
具体策略角色 (ConcreteStrategy) : 具体的策略或算法实现。
注意: 策略模式中的上下文环境 (Context) , 其职责本来是隔离客户端与策略类的耦合, 让客户端完全与上下文环境 沟通, 无需关系具体策略。
16.5.促销优惠业务场景
优惠策略会有很多种可能如 : 领取优惠券抵扣、返现促销、拼团优惠。下面我们用代码来模拟,
首先我们创建—个促销策略的抽象 PromotionStrategy :
/** * 促销策略抽象 */ public interface IPromotionStrategy { void doPromotion(); }
然后分别创建优惠券抵扣策略 CouponStrategy 类、返现促销策略 CashbackStrategy 类、拼团优惠策略 GroupbuyStrategy 类和无优惠策略 EmptyStrategy 类 :
public class CouponStrategy implements IPromotionStrategy { public void doPromotion() { System.out.println("使用优惠券抵扣"); } }
CashbackStrategy 类 :
public class CashbackStrategy implements IPromotionStrategy { public void doPromotion() { System.out.println("返现,直接打款到支付宝账号"); } }
GroupbuyStrategy 类 :
public class GroupbuyStrategy implements IPromotionStrategy { public void doPromotion() { System.out.println("5人成团,可以优惠"); } }
EmptyStrategy 类 :
public class EmptyStrategy implements IPromotionStrategy { public void doPromotion() { System.out.println("无优惠"); } }
然后创建促销活动方案 PromotionActivity 类 :
public class PromotionActivity { private IPromotionStrategy strategy; public PromotionActivity(IPromotionStrategy strategy) { this.strategy = strategy; } public void execute(){ strategy.doPromotion(); } }
编写客户端测试类 :
public class Test { public static void main(String[] args) { PromotionActivity activity618 = new PromotionActivity(new CouponStrategy()); PromotionActivity activityllll = new PromotionActivity(new CashbackStrategy()); activity618.execute(); activityllll.execute(); } }
运行效果如下:
使用优惠券抵扣 返现,直接打款到支付宝账号
此时,小伙伴们会发现,如果把上面这段则试代码放到实际的业务场景其实并不实用。因为我们做活动时候往往是要根据不同的需求对促销策略进行动态选择的,并不会一次性执行多种优惠。所以,我 们的代码通常会这样写 :
public class Test { 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 PromotionStrategyFacory { private static final IPromotionStrategy EMPTY = new EmptyStrategy(); private static Map PROMOTIONS = new HashMap(); static { PROMOTIONS.put(PromotionKey.COUPON, new CouponStrategy()); PROMOTIONS.put(PromotionKey.CASHBACK, new CashbackStrategy()); PROMOTIONS.put(PromotionKey.GROUPBUY, new GroupbuyStrategy()); } private PromotionStrategyFacory() { } public static IPromotionStrategy getPromotionStrategy(String promotionKey) { IPromotionStrategy strategy = PROMOTIONS.get(promotionKey); return strategy == null ? EMPTY : strategy; } public static Set getPromotionKeys() { return PROMOTIONS.keySet(); } private interface PromotionKey { String COUPON = "COUPON"; String CASHBACK = "CASHBACK"; String GROUPBUY = "GROUPBUY"; } }
这时候我们客户端代码就应该这样写了 :
public class Test { public static void main(String[] args) { PromotionActivity activity618 = new PromotionActivity(new CouponStrategy()); PromotionActivity activityllll = new PromotionActivity(new CashbackStrategy()); activity618.execute(); activityllll.execute(); } }
运行效果如下:
使用优惠券抵扣
代码优化之后 , 是不是我们程序猿小哥哥的维护工作就轻松了?每次上新活动,不影响原来的代码 逻辑。
16.6.用策略模式实现选择支付方式的业务场景
为了加深对策略模式的理解,我们再来举一个案例。 相信小伙伴们都用过支付宝、微信支付、银联 支付以及京东白条。 —个常见的应用场景就是大家在下单支付时会提示选择支付方式,如果用户未选, 系统也会默认好推荐的支付方式进行结算。来看一下类图,下面我们用策略模式来描拟此业务场罢 :
![fd9091513356ef12ce2c26c315ad0e08.png](https://img-blog.csdnimg.cn/img_convert/fd9091513356ef12ce2c26c315ad0e08.png)
创建 Payment 抽象类,定义支付规范和支付逻辑,代码如下 :
public abstract class Payment { public abstract String getName(); //通用逻辑放到抽象类里面实现 public MsgResult pay(String uid, double amount) { //余额是否足够 if (queryBalance(uid) < amount) { return new MsgResult(500, "支付失败", "余额不足"); } return new MsgResult(200, "支付成功", "支付金额" + amount); } protected abstract double queryBalance(String uid); }
分别创建具体的支付方式,支付宝 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; } }
微信支付 WechatPay类 :
public class WechatPay extends Payment { public String getName() { return "微信支付"; } protected double queryBalance(String uid) { return 263; } }
银联支付 UnionPay 类 :
public class UnionPay extends Payment { public String getName() { return "银联支付"; } protected double queryBalance(String uid) { return 120; } }
创建支付状态的包装类 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; } @Override public String toString() { return "MsgResult{" + "code=" + code + ", data=" + data + ", msg='" + msg + ''' + '}'; } }
创建支付策略管理类 :
public class PayStrategy { 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 strategy = new HashMap(); static { strategy.put(ALI_PAY,new AliPay()); strategy.put(JD_PAY,new JDPay()); strategy.put(WECHAT_PAY,new WechatPay()); strategy.put(UNION_PAY,new UnionPay()); } public static Payment get(String payKey){ if(!strategy.containsKey(payKey)){ return strategy.get(DEFAULT_PAY); } return strategy.get(payKey); } }
创建订单 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; } 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); } }
测试代码 :
public class Test { public static void main(String[] args) { Order order = new Order("1","2020031401000323",324.5); System.out.println(order.pay(PayStrategy.UNION_PAY)); } }
运行结果 :
欢迎使用银联支付 本次交易金额为324.5,开始扣款 MsgResult{code=500, data=余额不足, msg='支付失败'}
希望通过大家耳熟能详的业务场景来举例,让小伙伴们更深刻地理解策略模式。希望小伙伴们在面 试和工作体现出自己的优势。
16.7.策略模式在框架源码中的体现
首先来看JDK 中—个比较常用的比较器 Comparator 接口,我们看到的—个大家常用的 compare() 方法,就是一个策略抽象实现 :
public interface Comparator { int compare(T o1, T o2); }
Comparator抽象下面有非常多的实现类,我们经常会把 Comparator作为参数传入作为排序策略, 例如 Arrays 类的 parallelSort 方法等 :
public class Arrays { public static void parallelSort(byte[] a) { int n = a.length, p, g; if (n <= MIN_ARRAY_SORT_GRAN || (p = ForkJoinPool.getCommonPoolParallelism()) == 1) DualPivotQuicksort.sort(a, 0, n - 1); else new ArraysParallelSortHelpers.FJByte.Sorter (null, a, new byte[n], 0, n, 0, ((g = n / (p << 2)) <= MIN_ARRAY_SORT_GRAN) ? MIN_ARRAY_SORT_GRAN : g).invoke(); } }
还有 TreeMap 的构造方法:
public class TreeMap extends AbstractMap implements NavigableMap, Cloneable, java.io.Serializable { public TreeMap(Comparator super K> comparator) { this.comparator = comparator; } }
这就是 Comparator 在JDK原码中的应用。那我们来看策略模式在 Spring源码中的应用,来看 Resource 类 :
public interface Resource extends InputStreamSource { boolean exists(); default boolean isReadable() { return true; } default boolean isOpen() { return false; } default boolean isFile() { return false; } URL getURL() throws IOException; URI getURI() throws IOException; File getFile() throws IOException; default ReadableByteChannel readableChannel() throws IOException { return Channels.newChannel(this.getInputStream()); } long contentLength() throws IOException; long lastModified() throws IOException; Resource createRelative(String var1) throws IOException; @Nullable String getFilename(); String getDescription(); }
我们虽然没有直接使用 Resource 类,但是我们经常使用它的子类,例如 :
/* * @see WritableResource * @see ContextResource * @see UrlResource * @see ClassPathResource * @see FileSystemResource * @see PathResource * @see ByteArrayResource * @see InputStreamResource */
还有一个非常典型的场罢, Spring 的初始化也采用了策略模式,不同的类型的类采用不同的初始化 策略。首先有一个 lnstantiationStrategy 接口,我们来看一下涌码 :
public interface InstantiationStrategy { Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner) throws BeansException; Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner, Constructor> ctor, @Nullable Object... args) throws BeansException; Object instantiate(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner, @Nullable Object factoryBean, Method factoryMethod, @Nullable Object... args) throws BeansException; }
顶层的策略抽象非常简单,但是它下面有两种策略 SimplelnstantiationStrategy 和 CgIibSubclassingInsta ntiationStrategy , 我们看—下类图 :
![e586549f26360bf4faf54d1edbcf1461.png](https://img-blog.csdnimg.cn/img_convert/e586549f26360bf4faf54d1edbcf1461.png)
打开类图我们还发现 CgIibSubclassingInstantiationStrategy 策略类还继承了 SimplelnstantiationStrategy 类,说明在实际应用中多种策略之间还可以继承使用。小伙们可以作为 —个参考,在实际业务场萦中,可以根据需要来设计。
16.8.策略模式的优缺点
优点 :
1 、策略模式符合开闭原则。
2、避免使用多重条件转移语句,如 if…else.. 语句、 switch 语句
3、使用策略模式可以提高算法的保密性和安全性。
缺点 :
1 、客户端必须知道所有的策略,并且自行决定使用哪一个策略类。
2、代码中会产生非常多策略类,增加维护难度。
17.责任链模式
责任链模式 (Chain of Responsibility Pattern) 是将链中每一个节点看作是一个对象,每个节点处理 的清求均不同,且内部自动维护—个下—节点对象。当—个清求从链式的首端发出时,会沿看链的路径 依次传递给每—个节点对象,直至有对象处理这个清求为止。属千行为型模式。
原文:Avoid coupling the sender of a request to its receiver by giving more than one object a chance to hand I e the request. Chain the receiving objects and pass the request along the chain until an object handles it.
解释:使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链, 并沿着这条链传递该请求,直到有一个对象处理它为止。
17.1.责任链楼式的应用场景
在日常生活中责任链梩式还是比较常见的,我们平时工作处理一些事务,往往是各部门协同合作的 完成莘—个任务。而每个部门都有各自的职责,因此,很多时候事谓完成—半,便会转交给下—个部门, 直到所有部门都过一渔之后事谓才能完成。还有我们平时俗话说的过五关、斩六将其实也是一种责任链。
![3f7ace6f8e8c08921fa94b90ae9159e6.png](https://img-blog.csdnimg.cn/img_convert/3f7ace6f8e8c08921fa94b90ae9159e6.png)
责任链模式主要是解耦了请求与处理,客户只需将清求发送到链上即可,无需关心请求的具体内容 和处理细节,请求会自动进行传递直至有节点对象进行处理。
适用于以下应用场景 :
1 、多个对象可以处理同一清求,但具体由哪个对象处理则在运行时动态决定;
2、在不明确指定接收者的情况下,向多个对象中的—个提交—个请求;
3、可动态指定一组对象处理请求。
首先来看下责任链模式的通用 UML 类图 :
![491f41a10d43914fbb273e7efa88608b.png](https://img-blog.csdnimg.cn/img_convert/491f41a10d43914fbb273e7efa88608b.png)
从 UML类图中,我们可以看到,责任链模式主要包含两种角色:
抽象处理者(Handler):定义一个请求处理的方法,并维护一个下一个处理节点Handler对象的 引用;
具体处理者(ConcreteHandler):对请求进行处理,如果不感兴趣,则进行转发。
责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应 当理解的是其模式(道 )而不是其具体实现(术 ),责任链模式的独到之处是其将节点处理者组合成了 链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动了起来。
17.2.利用责任链模式进行数据校验拦截
首 先 , 创建一个实体类Member :
@Data public class Member { private String loginName; private String loginPass; private String roleName; public Member(String loginName, String loginPass) { this.loginName = loginName; this.loginPass = loginPass; } }
然后来看一段我们经常写的代码:
public class MemberService { public static void main(String[] args) { MemberService service = new MemberService(); service.login("tom", "666"); } public void login(String loginName, String loginPass) { if (StringUtils.isEmpty(loginName) || StringUtils.isEmpty(loginPass)) { System.out.println("用户名和密码为空"); return; } System.out.println("用户名和密码不为空,可以往下执行"); Member member = checkExists(loginName, loginPass); if (null == member) { System.out.println("用户不存在"); return; } System.out.println("登录成功!"); if (!"管理员".equals(member.getRoleName())) { System.out.println("您不是管理员,没有操作权限"); return; } System.out.println("允许操作"); } private Member checkExists(String loginName, String loginPass) { Member member = new Member(loginName, loginPass); member.setRoleName("管理员"); return member; } }
请问各位小伙伴,你们是不是经常这么干?上面的代码中,主要功能是做了登录前的数据验证,然 后 , 判断逻辑是有先后顺序的。首先做非空判断,然后检查账号是否有效,最终获得用户角色。然后根 据用户角色所拥有的权限再匹配是否有操作权限。那么这样的检验性代码一般都是必不可少的 , 但是写在具体的业务代码又显得代码非常臃肿,因此我们可以用责任链模式 , 将这些检查步骤串联起来,而且 不影响代码美观。可以使得我们在编码时可以更加专注于某一个具体的业务逻辑处理。
下面我们用责任链模式来优化一下代码,首先创建一个Handler类 :
public abstract class Handler { protected Handler next; public void next(Handler next){ this.next = next;} public abstract void doHandler(Member member); }
我们分别创建非空校验ValidateHandler类、登录校验LoginHandler类和权限校验AuthHandler 类 ,来看代码ValidateHandler类 :
public class ValidateHandler extends Handler { public void doHandler(Member member) { if (StringUtils.isEmpty(member.getLoginName()) || StringUtils.isEmpty(member.getLoginPass())) { System.out.println("用户名和密码为空"); return; } System.out.println("用户名和密码不为空,可以往下执行"); next.doHandler(member); } }
LoginHandler 类:
public class LoginHandler extends Handler { public void doHandler(Member member) { System.out.println("登录成功!"); member.setRoleName("管理员"); next.doHandler(member); } }
AuthHandler 类:
public class AuthHandler extends Handler { public void doHandler(Member member) { if (!"管理员".equals(member.getRoleName())) { System.out.println("您不是管理员,没有操作权限"); return; } System.out.println("允许操作"); } }
接下来,修 改Memberservice中的代码,其实我们只需要将前面定义好的几个Handler根据业务需求串联起来,形成一条链即可
public class MemberService { public void login(String loginName, String loginPass) { Handler validateHandler = new ValidateHandler(); Handler loginHandler = new LoginHandler(); Handler authHandler = new AuthHandler(); validateHandler.next(loginHandler); loginHandler.next(authHandler); validateHandler.doHandler(new Member(loginName, loginPass)); } }
来看客户端调用代码:
public class Test { public static void main(String[] args) { MemberService memberService = new MemberService(); memberService.login("tom","666"); } }
其运行结果如下:
![7a30acdd3c6acb0574955d247f07edf8.png](https://img-blog.csdnimg.cn/img_convert/7a30acdd3c6acb0574955d247f07edf8.png)
其实我们平时使用的很多权限校验框架都是运用这样一个原理,将各个维度的权限处理解耦之后再 串联起来,各自只处理各自相关的职责。如果职责与自己不相关则抛给链上的下一个Handler, 俗称踢 皮球。
17.3.责任链模式和建造者模式结合使用
因为责任链模式具备链式结构,而上面代码中,我们看到,负责组装链式结构的角色是 MemberService ,当链式结构较长时,MemberService的工作会非常繁琐,并且MemberService代 码相对臃肿,且后续更改处理者或消息类型时,都必须在MemberService中进行修改,不符合开闭原 则。产生这些问题的原因就是因为链式结构的组装过于复杂,而对于复杂结构的创建,很自然我们就会 想到建造者模式,使用建造者模式,我们完全可以MemberService指定的处理节点对象进行自动链式组装,客户只需指定处理节点对象,其他任何事情都无需关心,并且客户指定的处理节点对象顺序不 同 ,构造出来的链式结构也随之不同。我们来改造一下,修改Handler的代码:
public abstract class Handler { protected Handler next; public void next(Handler next){ this.next = next;} public abstract void doHandler(Member member); public static class Builder{ private Handler head; private Handler tail; public Builder addHandler(Handler handler){ // do { if (this.head == null) { this.head = this.tail = handler; return this; } this.tail.next(handler); this.tail = handler; // }while (false);//真正框架中,如果是双向链表,会判断是否已经到了尾部 return this; } public Handler build(){ return this.head; } } }
然 后 ,修改MemberService的代码:
public class MemberService { public void login(String loginName,String loginPass){ Handler.Builder builder = new Handler.Builder(); builder.addHandler(new ValidateHandler()) .addHandler(new LoginHandler()) .addHandler(new AuthHandler()); builder.build().doHandler(new Member(loginName,loginPass)); //用过Netty的人,肯定见过 } }
因为建造者模式要构建的是节点处理者,因此我们把Builder作为Handler的静态内部类,并且因 为客户端无需进行链式组装,因此我们还可以把链式组装方法next。方法设置为private,使 Handler 更加高内聚,代码如下:
public abstract class Handler { protected Handler chain; private void next(Handler handler) { this.chain = handler; } }
通过这个案例,小伙伴们应该已经感受到责任链和建造者结合的精 ITo
17.4.责任链模式在源码中的体现
首先我们来看责任链模式在JDK中的应用,来看一个J2EE标准中非常常见的Filter类:
public interface Filter { public void init(FilterConfig filterConfig) throws ServletException; public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; public void destroy(); }
这个Filter接口的定义非常简单,相当于责任链模型中的Handler抽象角色,那么它是如何形成一 条责任链的呢?我来看另外一个类,其实在doFilte()方法的最后一个参数我们已经看到其类型是 FilterChain类 ,来看它的源码:
public interface FilterChain { public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException; }
FilterChain类中也只定义了一个doFilter()方法,那么它们是怎么串联成一个责任链的呢?实际上 J2EE只是定义了一个规范,具体处理逻辑是由使用者自己来实现。我们来看一个Spring的实现 MockFilterChain类:
public class MockFilterChain implements FilterChain { @Nullable private ServletRequest request; @Nullable private ServletResponse response; private final List filters; @Nullable private Iterator iterator; public MockFilterChain() { this.filters = Collections.emptyList(); } public MockFilterChain(Servlet servlet) { this.filters = initFilterList(servlet); } public MockFilterChain(Servlet servlet, Filter... filters) { Assert.notNull(filters, "filters cannot be null"); Assert.noNullElements(filters, "filters cannot contain null values"); this.filters = initFilterList(servlet, filters); } private static List initFilterList(Servlet servlet, Filter... filters) { Filter[] allFilters = ObjectUtils.addObjectToArray(filters, new ServletFilterProxy(servlet)); return Arrays.asList(allFilters); } @Nullable public ServletRequest getRequest() { return this.request; } @Nullable public ServletResponse getResponse() { return this.response; } @Override public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { Assert.notNull(request, "Request must not be null"); Assert.notNull(response, "Response must not be null"); Assert.state(this.request == null, "This FilterChain has already been called!"); if (this.iterator == null) { this.iterator = this.filters.iterator(); } if (this.iterator.hasNext()) { Filter nextFilter = this.iterator.next(); nextFilter.doFilter(request, response, this); } this.request = request; this.response = response; } public void reset() { this.request = null; this.response = null; this.iterator = null; } private static class ServletFilterProxy implements Filter { private final Servlet delegateServlet; private ServletFilterProxy(Servlet servlet) { Assert.notNull(servlet, "servlet cannot be null"); this.delegateServlet = servlet; } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { this.delegateServlet.service(request, response); } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void destroy() { } @Override public String toString() { return this.delegateServlet.toString(); } } }
它把链条中的所有Filter放到List中 ,然后在调用doFilter。方法时循环迭代List , 也就是说List 中的Filter会顺序执行。
再来看一个在Netty中非常经典的串行化处理Pipeline就采用了责任链设计模式。它底层采用双向 链表的数据结构,将链上的各个处理器串联起来。客户端每一个请求的到来,Netty都认为Pipeline中 的所有的处理器都有机会处理它。因此,对于入栈的请求全部从头节点开始往后传播,一直传播到尾节 点才会把消息释放掉。来看一个Netty的责任处理器接口 ChannelHandler:
public interface ChannelHandler { // 当 handler被添加到真实的上下文中,并且准备处理事件时被调用 // handler被添加进去的回调 void handlerAdded(ChannelHandlerContext var1) throws Exception; // 是 handler被移出的后的回调 void handlerRemoved(ChannelHandlerContext var1) throws Exception; /** @deprecated */ @Deprecated void exceptionCaught(ChannelHandlerContext var1, Throwable var2) throws Exception; @Inherited @Documented @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface Sharable { } }
Netty对责任处理接口做了更细粒度的划分,处理器被分成了两种,一种是入栈处理器 ChannellnboundHandler, 另一种是出栈处理器ChannelOutboundHandler, 这两个接口都继承自 ChannelHandler.而所有的处理器最终都在添加在Pipeline上。所 以 ,添加删除责任处理器的接口的 行为在Netty的 Channelpipeline中的进行了规定:
public interface ChannelPipeline extends ChannelInboundInvoker,ChannelOutboundInvoker, Iterable> { ChannelPipeline addFirst(String name, ChannelHandler handler); ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler); ChannelPipeline addLast(String name, ChannelHandler handler); ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler); ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler); ... }
在默认是实现类中将所有的Handler都串成了一个链表:
public class DefaultChannelPipeline implements ChannelPipeline { static final InternalLogger logger = InternalLoggerFactory.getInstance(DefaultChannelPipeline.class); final AbstractChannelHandlerContext head; final AbstractChannelHandlerContext tail; ... }
在 Pipeline中的任意一个节点,只要我们不手动的往下传播下去,这个事件就会终止传播在当前节 点。对于入栈数据,默认会传递到尾节点进行回收。如果我们不进行下一步传播,事件就会终止在当前 节点。对于出栈数据把数据写会客户端也意味看事件的终止。
当然在很多安全框架中也会大量使用责任链模式,比如Spring Security. Apache Shiro都会用到 设计模式中的责任链模式,感兴趣的小伙伴可以尝试自己去研究一下。
大部分框架中无论怎么实现,所有的实现都是大同小异的。其实如果我们是站在设计者这个角度看 源码的话,对我们学习源码和编码内功是非常非常有益处的,因为这样,我们可以站在更高的角度来学 习优秀的思想,而不是钻到某一个代码细节里边。我们需要对所有的设计必须有一个宏观概念,这样学 习起来才更加轻松。
17.5.责任链模式的优缺点
优点:
1、将请求与处理解耦;
2、请求处理者(节点对象)只需关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转 发给下一级节点对象;
3、 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果;
4、 链路结构灵活,可以通过改变链路结构动态地新增或删减责任; 5、 易于扩展新的请求处理类(节点),符合开闭原则。
缺点:
1、 责任链太长或者处理时间过长,会影响整体性能
2、 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃;
17.6.作业
1、利用策略模式重构一段业务代码。
使用策略模式实现一个计算器。
步骤 1 创建一个接口。
public interface Strategy { public int doOperation(int num1, int num2); }
步骤 2 创建实现接口的实体类。
// 加 public class OperationAdd implements Strategy{ @Override public int doOperation(int num1, int num2) { return num1 + num2; } } // 减 public class OperationSubstract implements Strategy{ @Override public int doOperation(int num1, int num2) { return num1 - num2; } } // 乘 public class OperationMultiply implements Strategy{ @Override public int doOperation(int num1, int num2) { return num1 * num2; } }
步骤 3 创建 Context 类。
public class Context { private Strategy strategy; public Context(Strategy strategy){ this.strategy = strategy; } public int executeStrategy(int num1, int num2){ return strategy.doOperation(num1, num2); } }
步骤 4 使用 Context 来查看当它改变策略 Strategy 时的行为变化。
public class StrategyPatternDemo { public static void main(String[] args) { Context context = new Context(new OperationAdd()); System.out.println("10 + 5 = " + context.executeStrategy(10, 5)); context = new Context(new OperationSubstract()); System.out.println("10 - 5 = " + context.executeStrategy(10, 5)); context = new Context(new OperationMultiply()); System.out.println("10 * 5 = " + context.executeStrategy(10, 5)); } }
步骤 5 执行程序,输出结果:
10 + 5 = 15 10 - 5 = 5 10 * 5 = 50
2、请描述出责任链模式和建造者模式结合应用的精髓。
责任链模式通过于建造者模式结合,可以有效控制链表顺序。