设计模式_责任链模式_Chain

案例引入

学校OA系统的采购审批项目: 需求是

  • 采购员采购教学器材
  • 如果金额 小于等于5000(0<x<=5000),由教学主任审批
  • 如果金额 小于等于10000(5000<x<=10000),由院长审批
  • 如果金额 小于等于30000(10000<x<=30000),由副校长审批
  • 如果金额 超过30000以上(30000<x),由校长审批

传统方式实现

创建一个不同的审批人对应的类,在客户端中写 if else 判断程序,符合不同的条件,就让不同的审批人去处理

分析

客户端这里会使用到分支判断(比如switch)来对不同的采购请求处理, 这样就存在如下问题

  • 如果各个级别的人员审批金额发生变化,客户端的代码也需要发生变化
  • 客户端必须明确知道有多少个审批级别及其访问方式,这样对一个采购请求进行处理的类和Approver (审批人) 就存在强合耦合关系,不利于代码的扩展和维护

【改进】

使用职责链模式

介绍

基础介绍

  • 职责链模式又叫责任链模式,为请求创建了一个接收者对象的链(如下方的简单示意图)。这种模式对请求的发送者和接收者进行解耦。当发送者发送一个请求之后,接收者会按照职责链的顺序一个一个地找出到底应该由谁来负责处理这个请求
  • 职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理该请求,那么它会把相同的请求传给下一个接收者,依此类推,如果所有人都不能处理,最后就会提示或者报错说不能处理
  • 这种类型的设计模式属于行为型模式

登场角色

  • Handler(抽象处理者):定义了一个处理请求的接口,同时设有变量来保存其他的Handler
  • ConcreteHandler(具体处理者):处理它自己负责的请求,可以访问它的后继者(即下一个处理者),如果可以处理当前请求则处理,否则就将该请求交给后继者去处理,从而形成一个职责链
  • Request(请求对象):含有很多属性,表示一个请求
  • Client(请求者):发送请求

应用场景

  • 有多个对象可以处理同一个请求时,比如: 多级请求、请假/加薪等审批流程、Java Web中Tomcat对Encoding的处理、拦截器

案例实现

案例一

类图
实现

【采购请求】

java 复制代码
package com.test.responsibilitychain;

/**
* 请求类
*/
public class PurchaseRequest {

/**
* 请求类型
*/
private int type = 0;
/**
* 请求金额
*/
private float price = 0.0f;
private int id = 0;

/**
* 构造器
* @param type
* @param price
* @param id
*/
public PurchaseRequest(int type, float price, int id) {
this.type = type;
this.price = price;
this.id = id;
}
public int getType() {
return type;
}
public float getPrice() {
return price;
}
public int getId() {
return id;
}

}

【抽象请求处理者】

java 复制代码
package com.test.responsibilitychain;

/**
* 请求处理者
*/
public abstract class Approver {

/**
* 下一个处理者
*/
Approver approver;
/**
* 当前处理者名字
*/
String name;

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

/**
* 下一个处理者
* @param approver
*/
public void setApprover(Approver approver) {
this.approver = approver;
}

/**
* 处理审批请求的方法,得到一个请求, 处理是子类完成,因此该方法做成抽象
* @param purchaseRequest
*/
public abstract void processRequest(PurchaseRequest purchaseRequest);

}

【系级处理人】

java 复制代码
package com.test.responsibilitychain;

public class DepartmentApprover extends Approver {

public DepartmentApprover(String name) {
super(name);
}

@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if(purchaseRequest.getPrice() <= 5000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(purchaseRequest);
}
}

}

【学院级别处理人】

java 复制代码
package com.test.responsibilitychain;

public class CollegeApprover extends Approver {

public CollegeApprover(String name) {
super(name);
}

@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if(purchaseRequest.getPrice() < 5000 && purchaseRequest.getPrice() <= 10000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(purchaseRequest);
}
}
}

【副校长】

java 复制代码
package com.test.responsibilitychain;

public class ViceSchoolMasterApprover extends Approver {

public ViceSchoolMasterApprover(String name) {
super(name);
}

@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if(purchaseRequest.getPrice() < 10000 && purchaseRequest.getPrice() <= 30000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(purchaseRequest);
}
}
}

【校长】

java 复制代码
package com.test.responsibilitychain;

public class SchoolMasterApprover extends Approver {

public SchoolMasterApprover(String name) {
super(name);
}

@Override
public void processRequest(PurchaseRequest purchaseRequest) {
if(purchaseRequest.getPrice() > 30000) {
System.out.println(" 请求编号 id= " + purchaseRequest.getId() + " 被 " + this.name + " 处理");
}else {
approver.processRequest(purchaseRequest);
}
}
}

【客户端】

java 复制代码
package com.test.responsibilitychain;

