当前位置: 首页 > news >正文

设计模式之策略模式与责任链模式详解和应用

目录

  • 1.策略模式
    • 1.1 目标
    • 1.2.内容定位
    • 1.3.定义
    • 1.4.应用场景
    • 1.5.促销优惠业务场景
    • 1.6 用策略模式实现选择支付方式的业务场景
    • 1.7 策略模式在框架源码中的体现
    • 1.8 策略模式的优缺点
  • 2 责任链模式
    • 2.1 责任链楼式的应用场景
    • 2.2 利用责任链模式进行数据校验拦截
    • 2.3 责任链模式和建造者模式结合使用
    • 2.4 责任链模式在源码中的体现
    • 2.5 责任链模式的优缺点


1.策略模式

1.1 目标

1、 掌握策略模式和责任链模式的应用场景;

2、 通过学习策略模式来消除程序中大量的if…else…和 switch语 句 ;

3、 掌握策略模式和委派模式的结合使用;

4、 深刻理解责任链模式和建造者模式的结合应用

1.2.内容定位

1 、已经掌握建造者模式和委质模式的人群。

2、希望通过对策略模式的学习,来消除程序中大量的冗余代码和多重条件转移语句的人群。

3、希望通过学习责任链模式宜构校验逻辑的人群。

1.3.定义

策略模式 (Strategy Pattern) 又叫也叫政策摆式 (Policy Pattern) , 它是将定义的算法家族、分 别封装起来,让它们之间可以互相替换,从而让算法的变化不会影响到使用算法的用户。属千行为型模式。

原 文: Define a family of algorithms,encapsulate each one,and make them interchangeable.

策略摆式使用的就是面向对象的继承和多态机制,从而实现同—行为在不同场萦下具备不同实现。

1.4.应用场景

策略模式在生活场景中应用也非常多。比如—个入的交税比率与他的工资有关,不同的工资水平对 应不同的税率。再比如我们在互联网移动支付的大背景下,每次下单后付款前,需要选择支付方式。

在这里插入图片描述

策略模式可以解决在有多种算法相似的情况下,使用 if…else 或 switch… case 所带来的复杂性和臃肿性。在日常业务开发中,策略模式适用于以下场景 :

1 、针对同—类型问题,有多种处理方式,每—种都能独立解决问题 ;

2、 算法需要自由切换的场果 ;

3、需要屏蔽算法规则的场果。

首先来看下策略模式的通用 UML 类图 :

在这里插入图片描述

从 UML 类图中,我们可以看到,策略摆式主要包含三种角色 :

上下文角色 (Context) : 用来操作策略的上下文环境,屏蔽高层模块(客户端)对策略, 算法的 直接访问,封装可能存在的变化 ;

抽象策略角色 (Strategy) : 规定策略或算法的行为 ;

具体策略角色 (ConcreteStrategy) : 具体的策略或算法实现。

注意: 策略模式中的上下文环境 (Context) , 其职责本来是隔离客户端与策略类的耦合, 让客户端完全与上下文环境 沟通, 无需关系具体策略。

1.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<String, IPromotionStrategy> PROMOTIONS = new HashMap<String, IPromotionStrategy>();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<String> 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();}}

运行效果如下:

 使用优惠券抵扣

代码优化之后 , 是不是我们程序猿小哥哥的维护工作就轻松了?每次上新活动,不影响原来的代码 逻辑。

1.6 用策略模式实现选择支付方式的业务场景

为了加深对策略模式的理解,我们再来举一个案例。 相信小伙伴们都用过支付宝、微信支付、银联 支付以及京东白条。 —个常见的应用场景就是大家在下单支付时会提示选择支付方式,如果用户未选, 系统也会默认好推荐的支付方式进行结算。来看一下类图,下面我们用策略模式来描拟此业务场罢 :

在这里插入图片描述

创建 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;}@Overridepublic 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<String,Payment> strategy = new HashMap<String,Payment>();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='支付失败'}

希望通过大家耳熟能详的业务场景来举例,让小伙伴们更深刻地理解策略模式。希望小伙伴们在面 试和工作体现出自己的优势。

1.7 策略模式在框架源码中的体现

