对责任链模式的理解
一、场景
1、题目【来源】
1.1 题目描述
小明所在的公司请假需要在OA系统上发布申请,整个请求流程包括多个处理者,每个处理者负责处理不同范围的请假天数,如果一个处理者不能处理请求,就会将请求传递给下一个处理者,请你实现责任链模式,可以根据请求天数找到对应的处理者。
审批责任链由主管(Supervisor), 经理(Manager)和董事(Director)组成,他们分别能够处理3天、7天和10天的请假天数。如果超过10天,则进行否决。
1.2 输入描述
第一行是一个整数N(1 <= N <= 100), 表示请求申请的数量。
接下来的N行,每行包括一个请求申请的信息,格式为"姓名 请假天数"
1.3 输出描述
对于每个请假请求,输出一行,表示该请求是否被批准。如果被批准/否决,输出被哪一个职级的人批准/否决。
1.4 输入示例
4
Alice 2
Bob 5
Tom 10
Jerry 12
1.5 输出示例
Alice Approved by Supervisor.
Bob Approved by Manager.
Tom Approved by Director.
Jerry Denied by Director.
二、不采用责任链模式
1、代码
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
for (int i = 0; i < n; i++) {
String name = scanner.next();
int dayNum = scanner.nextInt();
if (dayNum <= 3) {
System.out.printf("%s Approved by Supervisor.%n", name);
} else if (dayNum <= 7) {
System.out.printf("%s Approved by Manager.%n", name);
} else {
if (dayNum <= 10) {
System.out.printf("%s Approved by Director.%n", name);
} else {
System.out.printf("%s Denied by Director.%n", name);
}
}
}
}
}
2、缺点
- 员工请假能否被批准需要经过多个环节,不采用责任链模式,需要对这些环节硬编码,将来请假审批规则变化了,这些代码都得重写了。
- 每个环节做好本职工作就好了,而不应该耦合在一起,完全了违背单一职责。
三、采用责任链模式
1、代码
- 定义每个环节
@Data
@Accessors(chain = true)
public class HandleContext implements Serializable {
private static final long serialVersionUID = 5483480993227416969L;
private String name;
private int dayNum;
}
public abstract class BaseHandler {
private BaseHandler nextHandler;
public BaseHandler setNextHandler(BaseHandler nextHandler) {
this.nextHandler = nextHandler;
return nextHandler;
}
public BaseHandler gotNextHandler() {
return nextHandler;
}
public abstract boolean handle(HandleContext context);
}
public class SupervisorHandler extends BaseHandler {
@Override
public boolean handle(HandleContext context) {
int dayNum = context.getDayNum();
if (dayNum <= 3) {
System.out.printf("%s Approved by Supervisor.%n", context.getName());
return true;
}
return false;
}
}
public class ManagerHandler extends BaseHandler {
@Override
public boolean handle(HandleContext context) {
int dayNum = context.getDayNum();
if (dayNum <= 7) {
System.out.printf("%s Approved by Manager.%n", context.getName());
return true;
}
return false;
}
}
public class DirectorHandler extends BaseHandler {
@Override
public boolean handle(HandleContext context) {
int dayNum = context.getDayNum();
if (dayNum <= 10) {
System.out.printf("%s Approved by Director.%n", context.getName());
} else {
System.out.printf("%s Denied by Director.%n", context.getName());
}
return true;
}
}
- 串链
public class LeaveApprovalFacade {
private static final BaseHandler FIRST_HANDLER = new SupervisorHandler();
static {
FIRST_HANDLER.setNextHandler(new ManagerHandler())
.setNextHandler(new DirectorHandler());
}
public void handle(HandleContext context) {
BaseHandler currentHandler = FIRST_HANDLER;
boolean handled = currentHandler.handle(context);
while (!handled && (currentHandler = currentHandler.gotNextHandler()) != null) {
handled = currentHandler.handle(context);
}
}
}
- 客户端
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
LeaveApprovalFacade leaveApprovalFacade = new LeaveApprovalFacade();
for (int i = 0; i < n; i++) {
String name = scanner.next();
int dayNum = scanner.nextInt();
HandleContext context = new HandleContext()
.setName(name)
.setDayNum(dayNum);
leaveApprovalFacade.handle(context);
}
}
}
2、优点
- 每个环节比较独立,做好本职工作即可,符合单一职责。
四、思考
LeaveApprovalFacade负责串链:
(1)环节排序
(2)单个环节执行后,是否继续往下执行
有的情况下,单个环节结束后,根据返回结果,判断是否继续。
本题便是如此,handle方法返回false,表示本环节无法处理。LeaveApprovalFacade根据这个结果,就知道要获取下一个环节继续执行了。
还有的情况每个环节都要执行,例如:解析PDF的过程分为3个步骤,步骤1 -> 步骤2 -> 步骤3。每个步骤都要执行。
环节A根据HandleContext的数据进行执行,可能会产生中间数据,记录在HandleContext中。下一个环节B,会继续根据HandleContext进行执行。
这么看,环节B不就依赖了环节A吗?这不就耦合了吗?
其实不然,环节B压根就不关心环节A的逻辑,只关心自己的输入。至于是环节A产生的还是其他环节产生的都行。这样环节A要重构自己的逻辑,只要输出不变,对环节B没有任何影响。
如果业务逻辑发生很大变化,即使采用了责任链模式,代码也会发生很大变化。首先,每个环节的逻辑要重写(当然了,可能有的环节可以不重写);其次,串链的逻辑也会发生变化。这么看,是不是还不如直接用if-else更好?
我们之所以觉得if-else好,因为我们对if-else的逻辑都比较清楚:
if (dayNum <= 3) { System.out.printf("%s Approved by Supervisor.%n", name); } else if (dayNum <= 7) { System.out.printf("%s Approved by Manager.%n", name); } else { if (dayNum <= 10) { System.out.printf("%s Approved by Director.%n", name); } else { System.out.printf("%s Denied by Director.%n", name); } }
- 假如,if-esle里面的代码块比较复杂,而且都是屎山。现在,我们要修改这块代码,就不得不把每块逻辑都去理解下,否则,很难知道要修改哪坨屎了。
如果采用责任链模式,产品经理说,要改变Manager的审批逻辑,很显然,我们只需要动ManagerHandler的代码就好了。哪怕这里的代码也是屎山,但我们要看的屎就少很多了,相对舒服一些。
总结:并不是设计模式有多牛批,而是设计原则真牛批,我们遵循单一职责、开闭原则,就会让代码变得好维护一些。