【设计模式深度剖析】【3】【行为型】【职责链模式】| 以购物中心客户服务流程为例加深理解

👈️上一篇:命令模式

设计模式-专栏👈️

---

文章目录

  • 职责链模式
  • 定义
    • 英文原话
    • 直译
    • 如何理解呢?
  • 职责链模式的角色
    • 1. Handler(抽象处理者)
    • 2. ConcreteHandler(具体处理者)
    • 3. Client(客户类)
    • 类图
    • 类图分析
    • 代码示例
  • 职责链模式的应用
    • 优点
    • 缺点
    • 使用场景
  • 示例解析:购物中心客户服务流程
    • 类图
    • 类图分析
    • 代码示例

职责链模式

职责链模式(Chain of Responsibility Pattern)是一种常见的行为模式。

职责链模式是一种将多个对象链接起来以处理相同请求的设计模式,就像一条流水线或接力棒传递,每个对象都有机会处理请求,如果不能处理则传递给下一个对象,直到找到能够处理的对象或传递完毕。

这种模式降低了对象间的耦合度,增强了系统的可扩展性和灵活性,使得请求的处理过程更加清晰和模块化。

简而言之,职责链模式==让请求在多个对象间“接力传递”,直到找到“合适的人”==来处理。

定义

英文原话

Chain of Responsibility Pattern: Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it.

直译

职责链模式:通过将多个对象连接成一条链,并沿着这条链传递请求,以避免请求的发送者与接收者之间的紧密耦合。直到链上的某个对象处理请求为止。

如何理解呢?

想象一下在图书馆借阅图书的流程。在一个大型的图书馆系统中,处理图书借阅的流程可能涉及到多个步骤和角色,每个角色都有自己特定的职责。

  1. 读者:读者想要借阅一本书。他们首先会到自助借书机或前台服务台提出借阅请求。
  2. 自助借书机:这是第一个“处理者”。如果自助借书机正常工作且读者持有有效的借阅证,它可以直接处理借阅请求,打印出借阅凭条,并更新图书的借阅状态。
  3. 前台服务台:如果自助借书机出现故障或读者遇到问题,他们可以将借阅请求传递给前台服务台的工作人员。工作人员会检查读者的借阅证是否有效,并手动处理借阅请求。
  4. 图书管理员:如果前台服务台的工作人员发现图书已经被其他读者预约或存在其他需要管理员处理的问题(例如,图书需要修复或更新库存),他们会将借阅请求传递给图书管理员。图书管理员会进一步处理这些特殊情况,并决定是否批准借阅请求。
  5. 系统管理员:在某些情况下,例如系统出现严重故障或需要更改借阅规则时,图书管理员可能无法直接处理请求。这时,他们可以将请求传递给系统管理员,由系统管理员来处理这些系统级别的问题。

在这个场景中,每个角色(自助借书机、前台服务台、图书管理员、系统管理员)都是一个“处理者”,他们共同组成了一个“职责链”。当一个借阅请求出现时,它会沿着这个链传递,直到有一个“处理者”能够处理它为止

这种流程设计降低了各个角色之间的依赖和耦合,使得借阅流程更加灵活和高效。同时,它也提高了系统的可扩展性,因为新的处理者可以很容易地添加到链中,以处理新的请求或应对新的情况

在软件系统中,当有多个对象可以处理同一类请求时,使用职责链模式可以避免请求发送者和接收者之间的紧密耦合,使得系统更加灵活和可扩展

职责链模式的角色

职责链模式中的角色通常包括以下几种:

1. Handler(抽象处理者)

  • 定义一个处理请求的接口

  • (可选) 实现后继链

定义一个处理请求的接口,通常包含一个方法用于处理请求和一个属性用于保存对下一个处理者的引用。

2. ConcreteHandler(具体处理者)

  • 处理它所负责的请求
  • 可访问它的后继者
  • 如果可处理该请求,就处理;否则将该请求转发给它的后继者

实现抽象处理者接口,处理它所负责的请求;如果可以处理该请求就处理,否则将该请求传给它的后继者。

3. Client(客户类)

  • 设置职责链

  • 向链上的具体处理者(ConcreteHandler)对象提交请求

创建处理链,并向链的第一个处理者对象发送请求。

类图

在这里插入图片描述

类图分析

