设计模式之职责链模式(Chain of Responsibility Pattern)

1.概念

 职责链模式(Chain of Responsibility Pattern):避免将请求发送者与接收者耦合在一起,让多个对象都有机会接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。职责链模式是一种对象行为型模式

2.结构

职责链模式结构的核心在于引入了一个抽象处理者
在这里插入图片描述

从图中可以看出,在职责链模式结构图中包含以下两个角色:
 (1)Handler(抽象处理者):它定义了一个处理请求的接口,一般设计为抽象类。由于不同的具体处理者处理请求的方式不同,因此在其中定义了抽象请求处理方法。因为每个处理者的下家还是一个处理者,因此在抽象处理者中定义一个抽象处理者类型的对象(结构图中的successor),作为其对下家的引用。通过该引用,处理者可以连成一条链。
 (2)ConcreteHandler(具体处理者):它是抽象处理者的子类,可以处理用户请求。在具体处理者类中实现了抽象处理者中定义的抽象请求处理方法,可以访问链中下一个对象,达到请求转发的效果。
 在职责链模式里,每个对象对其下家的引用连接起来形成一条链。请求在这个链上传递,直到链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织链和分配职责。

3.典型代码

职责链模式的核心在于抽象处理者类的设计,抽象处理者类的典型代码如下:

abstract class Handler {
    //维持对下家的引用
    protected Handler successor;
    
    public void setSuccessor(Handler successor) {
        this.successor = successor;    
    }
    
    public abstract void handleRequest(String request);
}

在上述代码中,抽象处理者定义了对下家的引用对象,以便将请求转发给下家。该对象的访问符可设为 protected,在其子类中可以使用。

具体处理者是抽象处理者的子类,它有两大作用:

  • 处理请求,不同的具体处理者以不同的形式实现抽象请求处理方法 handleRequest();
  • 转发请求,如果该请求超出了当前处理者的处理范围,可以将该请求转发给下家。

具体处理者类的典型代码如下:

class ConcreteHandler extends Handler {
    public void handleRequest(String request) {
        if (请求满足条件) {
            //处理请求        
        } 
        else {
            this.successor.handleRequest(request);  //转发请求
        }
    }
}

4.案例分析一:采购单审批系统

某一采购单审批系统是分级进行的,即根据采购金额的不同由不同层次的主管人员来审批。

主任可以审批5万元以下的采购单,副董事长可以审批 [5, 10) 万元的采购单,董事长可以审批 [10, 50) 万元的采购单,50万元及以上的采购单就需要开董事会讨论决定。
在这里插入图片描述

由于每个职位的审批者都有下家(除了董事会),且他们的行为是有共通性的,都涉及将审批转发给后继。因此设计一个审批者类作为抽象处理者:

//审批者类: 抽象处理者
abstract class Approver {
    protected Approver successor;  //定义后继对象
    protected String name;  //审批者姓名

    public Approver(String name) {
        this.name = name;
    }

    //设置后继者
    public void setSuccessor(Approver successor) {
        this.successor = successor;
    }

    public abstract void processRequest(PurchaseRequest request);
}

然后各个职位的作为具体处理者,要实现抽象处理者:

//主任: 具体处理者
class Director extends Approver{
    public Director(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < 50000) {
            System.out.println(
                    MessageFormat.format("主任 {0} 审批采购单:{1}, 金额:{2}元, 采购目的:{3}。",
                            this.name, request.getNumber(), request.getAmount(), request.getPurpose())
            );
        } else {
            this.successor.processRequest(request);  //转发请求
        }
    }
}

//副董事长:具体处理类
class VicePresident extends Approver{
    public VicePresident(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < 100000) {
            System.out.println(
                    MessageFormat.format("副董事长 {0} 审批采购单:{1}, 金额:{2}元, 采购目的:{3}。",
                            this.name, request.getNumber(), request.getAmount(), request.getPurpose())
            );
        } else {
            this.successor.processRequest(request);
        }
    }
}