首先来看JDK 中—个比较常用的比较器 Comparator 接口,我们看到的—个大家常用的 compare() 方法,就是一个策略抽象实现 :

 public interface Comparator<T> {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);elsenew 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<K,V>extends AbstractMap<K,V>implements NavigableMap<K,V>, 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;@NullableString 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 , 我们看—下类图 :

在这里插入图片描述

打开类图我们还发现 CgIibSubclassingInstantiationStrategy 策略类还继承了 SimplelnstantiationStrategy 类,说明在实际应用中多种策略之间还可以继承使用。小伙们可以作为 —个参考,在实际业务场萦中,可以根据需要来设计。

1.8 策略模式的优缺点

优点 :

1 、策略模式符合开闭原则。

2、避免使用多重条件转移语句,如 if…else… 语句、 switch 语句

3、使用策略模式可以提高算法的保密性和安全性。

缺点 :

1 、客户端必须知道所有的策略,并且自行决定使用哪一个策略类。

2、代码中会产生非常多策略类,增加维护难度。

2 责任链模式

责任链模式 (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.
解释:使多个对象都有机会处理请求,从而避免了请求的发送者和接收者之间的耦合关系。将这些对象连成一条链, 并沿着这条链传递该请求,直到有一个对象处理它为止。

2.1 责任链楼式的应用场景

在日常生活中责任链梩式还是比较常见的,我们平时工作处理一些事务,往往是各部门协同合作的 完成莘—个任务。而每个部门都有各自的职责,因此,很多时候事谓完成—半,便会转交给下—个部门, 直到所有部门都过一渔之后事谓才能完成。还有我们平时俗话说的过五关、斩六将其实也是一种责任链。

在这里插入图片描述

责任链模式主要是解耦了请求与处理,客户只需将清求发送到链上即可,无需关心请求的具体内容 和处理细节,请求会自动进行传递直至有节点对象进行处理。

适用于以下应用场景 :

1 、多个对象可以处理同一清求,但具体由哪个对象处理则在运行时动态决定;

2、在不明确指定接收者的情况下,向多个对象中的—个提交—个请求;

3、可动态指定一组对象处理请求。

首先来看下责任链模式的通用 UML 类图 :

在这里插入图片描述

从 UML类图中,我们可以看到,责任链模式主要包含两种角色:

抽象处理者(Handler):定义一个请求处理的方法,并维护一个下一个处理节点Handler对象的 引用;

具体处理者(ConcreteHandler):对请求进行处理,如果不感兴趣,则进行转发。

责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应 当理解的是其模式(道 )而不是其具体实现(术 ),责任链模式的独到之处是其将节点处理者组合成了 链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动了起来。

2.2 利用责任链模式进行数据校验拦截

首 先 , 创建一个实体类Member :

 @Datapublic 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");}}

其运行结果如下:

在这里插入图片描述

其实我们平时使用的很多权限校验框架都是运用这样一个原理,将各个维度的权限处理解耦之后再 串联起来,各自只处理各自相关的职责。如果职责与自己不相关则抛给链上的下一个Handler, 俗称踢 皮球。

2.3 责任链模式和建造者模式结合使用

因为责任链模式具备链式结构,而上面代码中,我们看到,负责组装链式结构的角色是 MemberService ,当链式结构较长时,MemberService的工作会非常繁琐,并且MemberService代 码相对臃肿,且后续更改处理者或消息类型时,都必须在MemberService中进行修改,不符合开闭原 则。产生这些问题的原因就是因为链式结构的组装过于复杂,而对于复杂结构的创建,很自然我们就会 想到建造者模式,使用建造者模式,我们完全可以MemberService指定的处理节点对象进行自动链式组装,客户只需指定处理节点对象,其他任何事情都无需关心,并且客户指定的处理节点对象顺序不 同 ,构造出来的链式结构也随之不同。我们来改造一下,修改Handler的代码:

 public abstract class Handler<T> {protected Handler next;public void next(Handler next){ this.next = next;}public abstract void doHandler(Member member);public static class Builder<T>{private Handler<T> head;private Handler<T> tail;public Builder<T> 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<T> 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<T> {protected Handler chain;private void next(Handler handler) {this.chain = handler;}}

通过这个案例,小伙伴们应该已经感受到责任链和建造者结合的精 ITo

2.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 {@Nullableprivate ServletRequest request;@Nullableprivate ServletResponse response;private final List<Filter> filters;@Nullableprivate Iterator<Filter> 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<Filter> initFilterList(Servlet servlet, Filter... filters) {Filter[] allFilters = ObjectUtils.addObjectToArray(filters, new ServletFilterProxy(servlet));return Arrays.asList(allFilters);}@Nullablepublic ServletRequest getRequest() {return this.request;}@Nullablepublic ServletResponse getResponse() {return this.response;}@Overridepublic 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;}@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {this.delegateServlet.service(request, response);}@Overridepublic void init(FilterConfig filterConfig) throws ServletException {}@Overridepublic void destroy() {}@Overridepublic 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 */@Deprecatedvoid 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<Entry<String, ChannelHandler>> {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都会用到 设计模式中的责任链模式,感兴趣的小伙伴可以尝试自己去研究一下。

大部分框架中无论怎么实现,所有的实现都是大同小异的。其实如果我们是站在设计者这个角度看 源码的话,对我们学习源码和编码内功是非常非常有益处的,因为这样,我们可以站在更高的角度来学 习优秀的思想,而不是钻到某一个代码细节里边。我们需要对所有的设计必须有一个宏观概念,这样学 习起来才更加轻松。

2.5 责任链模式的优缺点

优点:

1、将请求与处理解耦;

2、请求处理者(节点对象)只需关注自己感兴趣的请求进行处理即可,对于不感兴趣的请求,直接转 发给下一级节点对象;

3、 具备链式传递处理请求功能,请求发送者无需知晓链路结构,只需等待请求处理结果;

4、 链路结构灵活,可以通过改变链路结构动态地新增或删减责任; 5、 易于扩展新的请求处理类(节点),符合开闭原则。

缺点:

1、 责任链太长或者处理时间过长,会影响整体性能

2、 如果节点对象存在循环引用时,会造成死循环,导致系统崩溃;

相关文章:

设计模式之策略模式与责任链模式详解和应用

目录1.策略模式1.1 目标1.2.内容定位1.3.定义1.4.应用场景1.5.促销优惠业务场景1.6 用策略模式实现选择支付方式的业务场景1.7 策略模式在框架源码中的体现1.8 策略模式的优缺点2 责任链模式2.1 责任链楼式的应用场景2.2 利用责任链模式进行数据校验拦截2.3 责任链模式和建造者…...

广度优先搜索(BFS)-蓝桥杯

一、BFS搜索的原理BFS搜索的原理&#xff1a;“逐层扩散”&#xff0c;从起点出发&#xff0c;按层次从近到远&#xff0c;逐层先后搜索。编码&#xff1a;用队列实现。应用&#xff1a;BFS一般用于求最短路径问题&#xff0c;BFS的特点是逐层搜索&#xff0c;先搜到的层离起点…...

Java Type类

文章目录Type简介Type分类1. 原始类型(Class)2. 参数化类型(ParameterizedType)3. 类型变量(TypeVariable)4. 通配符类型(WildcardType)5. 泛型数组类型(GenericArrayType)Type简介 Type是Java编程语言中所有类型的公共高级接口。它们包括原始类型、参数化类型、数组类型、类型…...

Springboot扩展点之CommandLineRunner和ApplicationRunner

Springboot扩展点系列&#xff1a;Springboot扩展点之ApplicationContextInitializerSpringboot扩展点之BeanFactoryPostProcessorSpringboot扩展点之BeanDefinitionRegistryPostProcessorSpringboot扩展点之BeanPostProcessorSpringboot扩展点之InstantiationAwareBeanPostPro…...

ngixn 常用配置之文件类型与自定义 log

大家好&#xff0c;我是 17 。 总结了一些 nginx 的常用配置。从入口文件开始&#xff0c;今天讲一下文件类型和自定义log 为了讲述方便&#xff0c;环境为 CentOS 7&#xff0c; nginx 版本 1.21。 配置文件入口 /etc/nginx/nginx.conf这是入口文件&#xff0c;这个文件里…...

【100个 Unity实用技能】 | Unity 通过自定义菜单将资源导出

Unity 小科普 老规矩&#xff0c;先介绍一下 Unity 的科普小知识&#xff1a; Unity是 实时3D互动内容创作和运营平台 。包括游戏开发、美术、建筑、汽车设计、影视在内的所有创作者&#xff0c;借助 Unity 将创意变成现实。Unity 平台提供一整套完善的软件解决方案&#xff…...

0.3调试opencv源码的两种方式

调试opencv源码的两种方式 上两篇我们分别讲了如何配置opencv环境&#xff0c;以及如何编译opencv源码方便我们阅读。但我们还是无法调试我们的代码&#xff0c;无法以我们的程序作为入口来一步一步单点调试看opencv是如何执行的。 【opencv源码解析0.1】VS如何优雅的配置ope…...

Redis的常见操作和Session的持久化

安装Redis使用yum命令&#xff0c;直接将redis安装到linux服务器&#xff1a;yum -y install redis启动redis使用以下命令&#xff0c;以后台运行方式启动redis&#xff1a;redis -server /etc/redis.conf &操作redis使用以下命令启动redis客户端&#xff1a;redis-cli设置…...

TypeScript笔记(二)

背景 上一篇文章我们介绍了TypeScript的一些特性&#xff0c;主要是其与JavaScript的比较&#xff0c;接下来我们将会开始学习Type的语法&#xff0c;这篇文章将会介绍TypeScript的数据类型。 原始数据类型 TypeScript是JavaScript的超集&#xff0c;TypeScript的数据类型就…...

【MyBatis】源码学习 03 - 类型处理器 TypeHandler

文章目录前言参考目录学习笔记1、type 包中类的归类总结2、类型处理器2.1、TypeReference 类3、类型注册表3.1、TypeHandlerRegistry#getTypeHandler前言 本文内容对应的是书本第 8 章的内容&#xff0c;主要是关于类型处理器 TypeHandler 的学习。 这一章节的学习有些地方理…...

建造《流浪地球2》中要毁灭人类的超级量子计算机MOSS的核心量子技术是什么?

1.《流浪地球2》中的量子计算机 2023年中国最火的电影非《流浪地球2》莫属&#xff0c;在《流浪地球2》中有一个人工智能机器人MOSS &#xff0c;它的前身是“550W”超级量子计算机&#xff0c;“MOSS”是它给自己起的名字&#xff08;“550W”倒转180度就是“MOSS”&#xff…...

数据结构~七大排序算法(Java实现)

目录 插入排序 直接插入排序 希尔排序 选择排序 直接选择排序 堆排序 交换排序 冒泡排序 快速排序 递归实现 优化版本 归并排序 插入排序 直接插入排序 public class MySort {public static void insertSort(int[] array) {for (int i 1; i < array.length;…...

python练习

项目场景一&#xff1a; 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 问题描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶…...

RPC-thrift实践

参考&#xff1a;https://www.cnblogs.com/52fhy/p/11146047.html 参考&#xff1a;https://juejin.cn/post/7138032523648598030 实践 安装thrift brew install thriftthrift -version 编写thrift文件 新建文件夹thrift新建文件 结构体文件 Struct.thrift 服务文件 Service.…...

Maven:工程的拆分与聚合

Maven 拆分与聚合创建父工程创建子模块pom.xml配置示例拆分与聚合 在 Maven 中, 拆分是将一个完整的项目分成一个个独立的小模块,聚合是将各个模块进一步组合,形成一个完整的项目。接下来简单示例拆分与聚合的过程。 创建父工程 父工程,一个pom工程,目录结构简单,只需有…...

使用uniapp创建小程序和H5界面

uniapp的介绍可以看官网&#xff0c;接下来我们使用uniapp创建小程序和H5界面&#xff0c;其他小程序也是可以的&#xff0c;只演示创建这2个&#xff0c;其实都是一套代码&#xff0c;只是生成的方式不一样而已。 uni-app官网 1.打开HBuilder X 选择如图所示&#xff0c;下…...

密度峰值聚类算法(DPC)

密度峰值聚类算法目录DPC算法1.1 DPC算法的两个假设1.2 DPC算法的两个重要概念1.3 DPC算法的执行步骤1.4 DPC算法的优缺点matlab代码密度计算函数计算delta寻找聚类中心点聚类算法目录 DPC算法 1.1 DPC算法的两个假设 1&#xff09;类簇中心被类簇中其他密度较低的数据点包围…...

RabbitMQ相关问题

文章目录避免重复消费(保证消息幂等性)消息积压上线更多的消费者&#xff0c;进行正常消费惰性队列消息缓存延时队列RabbitMQ如何保证消息的有序性&#xff1f;RabbitMQ消息的可靠性、延时队列如何实现数据库与缓存数据一致&#xff1f;开启消费者多线程消费避免重复消费(保证消…...

操作系统 三(存储管理)

一、 存储系统的“金字塔”层次结构设计原理&#xff1a;cpu自身运算速度很快。内存、外存的访问速度受到限制各层次存储器的特点&#xff1a;1&#xff09;主存储器&#xff08;主存/内存/可执行存储器&#xff09;保存进程运行时的程序和数据&#xff0c;内存的访问速度远低于…...

day34 贪心算法 | 860、柠檬水找零 406、根据身高重建队列 452、用最少数量的箭引爆气球

题目 860、柠檬水找零 在柠檬水摊上&#xff0c;每一杯柠檬水的售价为 5 美元。 顾客排队购买你的产品&#xff0c;&#xff08;按账单 bills 支付的顺序&#xff09;一次购买一杯。 每位顾客只买一杯柠檬水&#xff0c;然后向你付 5 美元、10 美元或 20 美元。你必须给每个…...

使用canvas给上传的整张图片添加平铺的水印

写在开头 哈喽&#xff0c;各位倔友们又见面了&#xff0c;本章我们继续来分享一个实用小技巧&#xff0c;给图片加水印功能&#xff0c;水印功能的目的是为了保护网站或作者版权&#xff0c;防止内容被别人利用或白嫖。 但是网络中&#xff0c;是没有绝对安全的&#xff0c;…...

[安装之4] 联想ThinkPad 加装固态硬盘教程

方案&#xff1a;保留原有的机械硬盘&#xff0c;再加装一个固态硬盘作为系统盘。由于X250没有光驱&#xff0c;这样就无法使用第二个2.5寸的硬盘。还好&#xff0c;X250留有一个M.2接口&#xff0c;这样&#xff0c;就可以使用NGFF M.2接口的固态硬盘。不过&#xff0c;这种接…...

Java数据类型、基本与引用数据类型区别、装箱与拆箱、a=a+b与a+=b区别

文章目录1.Java有哪些数据类型2.Java中引用数据类型有哪些&#xff0c;它们与基本数据类型有什么区别&#xff1f;3.Java中的自动装箱与拆箱4.为什么要有包装类型&#xff1f;5.aab与ab有什么区别吗?1.Java有哪些数据类型 8种基本数据类型&#xff1a; 6种数字类型(4个整数型…...

GoLang设置gofmt和goimports自动格式化

目录 设置gofmt gofmt介绍 配置gofmt 设置goimports goimports介绍 配置goimports 设置gofmt gofmt介绍 Go语言的开发团队制定了统一的官方代码风格&#xff0c;并且推出了 gofmt 工具&#xff08;gofmt 或 go fmt&#xff09;来帮助开发者格式化他们的代码到统一的风格…...

【k8s】如何搭建搭建k8s服务器集群(Kubernetes)

搭建k8s服务器集群 服务器搭建环境随手记 文章目录搭建k8s服务器集群前言&#xff1a;一、前期准备&#xff08;所有节点&#xff09;1.1所有节点&#xff0c;关闭防火墙规则&#xff0c;关闭selinux&#xff0c;关闭swap交换&#xff0c;打通所有服务器网络&#xff0c;进行p…...

DIDL4_前向传播与反向传播(模型参数的更新)

前向传播与反向传播前向传播与反向传播的作用前向传播及公式前向传播范例反向传播及公式反向传播范例小结前向传播计算图前向传播与反向传播的作用 在训练神经网络时&#xff0c;前向传播和反向传播相互依赖。 对于前向传播&#xff0c;我们沿着依赖的方向遍历计算图并计算其路…...

链表学习之链表划分

链表解题技巧 额外的数据结构&#xff08;哈希表&#xff09;&#xff1b;快慢指针&#xff1b;虚拟头节点&#xff1b; 链表划分 将单向链表值划分为左边小、中间相等、右边大的形式。中间值为pivot划分值。 要求&#xff1a;调整之后节点的相对次序不变&#xff0c;时间复…...

(考研湖科大教书匠计算机网络)第五章传输层-第一、二节:传输层概述及端口号、复用分用等概念

获取pdf&#xff1a;密码7281专栏目录首页&#xff1a;【专栏必读】考研湖科大教书匠计算机网络笔记导航 文章目录一&#xff1a;传输层概述&#xff08;1&#xff09;概述&#xff08;2&#xff09;从计算机网络体系结构角度看传输层&#xff08;3&#xff09;传输层意义二&am…...

C#:Krypton控件使用方法详解(第七讲) ——kryptonHeader

今天介绍的Krypton控件中的kryptonHeader&#xff0c;下面开始介绍这个控件的属性&#xff1a;控件的样子如上图所示&#xff0c;从上面控件外观来看&#xff0c;这个控件有三部分组成。第一部分是前面的图片&#xff0c;第二部分是kryptonHeader1文本&#xff0c;第三部分是控…...

5年软件测试工程师分享的自动化测试经验,一定要看

今天给大家分享一个华为的软件测试工程师分享的关于自动化测试的经验及干货。真的后悔太晚找他要了&#xff0c; 纯干货。一定要看完&#xff01; 1.什么是自动化测试&#xff1f; 用程序测试程序&#xff0c;用代码取代思考&#xff0c;用脚本运行取代手工测试。自动化测试涵…...

日照的网站建设/推广平台网站有哪些

数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。通常情况下&#xff0c;精心选择的数据结构可以带来更高的运行或者存储效率。数据结构往往同高效的检索算法和索引技术有关&#xff0c;数据结构有以下几种基本的结构算法…...

如何制作公司内部网页/公司网站seo外包

本文以内部函数为主线&#xff0c;深入讲解内部函数和闭包的应用场景和原理&#xff0c;学会后你的Python水平会再上一个台阶&#xff0c;对工作面试或实战应用都会很有帮助。本文包括&#xff1a;函数是一等公民内部函数定义闭包和nonlocal关键词应用场景 - 封装应用场景 - 函…...

合肥网站策划/《新闻联播》今天

作为网页的基本常见特效&#xff0c;我今天趁着中午下班&#xff0c;赶紧试一下&#xff0c;没想到简单的实现了&#xff0c; 不过本来就是入门级的东西&#xff0c;就当打怪升级吧&#xff01; 效果如图&#xff1a; html: <ul class"nav"><li class"…...

wordpress插件直播/国内十大搜索引擎

这两天对iptables这块做温习&#xff0c;受其他网友的启发&#xff0c;发现recent这个模块在线上的环境做ssh防护还是挺受用的。recent的使用实际也很简单&#xff0c;咱们先来看看其各个常用的参数--name #设定列表名称&#xff0c;默认DEFAULT。 --rsource …...

常州本地网站/windows优化大师官方网站

1.什么是multidict词典> 在python中&#xff0c;“ multidict ”一词用于指代字典&#xff0c;在字典中可以将单个键映射到多个值。例如 多重结构 multidictWithList {key1 : [1, 2, 3],key2 : [4, 5]}multidictWithSet {key1 : {1, 2, 3},key2 : {4, 5}}1. list如果要保留…...

怎么做各类网站/抖音seo优化软件

自動目錄Base64 應用在非常多的地方&#xff0c;能夠處理許多難搞的字元&#xff0c;讓他們不會被系統或是協定當成令令來執行&#xff0c;例如中文字或二進位的字串。因此&#xff0c;常常會用 base64 encode 及 base64 decode在Linux上有一個指令 base64 可以幫我們完成這個任…...