抽象处理者组合了自身类型的对象(定义了 successor 后继处理器属性并通过setSuccssor()方法进行赋值),体现在子类上就是每个具体的处理者可以设置后继处理器,即当前无法处理的话,递给后继处理器进行处理。

代码示例

下面是一个简单的Java示例,演示了职责链模式的应用:

抽象处理者

abstract class Handler {protected Handler successor; // 持有后继者的引用  // 设置后继者  public void setSuccessor(Handler successor) {this.successor = successor;}// 处理请求的方法(声明为抽象方法,由具体处理者实现)  public abstract void handleRequest(int request);
}

具体处理者A

class ConcreteHandlerA extends Handler {@Overridepublic void handleRequest(int request) {if (request >= 0 && request < 10) {System.out.println("Handler A handled request " + request);} else if (successor != null) {successor.handleRequest(request); // 如果不能处理,则传递给后继者  }}
}

具体处理者B

class ConcreteHandlerB extends Handler {@Overridepublic void handleRequest(int request) {if (request >= 10 && request < 20) {System.out.println("Handler B handled request " + request);} else if (successor != null) {successor.handleRequest(request); // 如果不能处理,则传递给后继者  }}
}

具体处理者C

class ConcreteHandlerC extends Handler {@Overridepublic void handleRequest(int request) {if (request >= 20) {System.out.println("Handler C handled request " + request);} else {// 这里没有后继者,请求到此为止  System.out.println("No handler could process the request " + request);}}
}

客户类

package com.polaris.designpattern.list3.behavioral.pattern03.chainofresponsibility.classicdemo;public class Client {public static void main(String[] args) {Handler handlerA = new ConcreteHandlerA();Handler handlerB = new ConcreteHandlerB();Handler handlerC = new ConcreteHandlerC();// 设置职责链  handlerA.setSuccessor(handlerB);handlerB.setSuccessor(handlerC);// 发送请求  int[] requests = {2, 15, 30};for (int request : requests) {handlerA.handleRequest(request);}}
}
/* Output:
Handler A handled request 2
Handler B handled request 15
Handler C handled request 30
*///~

在上面的示例中,我们定义了三个具体处理者(A、B、C),它们分别处理不同范围的请求。在客户类中,我们创建了一个处理链,并将请求发送到链的第一个处理者(A)。如果A不能处理请求,它会将请求传递给B,依此类推。

最终,所有的请求都会被处理或者因为找不到合适的处理者而结束。

职责链模式的应用

优点

  1. 降低耦合度:请求者和接收者之间不直接联系,降低了系统的耦合度。
  2. 增强系统的可扩展性可以根据需要增加新的请求处理类,满足开闭原则
  3. 增强给对象指派职责的灵活性当工作流程发生变化,可以动态地改变链内的成员或者调动它们的次序,也可动态地新增或者删除责任
  4. 责任链简化了对象之间的连接:每个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。

缺点

  1. 不能保证每个请求一定被处理:由于一个请求没有明确的接收者,所以不能保证它一定会被处理,该请求可能一直传到链的末端都得不到处理;一个请求也可能因为链的结构没有得到正确构建而得不到处理(比如忘记给处理者设置后继者)。
  2. 对比较长的职责链,请求的处理可能涉及多个处理对象,系统性能将受到一定影响
  3. 职责链建立的合理性要靠客户端来保证,增加了客户端的复杂性,可能会由于职责链的错误设置而导致系统出错,如可能会造成循环调用。

使用场景

  1. 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定
  2. 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
  3. 可动态指定一组对象处理请求,客户端可以动态地设置职责链来处理请求,也可以改变链内的成员或者调动它们的次序。

示例解析:购物中心客户服务流程

为了更直观地解释职责链模式,我们可以使用一个简单的例子:购物中心的客户服务流程。当顾客在购物中心遇到问题(如退换货、咨询商品信息等)时,他们可能会首先找到最近的店员寻求帮助。如果店员不能解决问题,他们可能会将问题转交给部门经理,如果部门经理也不能处理,问题最终可能会提交给客服中心。

生活中的例子

  1. 顾客:在购物中心遇到问题的顾客。
  2. 店员:首先接待顾客的人,可以处理一些简单的请求,如商品信息查询。
  3. 部门经理:如果店员不能解决问题,顾客的问题会转交给部门经理,部门经理能处理更复杂的请求,如退换货。
  4. 客服中心:如果部门经理也无法解决问题,问题最终会提交给客服中心。

类图

在这里插入图片描述

类图分析

抽象出抽象处理者 CustomerServiceRequestHandler 客户服务请求处理器,

店员 Clerk , 部门经理 DepartmentManager , 客服中心 CustomerServiceCenter分别对其进行实现,是请求的具体处理者,

从类图也可发现,店员Clerk 对象组合了一个请求处理器对象,即后继处理节点,在这个示例是部门经理DepartmentManager 对象,表示店员如果可以处理的客户问题,店员就进行处理,否则交给他的后继节点部门经理处理;

如果请求被流转到部门经理DepartmentManager 对象来处理,如果他能处理,则自行处理返回了,否则将客户的问题递给后继节点处理,这里是客服中心CustomerServiceCenter对象,从类图可以看出部门经理对象组合了一个客户服务请求处理器,具体是客服中心对象。

在本示例中,任何店员,部门经理处理不了的问题,客服中心都负责处理,他是最后的问题处理节点,因此它不再组合后继节点。

代码示例

首先,我们定义一个处理请求的接口

public interface CustomerServiceRequestHandler {String handleRequest(String request);void setNextHandler(CustomerServiceRequestHandler nextHandler);
}

然后,我们创建实现该接口的类来表示不同的处理者:

1.店员 Clerk

public class Clerk implements CustomerServiceRequestHandler {private CustomerServiceRequestHandler nextHandler;@Overridepublic String handleRequest(String request) {if (canHandleRequest(request)) {return "Clerk handled request: " + request;} else if (nextHandler != null) {return nextHandler.handleRequest(request);} else {return "No handler could process the request: " + request;}}@Overridepublic void setNextHandler(CustomerServiceRequestHandler nextHandler) {this.nextHandler = nextHandler;}private boolean canHandleRequest(String request) {// 假设店员只能处理商品信息查询请求return request.startsWith("Product Info");}
}

2.部门经理 DepartmentManager

public class DepartmentManager implements CustomerServiceRequestHandler {private CustomerServiceRequestHandler nextHandler;@Overridepublic String handleRequest(String request) {if (canHandleRequest(request)) {return "Department Manager handled request: " + request;} else if (nextHandler != null) {return nextHandler.handleRequest(request);} else {return "No handler could process the request: " + request;}}@Overridepublic void setNextHandler(CustomerServiceRequestHandler nextHandler) {this.nextHandler = nextHandler;}private boolean canHandleRequest(String request) {// 假设部门经理可以处理退货请求return request.startsWith("Return an item");}
}

3.客服中心 CustomerServiceCenter

public class CustomerServiceCenter implements CustomerServiceRequestHandler {@Overridepublic String handleRequest(String request) {return "Customer Service Center handled request: " + request;}@Overridepublic void setNextHandler(CustomerServiceRequestHandler nextHandler) {// 客服中心是链的末尾,不需要设置下一个处理者// 这里可以抛出一个异常或者忽略这个调用}
}

最后,我们可以创建一个客户端类来演示如何使用这个职责链

public class CustomerServiceChainDemo {public static void main(String[] args) {// 创建处理者实例CustomerServiceRequestHandler clerk = new Clerk();CustomerServiceRequestHandler manager = new DepartmentManager();CustomerServiceRequestHandler center = new CustomerServiceCenter();// 构建职责链clerk.setNextHandler(manager);manager.setNextHandler(center);// 模拟顾客提交请求//查询商品信息String request1 = "Product Info 123";String result1 = clerk.handleRequest(request1);System.out.println(result1); // 输出:Clerk handled request: Product Info 123//退货String request2 = "Return an item";String result2 = clerk.handleRequest(request2);System.out.println(result2); // 输出:Department Manager handled request: Return an item//服务投诉String request3 = "Complaint about service";String result3 = clerk.handleRequest(request3);System.out.println(result3); // 输出:Customer Service Center handled request: Complaint about service}
}
/* Output:
Clerk handled request: Product Info 123
Department Manager handled request: Return an item
Customer Service Center handled request: Complaint about service
*///~

在这个例子中,我们模拟了三种不同类型的顾客请求,并展示了它们如何通过职责链被不同的处理者处理。如果一个处理者不能处理请求,它会将请求传递给链中的下一个处理者,直到找到能够处理该请求的处理者,或者到达链的末尾。

---

👈️上一篇:命令模式

设计模式-专栏👈️

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Vue】普通组件的注册使用-局部注册

文章目录 一、组件注册的两种方式二、使用步骤三、练习 一、组件注册的两种方式 局部注册&#xff1a;只能在注册的组件内使用 ① 创建 .vue 文件 (三个组成部分) 以.vue结尾的组件&#xff0c;一般也叫做 单文件组件&#xff0c;即一个组件就是组件里的全部内容 ② 在使用的组…

Qt窗口与对话框

目录 Qt窗口 1.菜单栏 2.工具栏 3.状态栏 4.滑动窗口 QT对话框 1.基础对话框QDiaog 创建新的ui文件 模态对话框与非模态对话框 2.消息对话框 QMessageBox 3.QColorDialog 4.QFileDialog文件对话框 5.QFontDialog 6.QInputDialog Qt窗口 前言&#xff1a;之前以上…

Linux驱动开发笔记(三)平台设备驱动

文章目录 前言一、Linux的设备模型1. 总线1.1 bus_type结构体1.2 注册/注销总线 2. 设备2.1 device结构体2.2 内核注册/注销设备 3. 驱动3.1 device_driver结构体3.2 注册/注销驱动 4. attribute属性文件4.1 attribute_group结构体4.2 设备属性文件4.3 驱动属性文件4.3. 总线属…

数组array 和 array的区别

问题 对于数组 array和&array有什么区别呢? 先说答案 array: 指向数组第一个数地址的指针 &array: 指向整个数组地址的指针 所以直接打印的话, 地址是一样的. 但是如果1的话, 那么array是增加sizeof(int)大小, &array是增加sizeof(int) * array.size() 测试 #i…

必应bing国内广告账户如何注册推广呢?

作为全球第二大搜索引擎&#xff0c;必应Bing以其庞大的用户基础和精准的定向能力&#xff0c;为企业提供了拓展市场的绝佳平台。对于许多企业来说&#xff0c;必应Bing广告账户的注册与推广流程可能显得复杂而繁琐。此时&#xff0c;您不妨考虑携手云衔科技&#xff0c;共同开…

程序员职业素养:AI新时代下的机遇与挑战

目录 一、引言二、程序员职业素养的五大要点1. 技术能力2. 沟通能力3. 团队合作4. 责任心5. 敬业精神 三、实际案例解析四、程序员职业素养在实际工作中的应用五、AI新时代的程序员的职业发展建议六、总结七、结语 一、引言 在当今这个科技飞速发展的时代&#xff0c;程序员这…

景区ar互动大屏游戏化体验提升营销力度

从20世纪60年代的初步构想&#xff0c;到如今全球范围内无数企业的竞相投入&#xff0c;AR增强现实技术已成为引领科技潮流的重要力量。而在这一浪潮中&#xff0c;中国的AR公司正以其独特的魅力和创新力&#xff0c;崭露头角。 中国的AR市场正在迎来前所未有的发展机遇。如今&…

将现有web项目打包成electron桌面端教程(一)vue3+vite+js版

说明&#xff1a;后续项目需要web端和桌面端&#xff0c;为了提高开发效率&#xff0c;准备直接将web端的代码打包成桌面端&#xff0c;在此提前记录一下demo打包的过程&#xff0c;需要注意的是vue2或者vue3vitets或者vue-cli的打包方式各不同&#xff0c;如果你的项目不是vue…

CasaOS玩客云如何部署小雅AList并结合内网穿透远程访问海量资源

文章目录 前言1. 本地部署AList2. AList挂载网盘3. 部署小雅alist3.1 Token获取3.2 部署小雅3.3 挂载小雅alist到AList中 4. Cpolar内网穿透安装5. 创建公网地址6. 配置固定公网地址 前言 本文主要介绍如何在安装了CasaOS的玩客云主机中部署小雅AList&#xff0c;并在AList中挂…

Ubuntu系统的k8s常见的错误和解决的问题

K8s配置的时候出现的常见问题 Q1: master节点kubectl get nodes 出现的错误 或者 解决方法&#xff1a; cat <<EOF >> /root/.bashrc export KUBECONFIG/etc/kubernetes/admin.conf EOFsource /root/.bashrc重新执行 kubectl get nodes 记得需要查看一下自己的…

【粽子大师】甜咸粽之争来看大师pick谁

概述 粽子大师是一款专门设计用来回答关于粽子制作和历史的问题的应用。无论用户是想了解甜粽还是咸粽的制作方法&#xff0c;或是希望探索粽子的地域文化差异&#xff0c;粽子大师都能提供详细的解答和指导。 功能详述 角色任务 粽子历史和文化专家: 详细了解甜咸粽子之争的…

C语言:详解gcc驱动程序完成编译、汇编、链接的过程

相关阅读 C语言https://blog.csdn.net/weixin_45791458/category_12423166.html?spm1001.2014.3001.5482 gcc是一个命令&#xff0c;严格意义上说&#xff0c;它只是一个驱动程序&#xff0c;而不是一个编译器。gcc负责调用GNU工具链中的预处理器、编译器、汇编器、链接器等工…

翻译《The Old New Thing》- What’s with this MSH_MOUSEWHEEL message?

Whats with this MSH_MOUSEWHEEL message? - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20080806-00/?p21353 Raymond Chen 2008年06月06日 MSH_MOUSEWHEEL 消息是怎么回事&#xff1f; 硬件团队正在研发一种鼠标滚轮设备&#xff0c;并…

matrix-breakout-2-morpheus vulnhub靶场

端口扫描 80 81 需要用户名密码登录 目录扫描 robots.txt 妹用 找不到利用点&#xff0c;换个扫描器再扫 发现新的文件 graffiti.txt graffiti.php 输入的数据Post后会回显到页面上 抓包看看&#xff0c;居然直接传文件路径 发现我们post的数据被写入了graffiti.…

yolov5-ros模型结合zed2相机部署在 Ubuntu系统

前言 本篇文章主要讲解yolov5-ros模型结合zed2相机进行实时检测&#xff0c;经改进实现了红绿灯检测&#xff0c;并输出检测类别与置信度&#xff01; 目录 一、环境配置二、zed2驱动安装三、yolov5-ros功能包配置四、运行官方权重文件四、运行自己权重文件 一、环境配置 1、…

android睡眠分期图

一、效果图 做医疗类项目&#xff0c;经常会遇到做各种图表&#xff0c;本文做的睡眠分期图。 二、代码 引入用到的库 api joda-time:joda-time:2.10.1 调用代码 /*** 睡眠* 分期*/private SleepChartAdapter mAdapter;private SleepChartAttrs mAttrs;private List<SleepI…

不会制作企业版电子书?学会这几个步骤就好啦!

公司安排你制作一本专业的电子书&#xff0c;不知道如何下手&#xff1f;别担心&#xff0c;今天LookLook同学就来给大家分享一下如何轻松制作企业版电子书。参考这几个步骤&#xff0c;相信你一定能轻松搞定&#xff01; 第一步&#xff1a;明确电子书的目标和受众 在开始制作…

zeppelin 未授权任意命令执行漏洞复现

一、命令执行复现 访问http://ip:8080&#xff0c;打开zeppelin页面&#xff0c;&#xff08;zeppelin默认监听端口在8080&#xff09; 点击Notebook->create new note创建新笔记 在创建笔记的时候选择Default Interpreter为sh&#xff0c;即可执行sh命令 如下图&#x…

最新去水印小程序源码分享/无需后台/对接接口/支持全网去水印功能

最新去水印小程序源码分享&#xff1a;无需后台、对接接口&#xff0c;支持全网去水印功能&#xff0c;经过测试发现&#xff0c;该去水印小程序的解析接口需要付费使用。如果您有免费的解析接口&#xff0c;可以自行替换原有接口。 不过&#xff0c;不论是否付费&#xff0c;…

JAVA小案例-分别计算100以内奇数和偶数的和

JAVA小案例-分别计算100以内奇数和偶数的和 没啥可说的&#xff0c;就是for循环加if分支&#xff0c;也可以用while写。 代码如下&#xff1a; public class Jiouhe {/*** 分别计算100以内奇数和偶数的和* param args*/public static void main(String[] args){int sum10;in…