//董事长类:具体处理者
class President extends Approver{
    public President(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest request) {
        if (request.getAmount() < 500000) {
            System.out.println(
                    MessageFormat.format("董事长 {0} 审批采购单:{1}, 金额:{2}元, 采购目的:{3}。",
                            this.name, request.getNumber(), request.getAmount(), request.getPurpose())
            );
        } else {
            this.successor.processRequest(request);
        }
    }
}

//董事会类:具体处理者
class Congress extends Approver{
    public Congress(String name) {
        super(name);
    }

    @Override
    public void processRequest(PurchaseRequest request) {
        System.out.println(
                MessageFormat.format("召开董事会 审批采购单:{0}, 金额:{1}元, 采购目的:{2}。",
                        request.getNumber(), request.getAmount(), request.getPurpose())
        );
    }
}

再定义一个采购单类,作为需要被审批的目标:

//采购单: 请求类
class PurchaseRequest {
    private double amount;  //采购金额
    private int number;  //采购单编号
    private String purpose;  //采购目的

    public PurchaseRequest(double amount, int number, String purpose) {
        this.amount = amount;
        this.number = number;
        this.purpose = purpose;
    }

    public double getAmount() {
        return amount;
    }

    public void setAmount(double amount) {
        this.amount = amount;
    }

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }

    public String getPurpose() {
        return purpose;
    }

    public void setPurpose(String purpose) {
        this.purpose = purpose;
    }
}

编写客户端测试代码:

class Client {
    public static void main(String[] args) {
        Approver kangXi, yongZheng, qianLong, hanLinYuan;
        kangXi = new Director("康熙");
        yongZheng = new VicePresident("雍正");
        qianLong = new VicePresident("乾隆");
        hanLinYuan = new Congress("翰林院");

        //创建职责链
        kangXi.setSuccessor(yongZheng);
        yongZheng.setSuccessor(qianLong);
        qianLong.setSuccessor(hanLinYuan);

        //创建采购单
        PurchaseRequest pr1 = new PurchaseRequest(45000, 10001, "购买刀");
        kangXi.processRequest(pr1);

        PurchaseRequest pr2 = new PurchaseRequest(60000, 10002, "购买枪");
        kangXi.processRequest(pr2);

        PurchaseRequest pr3 = new PurchaseRequest(160000, 10003, "购买火炮");
        kangXi.processRequest(pr3);

        PurchaseRequest pr4 = new PurchaseRequest(800000, 10004, "购买军舰");
        kangXi.processRequest(pr4);
    }
}

编译并运行程序,输出结果如下:

主任 康熙 审批采购单:10,001, 金额:45,000元, 采购目的:购买刀。
副董事长 雍正 审批采购单:10,002, 金额:60,000元, 采购目的:购买枪。
董事长 乾隆 审批采购单:10,003, 金额:160,000元, 采购目的:购买火炮。
召开董事会 审批采购单:10,004, 金额:800,000元, 采购目的:购买军舰。

5.案例分析二:班级任务处理

下面来设计一个和上面有些不同的案例,这样可以扩展我们对职责链模式的理解,以及它所能应用的场景。

  • 第1点不同:处理者的请求方法带有返回值,可以反馈信息给上家;
  • 第2点不同:《采购单审批系统》案例是先处理请求再转发请求,而该《班级任务处理》案例是先转发请求再处理请求

学校会派发一些任务给班级进行处理,这些类型包括 one, two, three, four 等等类型,班主任可以处理 one, two, three 这三种类型的任务,班长可以处理 one, two 这两种类型的任务,学习委员可以处理 one 这种类型的任务。班主任在收到任务时,会先将任务交给班长处理,如果下家处理不了,班主任再自己处理;班长在收到任务时,会先将任务交给学委处理,如果下家处理不了,班长再自己处理;学委收到任务时,如果能则自己处理。且他们处理不了时,都会向上反馈。

