职责链模式
这是我们行为型模式的第一节,所以我们先了解了解行为型模式
行为型模式
行为型模式(Behavioral Pattern
) 关注系统中对象之间的交互,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责
行为型模式:不仅仅关注类和对象本身,还重点关注它们之间的相互作用和职责划分
行为型模式分类
类行为型模式
- 使用继承关系在几个类之间分配行为,主要通过多态等方式来分配父类与子类的职责
对象行为型模式
- 使用对象的关联关系来分配行为,主要通过对象关联等方式来分配两个或多个类的职责
职责链概述
辅导员、系主任、院长、校长都可以处理奖学金申请表,他们构成一个处理申请表的链式结构,申请表沿着这条链进行传递,这条链就称为职责链
职责链可以是一条直线
、一个环
或者一个树形结构
,最常见的职责链是直线型
,即沿着一条单向的链来传递请求
环形链比如打牌,一轮一轮的出牌。
树状图比如上级领导发布命令,一层一层的向下传递,直到底层员工。
职责链模式的定义
- 避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求。将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止。
- 客户端无须关心请求的处理细节以及请求的传递,只需将请求发送到链上,将请求的发送者和请求的处理者解耦
职责链模式的结构
职责链模式包含以下2个角色:
- Handler(抽象处理者)
- ConcreteHandler(具体处理者)
通过案例来学习
需求:
某企业的SCM(Supply Chain Management,供应链管理)系统中包含一个采购审批子系统。
该企业的采购审批是分级进行的,即根据采购金额的不同由不同层次的主管人员来审批,
主任可以审批5万元以下(不包括5万元)的采购单,
经理可以审批5万元至10万元(不包括10万元)的采购单,
董事长可以审批10万元至50万元(不包括50万元)的采购单
为了从程序简单, 只写了三个具体处理者
请求类
//采购单:请求类
//被处理的对象类
public 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 this.amount;
}
public int getNumber() {
return number;
}
public String getPurpose() {
return purpose;
}
}
抽象处理类
既然是职责链,必须得要有抽象类,这样才能正确设置链条。
//审批者类:抽象处理者
//里面需要书写设置下家的方法
和抽象处理方法
public abstract class Approves {
protected Approves successor; //定义后继对象
protected String name; //审批者姓名
public Approves(String name) {
this.name = name;
}
//设置后继者
public void setSuccessor(Approves successor) {
this.successor = successor;
}
//抽象请求处理方法
public abstract void processRequest(PurchaseRequest request);
}
具体处理者
具体处理者类需要有详细的处理方法,并且拥有条件判断,拥有自身处理能力和转发下家的能力。
//主任类:具体处理者
public class Director extends Approves {
public Director(String name) {
super(name);
}
//具体请求处理方法
public void processRequest(PurchaseRequest request) {
if (request.getAmount() < 50000) {
System.out.println("主任" + this.name + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。"); //处理请求
}
else {
this.successor.processRequest(request); //转发请求
}
}
}
//经理类:具体处理者
public class Manager extends Approves {
public Manager(String name) {
super(name);
}
//具体请求处理方法
public void processRequest(PurchaseRequest request) {
if (request.getAmount() < 100000) {
System.out.println("经理" + this.name + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。"); //处理请求
}
else {
this.successor.processRequest(request); //转发请求
}
}
}
//董事长类:具体处理者
public class President extends Approves {
public President(String name) {
super(name);
}
//具体请求处理方法
public void processRequest(PurchaseRequest request) {
if (request.getAmount() < 500000) {
System.out.println("董事长" + this.name + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。"); //处理请求
}
else {
System.out.println("太贵了,不买");
}
}
}
客户端调用
public class Client {
public static void main(String[] args) {
Approves director, manager, present;
director = new Director("化骨龙");
manager = new Manager("傻不拉几");
present = new President("阿松大");
//创建职责链
director.setSuccessor(manager);
manager.setSuccessor(present);
//创建采购单
PurchaseRequest pr1 = new PurchaseRequest(45000,10001,"据为己有");
director.processRequest(pr1);
PurchaseRequest pr2 = new PurchaseRequest(60000,10002,"购买大保健");
director.processRequest(pr2);
PurchaseRequest pr3 = new PurchaseRequest(160000,10003,"钱太多了");
director.processRequest(pr3);
PurchaseRequest pr4 = new PurchaseRequest(800000,10004,"你不管");
director.processRequest(pr4);
}
}
输出及分析
主任化骨龙审批采购单:10001,金额:45000.0元,采购目的:据为己有。
经理傻不拉几审批采购单:10002,金额:60000.0元,采购目的:购买大保健。
董事长阿松大审批采购单:10003,金额:160000.0元,采购目的:钱太多了。
太贵了,不买
分析:
- 我们完成了基本的功能,里面的转发是由采购所需money来决定的,正确的完成了转发功能。
- 但是,这里我们在客户端进行了职责链的创建,这显然是不合理的,一般都是程序自动创建或读取配置文件来创建。
- 我们需要给客户端暴露一个接口,它不用管具体是怎么调用的,因此,需要改进代码。
对代码进行改进
XML文件
因为有构造函数,所以我需要给XML文件设置属性
<?xml version="1.0" encoding="UTF-8" ?>
<chain>
<classname value="化骨龙">designpatterns.cor.Director</classname>
<classname value="傻不拉几">designpatterns.cor.Manager</classname>
<classname value="阿松大">designpatterns.cor.President</classname>
</chain>
XML文件读取类
该XML文件需要读取classname
来获得具体处理者的完整类名,从而通过反射去生成对象,但是呢,我们这里使用的是有参构造函数,所以还要读取classname
标签里的value
属性。
并将value
作为参数传入构造函数。
最后,返回一个职责链数组对象,启动类在启动时将链条构造好。
暴露出一个对象用于客户端调用。
public class XMLUtil {
public static Object[] getChains(){
Object[] objects;
DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newDefaultInstance();
DocumentBuilder documentBuilder = null;
try {
documentBuilder = documentBuilderFactory.newDocumentBuilder();
} catch (ParserConfigurationException e) {
throw new RuntimeException(e);
}
Document document = null;
try {
document = documentBuilder.parse("./chain.xml");
} catch (SAXException | IOException e) {
throw new RuntimeException(e);
}
NodeList classnames = document.getElementsByTagName("classname");
objects = new Object[classnames.getLength()];
for (int i = 0; i < classnames.getLength(); i++) {
Node node = classnames.item(i);
String className = node.getTextContent();
String value = node.getAttributes().getNamedItem("value").getNodeValue();;
try {
Class<?> aClass = Class.forName(className);
Object o = aClass.getConstructor(String.class).newInstance(value);
objects[i] = o;
} catch (ClassNotFoundException | InvocationTargetException | InstantiationException |
IllegalAccessException | NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
return objects;
}
}
客户端改造
注意: 链条的创建应该在服务端的启动类中创建,但是为了方便,我这里还是在客户端类中创建,大家能理解意思就行,实际操作时放在正确的位置。
public class Client {
public static void main(String[] args) {
//创建采购单
PurchaseRequest pr1 = new PurchaseRequest(45000,10001,"据为己有");
PurchaseRequest pr2 = new PurchaseRequest(60000,10002,"购买大保健");
PurchaseRequest pr3 = new PurchaseRequest(160000,10003,"钱太多了");
PurchaseRequest pr4 = new PurchaseRequest(800000,10004,"你不管");
//创建职责链
Object[] chains = XMLUtil.getChains();
for (int i = 0; i < chains.length-1; i++) {
Approves approve = (Approves) chains[i];
Approves approveNext = (Approves) chains[i+1];
approve.setSuccessor(approveNext);
}
Approves approve = (Approves) chains[0];
approve.processRequest(pr1);
approve.processRequest(pr2);
approve.processRequest(pr3);
approve.processRequest(pr4);
}
}
输出及分析
可以发现结果正确输出:
主任化骨龙审批采购单:10001,金额:45000.0元,采购目的:据为己有。
经理傻不拉几审批采购单:10002,金额:60000.0元,采购目的:购买大保健。
董事长阿松大审批采购单:10003,金额:160000.0元,采购目的:钱太多了。
太贵了,不买
完成上面的改进之后,我们就不用再修改源代码,
当我们需要修改链条长度或添加链条,可以直接修改配置文件。
思考
其实链条的创建也可以不在启动类中,而是放在配置文件读取类中:
启动类中创建的好处:
- 代码分离,耦合度低,配置文件类与职责链类解耦。
- 职责分离 ,配置文件类只负责读取配置,启动类负责创建职责链。
- 易扩展。启动类可以被替换或者复用。
缺点:
- 逻辑不连贯
- 需要读取配置文件的返回值,编写麻烦
配置文件中创建的好处:
- 逻辑连贯,因为就在配置文件中创建了具体处理对象
- 不需要将读取的配置信息通过参数或者返回值传递给外部。
缺点:
- 配置文件类和职责链类耦合度高。
- 读取配置文件的类,却做了业务相关的事情,职责偏差。
模式优缺点
模式优点:
- 使得一个对象无须知道是其他哪一个对象处理其请求,降低了系统的耦合度
- 可简化对象之间的相互连接
- 给对象职责的分配带来更多的灵活性
- 增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在服务端重新建链即可
模式缺点
- 不能保证请求一定会被处理
- 对于比较长的职责链,系统性能将受到一定影响,在进行代码调试时不太方便
- 如果建链不当,可能会造成循环调用,将导致系统陷入死循环
适用环境
- 有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
- 可动态指定一组对象处理请求
比如权限验证,先验证你的身份,再允许你执行操作。