策略模式和责任链模式(5)

简介: 策略模式和责任链模式(5)

来看客户端调用代码:


public class Test {
    public static void main(String[] args) {
        MemberService memberService = new MemberService();
        memberService.login("tom","666");
    }
}


其运行结果如下:


用户名和密码校验成功,可以往下执行
登录成功!
您是管理员,允许操作


其实我们平时使用的很多权限校验框架都是运用这样一个原理,将各个维度的权限处理解耦之后再

串联起来,各自只处理各自相关的职责。如果职责与自己不相关则抛给链上的下一个Handler, 俗称踢

皮球。


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


因为责任链模式具备链式结构,而上面代码中,我们看到,负责组装链式结构的角色是

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


七、责任链模式在源码中的体现


首先我们来看责任链模式在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<Filter> filters;
   @Nullable
   private 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);
   }
   @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<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都会用到

设计模式中的责任链模式,感兴趣的小伙伴可以尝试自己去研究一下。


大部分框架中无论怎么实现,所有的实现都是大同小异的。其实如果我们是站在设计者这个角度看

源码的话,对我们学习源码和编码内功是非常非常有益处的,因为这样,我们可以站在更高的角度来学

习优秀的思想,而不是钻到某一个代码细节里边。我们需要对所有的设计必须有一个宏观概念,这样学

习起来才更加轻松。


八、责任链模式的优缺点

优点:


1、将请求与处理解耦;


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

发给下一级节点对象;


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


4、 链路结构灵活,可以通过改变链路结构动态地新增或删减责任;

5、 易于扩展新的请求处理类(节点),符合开闭原则。


缺点:


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


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


参考文献:策略模式和参考者模式


目录
相关文章
|
11月前
|
监控 前端开发 Linux
Zabbix 7.4 新功能介绍
Zabbix 7.4重磅升级:主机向导简化配置,监控指标卡片直观展示,Map层级自由调整,无限嵌套发现打破限制,TLS加密保障通信安全,助力运维效率飞跃提升!
635 1
|
数据采集 前端开发 搜索推荐
《颠覆认知!React 20构建SPA如何在SEO战场碾压传统多页网站》
本内容探讨如何利用React 20重构单页应用(SPA)的SEO策略,打破传统偏见,提升搜索引擎可见性。通过内容预加载、URL优化、元数据管理及用户体验设计,实现SPA在搜索排名中的突破,同时结合数据分析持续优化,助力SPA在现代搜索引擎生态中脱颖而出。
343 8
|
数据采集 缓存 搜索推荐
NewsNow:开源个性化新闻聚合平台
NewsNow是一个功能强大且易于上手的新闻聚合项目,通过简单的部署步骤,你就可以拥有一个属于自己的个性化新闻聚合平台。无论是学习TypeScript、了解Web开发,还是打造专属的新闻阅读工具,NewsNow都是一个不错的选择。
1453 2
NewsNow:开源个性化新闻聚合平台
|
人工智能 程序员 测试技术
AI编程:Coze + Cursor实现一个思维导图的浏览器插件
本文是小卷关于AI编程工具学习的第3篇文章,通过开发一个思维导图生成工具,详细介绍了AI编程的完整流程。从需求分析、插件选择(如Coze的TreeMind),到创建测试工作流、发布API,再到整合API和开发浏览器插件,最终实现了用户选中文字后生成思维导图的功能。文章展示了如何利用现有工具高效开发,并总结了AI编程的优势与未来趋势。
1814 14
|
数据采集 机器学习/深度学习 搜索推荐
Python爬虫技术基础与应用场景详解
本文介绍了爬虫技术的基本概念、原理及应用场景,包括数据收集、价格监测、竞品分析和搜索引擎优化等。通过一个实战案例展示了如何使用Python爬取电商网站的商品信息。强调了在使用爬虫技术时需遵守法律法规和道德规范,确保数据抓取的合法性和合规性。
|
Ubuntu 开发者 Python
|
关系型数据库 MySQL Java
Java 最常见的面试题:说一下 mysql 的行锁和表锁?
Java 最常见的面试题:说一下 mysql 的行锁和表锁?
|
6天前
|
人工智能 运维 BI
阿里云千问办公QwenWork深度解析:基于Qwen3.8,六大核心能力重构企业全自动化工作流与计费选型指南
传统AI办公工具大多停留在对话问答、文档摘要、简单文案生成层面,只能完成单点碎片化任务,无法自主拆解复杂业务流程,很难串联多工具、多文档、外部业务系统完成端到端完整工作交付。很多企业在落地AI办公的时候,需要组合多款不同工具,来回切换界面,手动复制粘贴中间结果,智能化改造落地门槛居高不下。千问办公QwenWork是整合多款智能体产品能力打造的一体化企业办公智能体平台,底层基座依托Qwen3.8大模型,打通桌面端Agent、云端Agent、企业协同Agent三种运行形态,不再局限简单问答,接收业务目标之后自主拆解任务步骤,调用各类工具,处理文档、表格、浏览器自动化、数据查询,直接输出可交付的办公
1511 0
|
6天前
|
人工智能 自然语言处理 安全
阿里云AI数智鉴密:AI 生成内容如何拿到一张"防篡改的身份证"
隐形水印 + C2PA签名:让AI生成内容“持证上岗”。
1133 0
|
15天前
|
人工智能 自然语言处理 安全
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
本文聚焦阿里云2026年推出的三款自研AI办公产品,清晰拆解千问办公、Qoder Teams、Qoder CN的差异化定位与能力边界:千问办公主打职场全场景提效,支持自然语言指令一键完成PPT生成、数据分析等高频办公任务;Qoder Teams面向程序员团队,深度整合AI代码生成、团队协同与企业知识库能力;Qoder CN则专为金融、政务等强合规场景打造,实现数据不出境与VPC私有化部署。文章同步给出分场景选型指南与最新活动定价,帮助不同类型的企业按需组合产品,实现业务岗、研发岗与强合规场景的AI能力全覆盖。
3788 4
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考