下面设计Handler类作为抽象处理者:
由于自己以及自己的下家并不一定能处理某些班级任务,存在向上反馈的情况,因此处理者的请求方法需要有boolean型返回值,用于告诉上家是否能处理该任务。

abstract class Handler {
    protected Handler successor;  //定义后继对象

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

    public abstract boolean handleRequest(String taskName);
}

下面分别将班主任、班长、学习委员设计为具体处理者。
班主任默认先将任务给班长处理,班长默认先将任务给学习委员处理,学习委员只能处理 one 类型的任务,处理不了则向上返回 false;
班长如果收到的反馈为false,则自己处理,他只能处理 one, two 类型的任务,处理不了则向上返回 false;
班主任如果收到的反馈为false,则自己处理,他只能处理 one, two, three 类型的任务,处理不了则向上返回false。

//班主任
class HeadTeacher extends Handler{
    @Override
    public boolean handleRequest(String taskName) {
        boolean handled = successor.handleRequest(taskName);
        if (handled) {
            return true;
        }
        if (taskName.equals("one") || taskName.equals("two") || taskName.equals("three")) {
            System.out.println("班主任处理了该事务");
            return true;
        }

        return false;
    }
}

//班长
class Monitor extends Handler{
    @Override
    public boolean handleRequest(String taskName) {
        boolean handled = successor.handleRequest(taskName);
        if (handled) {
            return true;
        }
        if (taskName.equals("one") || taskName.equals("two")) {
            System.out.println("班长处理了该事务");
            return true;
        }

        return false;
    }
}

//学习委员
class StudyCommissary extends Handler{
    @Override
    public boolean handleRequest(String taskName) {
        boolean handled;
        if (successor == null) {  //注意学习委员可能没有下家,所以这里判一下是否为空
            handled = false;
        } else {
            handled = successor.handleRequest(taskName);
        }

        if (handled) {
            return true;
        }
        if (taskName.equals("one")) {
            System.out.println("学习委员处理了该事务");
            return true;
        }

        return false;
    }
}

编写客户端测试代码:
分别给每个职位设置下家,不过注意学习委员没有下家。
还有如果出现班主任也处理不了的任务,则打印一行日志进行说明

public class SchoolClient {
    public static void main(String[] args) {
        Handler headTeacher, monitor, studyCommissary;
        headTeacher = new HeadTeacher();
        monitor = new Monitor();
        studyCommissary = new StudyCommissary();

        headTeacher.setSuccessor(monitor);
        monitor.setSuccessor(studyCommissary);
        studyCommissary.setSuccessor(null);  //没有下一职责人

        startRequest(headTeacher, "one");
        startRequest(headTeacher, "two");
        startRequest(headTeacher, "three");
        startRequest(headTeacher, "four");
    }

    private static void startRequest(Handler headTeacher, String taskName) {
        if (! headTeacher.handleRequest(taskName)) {
            System.out.println("该班级处理不了此类任务!");
        }
    }
}

编译并运行程序,输出结果如下:

学习委员处理了该事务
班长处理了该事务
班主任处理了该事务
该班级处理不了此类任务!

6.适用场景

在以下情况下可以考虑适用职责链模式:

  • 有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定。客户端只需将请求提交到链上,而无须关心请求的处理对象是谁以及它是如何处理的
  • 在不明确指定接收者的情况下,向多个对象中的一个提交请求
  • 可动态指定一组对象处理请求。客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序


参考书籍:
《设计模式的艺术》——刘伟

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/785076.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

单例模式(大话设计模式)C/C++版本

单例模式 C 饿汉 /* HM hungry man 饿汉 */ #include <iostream> using namespace std; class Singleton { private:Singleton() { cout << "单例对象创建&#xff01;" << endl; };Singleton(const Singleton &);Singleton &operator(c…

【ARMv8/v9 GIC 系列 2.4 -- GIC SGI 和 PPI 中断的启用配置】

