设计模式:如何优雅地使用责任链模式

责任链模式(Chain of Responsibility Pattern)在《Head First设计模式》一书中被称为"剩下的模式",其实使用也是蛮多的。最近在学习Netty的过程中用到了责任链模式,在此反过头来重温一下责任链模式。

当你想要让一个以上的对象有机会能够处理某个请求的时候,就使用责任链模式。

一、场景

借用《Head First设计模式》书中的典型场景:需要处理四种类型的电子邮件,第一种类型是粉丝寄来的信,表示他们喜欢新推出的游戏;第二种类型是父母寄来的信,他们抱怨孩子总是沉迷游戏而忘记做作业;第三种类型是店家希望在其他地方也摆放糖果机;第四种类型是垃圾邮件。现在已经可以根据邮件内容确定收到的邮件属于哪种类型,需要设计一个程序来处理这些邮件。

Talk is cheap. Show me the code.直接用代码来说话吧。

用枚举来定义四种类型的邮件:

java 复制代码
    public enum EmailEnum {
        SPAM_EMAIL(1, "Spam_Email"),
        FAN_EMAIL(2, "Fan_Email"),
        COMPLAINT_EMAIL(3, "Complaint_Email"),
        NEW_LOC_EMAIL(4, "New_Loc_Email");

        private Integer code;
        private String desc;

        EmailEnum(Integer code, String desc) {
            this.code = code;
            this.desc = desc;
        }

        public Integer getCode() {
            return code;
        }

        public String getDesc() {
            return desc;
        }
    }

假设邮件有两个属性:邮件类型和邮件内容,定义邮件:

java 复制代码
    public class Email {
        int type;
        String content;

        public Email(int type, String content) {
            this.type = type;
            this.content = content;
        }

        public int getType() {
            return type;
        }

        public String getContent() {
            return content;
        }
    }

二、不使用责任链模式

如果不采用责任链模式?使用EmailHandler这个类来统一处理上述四种邮件,程序是这样子的:

java 复制代码
    public class EmailHandler {

        public void handleEmai(Email email) {
            if (EmailEnum.SPAM_EMAIL.getCode().equals(email.getType())) {
                // 处理垃圾邮件
                handleSpamEmail(email);
            }
            if (EmailEnum.FAN_EMAIL.getCode().equals(email.getType())) {
                // 处理粉丝邮件
                handleFanEmail(email);
            }
            if (EmailEnum.COMPLAINT_EMAIL.getCode().equals(email.getType())) {
                // 处理抱怨邮件
                handleComplaintEmail(email);
            } else {
                // 处理新增邮件
                handleNewLocEmail(email);
            }
        }

        private void handleNewLocEmail(Email email) {
            System.out.println("handleNewLocEmail...");
            // 处理代码省略
        }

        private void handleComplaintEmail(Email email) {
            System.out.println("handleComplaintEmail...");
            // 处理代码省略
        }

        private void handleFanEmail(Email email) {
            System.out.println("handleFanEmail...");
            // 处理代码省略
        }

        public void handleSpamEmail(Email email) {
            System.out.println("handleSpamEmail...");
            // 处理代码省略
        }
    }

这个类虽然强大,但是是不够优雅的:

(1)EmailHandler类较为庞大,各种类型邮件的处理都集中在一个类中,违反了"单一职责原则"。

(2)如果之后增加新的邮件类型、删除某一种邮件类型,或者有其他新功能的拓展,都必须修改源代码并进行严格测试,违反了"开闭原则"。

开放-关闭原则:类应该对扩展开放,对修改关闭。

三、使用责任链模式

如何进行改进呢?那就是使用责任链模式,为某个请求创建一个对象链。每个对象按照顺序检查这个请求,并对其处理,或者将它传递给链中的下一个对象。在本例中,当收到电子邮件的时候,先进入第一个处理器SpamHandler,如果SpamHandler无法处理,就将它传给FanHandler,以此类推...

本例使用责任链模式的结构图如图所示:

Handler是一个抽象的处理器,是一个抽象类。抽象类中定义了一个抽象处理器的对象successor,通过该引用,可以形成一条责任链。抽象类中还定义了抽象处理请求的的方法handleRequest()。代码如下:

java 复制代码
    public abstract class Handler {
        protected Handler successor;

        public void setSuccessor(Handler successor) {
            this.successor = successor;
        }

        public abstract void handleRequest(Email email);
    }

SpamHandler、FanHandler、ComplaintHandler和NewLocHandler是具体的处理器,继承抽象类Handler,用来处理具体的邮件请求。处理细节:处理之前要进行类型的判断,看是否能够处理该请求,如果可以处理就处理,否则就转发给后继的处理器去处理。代码如下:

SpamHandler