public class Client {

public static void main(String[] args) {
//创建一个请求
PurchaseRequest purchaseRequest = new PurchaseRequest(1, 31000, 1);

//创建相关的审批人
DepartmentApprover departmentApprover = new DepartmentApprover("张主任");
CollegeApprover collegeApprover = new CollegeApprover("李院长");
ViceSchoolMasterApprover viceSchoolMasterApprover = new ViceSchoolMasterApprover("王副校");
SchoolMasterApprover schoolMasterApprover = new SchoolMasterApprover("佟校长");

//需要将各个审批级别的下一个设置好 (处理人构成环形)
departmentApprover.setApprover(collegeApprover);
collegeApprover.setApprover(viceSchoolMasterApprover);
viceSchoolMasterApprover.setApprover(schoolMasterApprover);
//防止一千块钱也来找校长处理,找校长的话,校长就去找手下处理
schoolMasterApprover.setApprover(departmentApprover);

//找谁开始调用都可以实现功能
departmentApprover.processRequest(purchaseRequest);
viceSchoolMasterApprover.processRequest(purchaseRequest);
}

}

【主类】

SQL 复制代码
 请求编号 id= 1 被 佟校长 处理
请求编号 id= 1 被 佟校长 处理

Process finished with exit code 0

案例二

类图
实现

【问题类】

java 复制代码
package com.test.responsibilitychain.Sample;

public class Trouble {
/**
* 问题编号
*/
private int number;

/**
* 根据编号生成问题
* @param number
*/
public Trouble(int number) {    
this.number = number;
}

/**
* 获取问题编号
* @return
*/
public int getNumber() {        
return number;
}

/**
* 代表问题的字符串
* @return
*/
public String toString() {      
return "[Trouble " + number + "]";
}
}

【用来解决问题的抽象类】

java 复制代码
package com.test.responsibilitychain.Sample;

public abstract class Support {
/**
* 解决问题的实例的名字
*/
private String name;
/**
* 要推卸给的对象
*/
private Support next;

/**
* 生成解决问题的实例
*
* @param name
*/
public Support(String name) {
this.name = name;
}

/**
* 设置要推卸给的对象
*
* @param next
* @return
*/
public Support setNext(Support next) {
this.next = next;
return next;
}

/**
* 解决问题的步骤
* 调用了抽象方法resolve,这里属于模板方法模式
* @param trouble
*/
public void support(Trouble trouble) {
if (resolve(trouble)) {
done(trouble);
} else if (next != null) {
// 问题还没有解决,让下一个对象来尝试解决
next.support(trouble);
} else {
// 所有对象都没有办法解决该问题,提示用户 问题没办法解决
fail(trouble);
}
}

/**
* 显示字符串
*
* @return
*/
public String toString() {
return "[" + name + "]";
}

/**
* 解决问题的方法
* 需要子类去具体实现,如果解决了问题,就返回true
*
* @param trouble
* @return
*/
protected abstract boolean resolve(Trouble trouble);

/**
* 解决
*
* @param trouble
*/
protected void done(Trouble trouble) {
System.out.println(trouble + " is resolved by " + this + ".");
}

/**
* 未解决
*
* @param trouble
*/
protected void fail(Trouble trouble) {
System.out.println(trouble + " cannot be resolved.");
}
}

【不解决问题的类】

java 复制代码
package com.test.responsibilitychain.Sample;

public class NoSupport extends Support {
public NoSupport(String name) {
super(name);
}

/**
* 解决问题的方法 
* 什么都解决不了,直接返回false
* @param trouble
* @return
*/
protected boolean resolve(Trouble trouble) {     
return false; 
}
}

【具体解决问题的类:只要问题的编号小于limit,就可以解决】

java 复制代码
package com.test.responsibilitychain.Sample;

/**
* 可以解决编号小于limit的问题
*/
public class LimitSupport extends Support {
private int limit;

/**
* 构造函数
*
* @param name
* @param limit
*/
public LimitSupport(String name, int limit) {
super(name);
this.limit = limit;
}

protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() < limit) {
/*
解决了什么什么问题
*/
return true;
} else {
return false;
}
}
} 

【具体解决问题的类:只要问题的编号是奇数,就可以解决】

java 复制代码
package com.test.responsibilitychain.Sample;

public class OddSupport extends Support {
/**
* 构造函数
* @param name
*/
public OddSupport(String name) {                
super(name);
}
protected boolean resolve(Trouble trouble) {    
if (trouble.getNumber() % 2 == 1) {
return true;
} else {
return false;
}
}
}

【具体解决问题的类:只能解决指定编号的问题】

java 复制代码
package com.test.responsibilitychain.Sample;

public class SpecialSupport extends Support {
private int number;

/**
* 构造函数
*
* @param name
* @param number
*/
public SpecialSupport(String name, int number) {
super(name);
this.number = number;
}

/**
* 解决问题的方法
*
* @param trouble
* @return
*/
protected boolean resolve(Trouble trouble) {
if (trouble.getNumber() == number) {
return true;
} else {
return false;
}
}
}