请阅读【ARM GICv3/v4 实战学习 】 文章目录 GIC SGI 和 PPI 中断的使能配置GICR_ISENABLER0 操作使用举例SummaryGIC SGI 和 PPI 中断的使能配置 GICR_ISENABLER0寄存器(中断设置-使能寄存器0)用于启用相应的SGI(软件生成中断)或PPI(专用外设中断)向CPU接口的转发。每个…

Android多开应用软件系统设计

设计一个支持Android多开应用的软件系统&#xff0c;主要涉及到以下几个关键技术点和设计考虑&#xff1a; 1. 虚拟化技术 容器技术&#xff1a;与传统的虚拟机不同&#xff0c;可以采用更轻量级的容器技术&#xff0c;为每个应用实例创建独立的运行环境。这包括分配独立的用…

atcoder 357 F Two Sequence Queries (线段树板子)

题目&#xff1a; 分析&#xff1a; 线段树 代码&#xff1a; // Problem: F - Two Sequence Queries // Contest: AtCoder - SuntoryProgrammingContest2024&#xff08;AtCoder Beginner Contest 357&#xff09; // URL: https://atcoder.jp/contests/abc357/tasks/abc357_…

AI实时免费在线图片工具6:以图生相似图

1、以图生图&#xff0c;生成相似图 https://huggingface.co/spaces/diffusers/unofficial-SDXL-Turbo-i2i-t2i 间接实现&#xff1a;可以是图片先提取描述&#xff0c;再通过描述再去生成新图片 https://huggingface.co/spaces/gokaygokay/KolorsPlusPlus

JAVA基础-----128陷阱

一、何为128陷阱 Java中Integer类型在使用比较时的特殊行为------128陷阱&#xff0c;解释了当数值在-128到127范围内&#xff0c;由于valueOf方法的缓存机制导致地址相同&#xff0c;比较为真&#xff1b;超出这个范围则新分配内存&#xff0c;地址不同&#xff0c;比较为假。…

动态数据库设计

动态数据库设计是一种灵活的方法&#xff0c;用于构建能够适应不断变化的数据需求的数据库结构。它强调在不频繁修改数据库表结构的前提下&#xff0c;有效管理和存储多样化的数据。以下是实现动态数据库设计的一些关键技术点和策略&#xff1a; 实体-属性-值&#xff08;EAV&a…

安卓项目中so库选择

接上篇Android中常见SDK类型区别-CSDN博客 一些重要的加密算法或者核心协议一般都在C中编写&#xff0c;然后给java调用。这样可以避免反编译后查看到应用的源码。此时就需要了解一下NDK中的ABI&#xff08;Application Binary Interface的缩写&#xff0c;也就是应用二进制接…

代谢组数据分析一:代谢组数据准备

介绍 该数据集是来自于Zeybel 2022年发布的文章_Multiomics Analysis Reveals the Impact of Microbiota on Host Metabolism in Hepatic Steatosis_ [@zeybel2022multiomics],它包含了多种组学数据,如: 微生物组(粪便和口腔) 宿主人体学指标 宿主临床学指标 宿主血浆代谢…

8.8.8.8 IP地址的作用

在跟着韦东山老师的学习手册中看见了关于8.8.8.8 IP用于检测网络状态&#xff0c;然后搜索了关于此IP的相关作用如下&#xff1a; 公共DNS服务&#xff1a;8.8.8.8是Google提供的两个公共DNS服务器地址之一&#xff08;另一个是8.8.4.4&#xff09;。DNS&#xff08;域名系统&a…

GNN Algorithms(9): LLM Prompts--p-tuning、LoRA