java 复制代码
    public class SpamHandler extends Handler{
        @Override
        public void handleRequest(Email email) {
            if (EmailEnum.SPAM_EMAIL.getCode().equals(email.getType())) {
                //处理请求
                System.out.println("SpamHandler handleRequest...");
            }
            else {
                this.successor.handleRequest(email);  //转发请求
            }
        }
    }

FanHandler

java 复制代码
    public class FanHandler extends Handler{
        @Override
        public void handleRequest(Email email) {
            if (EmailEnum.FAN_EMAIL.getCode().equals(email.getType())) {
                //处理请求
                System.out.println("FanHandler handleRequest...");
            }
            else {
                this.successor.handleRequest(email);  //转发请求
            }
        }
    }

ComplaintHandler

java 复制代码
    public class ComplaintHandler extends Handler{
        @Override
        public void handleRequest(Email email) {
            if (EmailEnum.COMPLAINT_EMAIL.getCode().equals(email.getType())) {
                //处理请求
                System.out.println("ComplaintHandler handleRequest...");
            }
            else {
                this.successor.handleRequest(email);  //转发请求
            }
        }
    }

NewLocHandler

java 复制代码
    public class NewLocHandler extends Handler{
        @Override
        public void handleRequest(Email email) {
            if (EmailEnum.NEW_LOC_EMAIL.getCode().equals(email.getType())) {
                //处理请求
                System.out.println("NewLocHandler handleRequest...");
            }
            else {
                this.successor.handleRequest(email);  //转发请求
            }
        }
    }

需要注意的是,责任链模式并不创建责任链,责任链的创建工作必须由系统的其他部分来完成,一般是在使用该责任链的客户端中创建责任链。责任链模式降低了请求的发送端和接收端之间的耦合,使多个对象都有机会处理这个请求。下面编写测试类进行测试:

java 复制代码
    public class Test {
        public static void main(String[] args) {
            // 创建邮件处理请求
            Email email1 = new Email(1,"aaa");
            Email email2 = new Email(2,"bbb");
            Email email3 = new Email(3,"ccc");
            Email email4 = new Email(4,"ddd");
            // 创建Handler
            SpamHandler handler1 = new SpamHandler();
            FanHandler handler2 = new FanHandler();
            ComplaintHandler handler3 = new ComplaintHandler();
            NewLocHandler handler4 = new NewLocHandler();
            // 创建责任链
            handler1.setSuccessor(handler2);
            handler2.setSuccessor(handler3);
            handler3.setSuccessor(handler4);
            // 处理请求
            handler1.handleRequest(email1);
            handler1.handleRequest(email2);
            handler1.handleRequest(email3);
            handler1.handleRequest(email4);
        }
    }

在代码中创建四种类型的邮件用于处理,创建了四种不同的处理器(SpamHandler、FanHandler、ComplaintHandler、NewLocHandler),形成"handler1 -> handler2 -> handler3 -> handler4"的责任链,使用这条责任链处理四种类型的邮件。运行结构如下:

这样处理之后,明显使得请求发送者和接受者解耦;每个实现类都有自己明确且独一无二的职责;如果增加一个类型,只需要再增加一个具体类去继承Handler,书写自己的处理逻辑,在责任链中进行添加;如果删除某种类型的,只需要在构建责任链的时候,把它删除就可以了,实现动态增加或者删除责任,符合设计模式的原则。

四、总结

责任链模式通过建立一条链来组织请求的处理者,请求将沿着链进行传递,请求发送者无须知道请求在何时、何处以及如何被处理,实现了请求发送者与处理者的解耦。在软件开发中,如果遇到有多个对象可以处理同一请求时可以应用责任链模式,例如在Web应用开发中创建一个过滤器链来对请求数据进行过滤,在工作流系统中实现公文的分级审批等等,使用责任链模式可以较好地解决此类问题。

相关推荐
摇滚侠2 小时前
Spring Boot 3零基础教程,IOC容器中组件的注册,笔记08
spring boot·笔记·后端
程序员小凯4 小时前
Spring Boot测试框架详解
java·spring boot·后端
你的人类朋友5 小时前
什么是断言?
前端·后端·安全
程序员小凯6 小时前
Spring Boot缓存机制详解
spring boot·后端·缓存
i学长的猫6 小时前
Ruby on Rails 从0 开始入门到进阶到高级 - 10分钟速通版
后端·ruby on rails·ruby
用户21411832636026 小时前
别再为 Claude 付费!Codex + 免费模型 + cc-switch,多场景 AI 编程全搞定
后端
茯苓gao7 小时前
Django网站开发记录(一)配置Mniconda,Python虚拟环境,配置Django
后端·python·django
Cherry Zack7 小时前
Django视图进阶:快捷函数、装饰器与请求响应
后端·python·django
爱读源码的大都督7 小时前
为什么有了HTTP,还需要gPRC?
java·后端·架构
码事漫谈7 小时前
致软件新手的第一个项目指南:阶段、文档与破局之道
后端