【主类】

java 复制代码
package com.test.responsibilitychain.Sample;

public class Main {
public static void main(String[] args) {
// 生成六个解决问题的实例
Support alice = new NoSupport("Alice");
Support bob = new LimitSupport("Bob", 100);
Support charlie = new SpecialSupport("Charlie", 429);
Support diana = new LimitSupport("Diana", 200);
Support elmo = new OddSupport("Elmo");
Support fred = new LimitSupport("Fred", 300);
// 连接对象,形成职责链
alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred);
// 制造各种问题,增长步长大一点,让问题的编号更加的分散
for (int i = 0; i < 500; i += 33) {
alice.support(new Trouble(i));
}
}
}

【运行】

java 复制代码
[Trouble 0] is resolved by [Bob].
[Trouble 33] is resolved by [Bob].
[Trouble 66] is resolved by [Bob].
[Trouble 99] is resolved by [Bob].
[Trouble 132] is resolved by [Diana].
[Trouble 165] is resolved by [Diana].
[Trouble 198] is resolved by [Diana].
[Trouble 231] is resolved by [Elmo].
[Trouble 264] is resolved by [Fred].
[Trouble 297] is resolved by [Elmo].
[Trouble 330] cannot be resolved.
[Trouble 363] is resolved by [Elmo].
[Trouble 396] cannot be resolved.
[Trouble 429] is resolved by [Charlie].
[Trouble 462] cannot be resolved.
[Trouble 495] is resolved by [Elmo].

Process finished with exit code 0

职责链模式在Spring中的应用

  • springmvc请求的流程图中,执行了拦截器相关方法interceptor.preHandler等等,在处理SpringMvc请求时,使用到职责链模式,还使用到适配器模式
  • HandlerExecutionChain主要负责的是请求拦截器的执行和请求处理,但是他本身不处理请求,只是将请求分配给链上注册处理器执行,这是职责链实现方式,减少职责链本身与处理逻辑之间的耦合,规范了处理流程
  • HandlerExecutionChain维护了Handlerlnterceptor的集合, 可以向其中注册相应的拦截器

总结

【优点】

  • 将请求和处理分开,实现解耦,提高系统的灵活性
  • 简化了对象,使对象不需要知道链的结构
  • 可以动态地调整链的结构
  • 让每一个ConcreteHandler更加专注于自己的工作,程序的逻辑更加清晰

【缺点】

  • 性能会受到影响,特别是在链比较长的时候。因此需控制链中最大节点数量,一般通过在Handler中设置一个最大节点数量,在setNext()方法中判断是否已经超过阀值超过则不允许该链建立,避免出现超长链破坏系统性能
  • 调试不方便,采用了类似递归的方式,调试时逻辑可能比较复杂

【问答】

在视窗系统中,经常使用职责链模式。在视窗系统的窗口中,有按钮和文本输入框、勾选框等组件(也称为部件或控件)。当点击鼠标时,鼠标点击事件的处理是如何传播的呢? 职责链模式中的next(要推卸给的对象)是哪个组件呢?

答:一般next字段中保存的多是控件的父窗口

如下图中的小对话框。当焦点移动至"字体"列表框上时,按下键盘上的↑↓键可以选择相应的字体。但是,当焦点移动至"显示均衡字体"勾选框上时如果按下↑键,焦点会移动至"字体"列表框,之后,即使按下↓键,焦点也不会返回到勾选框上。请运用Chain ofResponsibility模式的思考方法来说明这个问题

如果焦点在列表框中,列表框会自己处理↑↓键被按下的事件,不会将请求推卸给next,但如果焦点在勾选框中,它则不会自己处理↑↓小键被按下的事件,而是将请求推卸给next所对应的父对话框。当父对话框接收到↑↓键被按下的事件时,会将焦点移动至列表框中

相关推荐
zjw_rp20 分钟前
Spring-AOP
java·后端·spring·spring-aop
Oneforlove_twoforjob33 分钟前
【Java基础面试题033】Java泛型的作用是什么?
java·开发语言
TodoCoder41 分钟前
【编程思想】CopyOnWrite是如何解决高并发场景中的读写瓶颈?
java·后端·面试
向宇it1 小时前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
小蜗牛慢慢爬行1 小时前
Hibernate、JPA、Spring DATA JPA、Hibernate 代理和架构
java·架构·hibernate
星河梦瑾2 小时前
SpringBoot相关漏洞学习资料
java·经验分享·spring boot·安全
黄名富2 小时前
Redis 附加功能(二)— 自动过期、流水线与事务及Lua脚本
java·数据库·redis·lua
love静思冥想2 小时前
JMeter 使用详解
java·jmeter
言、雲2 小时前
从tryLock()源码来出发,解析Redisson的重试机制和看门狗机制
java·开发语言·数据库
TT哇2 小时前
【数据结构练习题】链表与LinkedList
java·数据结构·链表