目录 1. prompt-tuning background 2. Prompt Tuning 模型介绍 2.1 2021 prefix-tuning 2.2 2021 P-tuning v1 2.3 2021 Parameter-efficient prompt tuning (PET) 2.4 2022 P-tuning v2 2.5 2019 Adapter ​2.6 2021 LoRA (Low-Rank Adaptation) 2.7 2024 DoRA (…

LT8644EX 国产芯片 低功耗 数字交叉点开关 用于光纤网络交换 数字视频 数据存储网络

2.一般说明 LT8644EX是一个16x16数字交叉点交换机:具有16个差分CML兼容输入端和16个差动CML输出端。该LT8644EX是优化非归零(NRZ)与高达每端口6 Gbps的数据速率信令。每个端口提供可编程水平的输入均衡和可编程输出摆幅。tell 18171547226,该LT8644EX支持通过串行控制接口的独立…

尚品汇-(十五)

&#xff08;1&#xff09;快速入门 SpringBoot形式创建 Maven形式创建&#xff1a; 加入依赖&#xff1a; 创建启动类&#xff1a; 设置头文件 就想Jsp的<%Page %>一样 &#xff0c;Thymeleaf的也要引入标签规范。不加这个虽然不影响程序运行&#xff0c;但是你的idea…

Open-Sora1.2环境搭建推理测试

引子 前阵子写了一篇Open-Sora1.0环境搭建&推理测试&#xff08;Open-Sora1.0环境搭建&推理测试_自己搭建sora服务-CSDN博客&#xff0c;感兴趣的童鞋&#xff0c;请移步&#xff09;。Open-Sora1.1发布的时候&#xff0c;撇了一眼新闻。后面一转头&#xff0c;忘记这…

Flink 提交作业的方式

首先我进行了flink单机部署&#xff0c;个人建议不管是学习还是开发尽量不使用 然后开始了flink自带集群部署&#xff0c;部署在三台服务器上&#xff0c;资源管理由flink集群自己管理&#xff0c;然后为了解决集群的单点故障问题&#xff0c;使用zookeeper监听事件&#xff0…

如何做一个透明度渐现且向上位移逐行出现的文字效果

前言 在这个夜黑风高的夜晚&#xff0c;你的眼睛已经开始有些疲惫。你的手指在键盘上轻轻地敲击着&#xff0c;仿佛在弹奏一首无声的夜曲。你的思绪在代码的海洋中飘荡&#xff0c;寻找着最后一行需要完成的代码。就在这时&#xff0c;你的老板走了过来&#xff0c;他的脸上带…

7/8 复盘

后端数据传输&#xff1f; 后端代码的耦合&#xff1a;打点调用、方法调用、接口、继承。 Dao、Service、servlet(controller)各层的作用&#xff1f; Dao负责与数据库交互&#xff0c;执行SQL语句&#xff0c;例如简单的增删改查等等。&#xff08;要创建对应的接口和实现类…

采用前后端分离技术架构+java语言开发的全套产科信息管理系统源码 可与医院HIS、LIS、PACS、RIS等系统进行对接

采用前后端分离技术架构java语言开发的全套产科信息管理系统源码 可与医院HIS、LIS、PACS、RIS等系统进行对接 什么是产科信息管理系统-建档管理&#xff1f; 产科信息管理系统建档管理通过信息技术实现了孕产妇健康信息的电子化、网络化和智能化管理&#xff0c;提高了医疗服…

谷粒商城学习笔记-2-分布式组件-SpringCloud Alibaba-Nacos注册中心

文章目录 一&#xff0c;Nacos简介1&#xff0c;简介2&#xff0c;Nacos原理剖析 二&#xff0c;Nacos服务端安装1&#xff0c;下载 nacos-server2&#xff0c;解压启动nacos-server3&#xff0c;验证 三&#xff0c;服务注册步骤1&#xff0c;引用Nacas客户端的Jar包2&#xf…

鸿蒙 arkts 实现手机号中间四位隐藏, 可以使用 substring [ 简单适用新手 ]

1, 看效果 2, 直接cv代码就可以 Preview Entry Component struct Setting {Statephone:string 15555555555maskPhoneNumber(phone:string){const start phone.substring(0,3)const end phone.substring(7)return ${start}****${end}}build() {Column(){Text(this.maskPhon…