java设计模式之责任链模式(双向)
前言
本文介绍java设计模式中的责任链模式,又叫职责链模式。从实际需求出发,说明为什么要使用该模式,它的优缺点是什么。
一、概念
它是行为模式的一种,在责任链模式里,很多对象由每一个对象对其下家的引用而连接起来形成一条链。请求在这个链上传递,直到链上的某一个对象决定处理此请求。发出这个请求的客户端并不知道链上的哪一个对象最终处理这个请求,这使得系统可以在不影响客户端的情况下动态地重新组织和分配责任。
责任链模式的本质是解耦请求与处理,让请求在处理链中能进行传递与被处理;理解责任链模式应当理解其模式,而不是其具体实现。责任链模式的独到之处是将其节点处理者组合成了链式结构,并允许节点自身决定是否进行请求处理或转发,相当于让请求流动起来。
本文并不是传统意义上的单一方向的责任链,会是双向的责任链。
二、角色
1.抽象处理者(Handler)
定义出一个处理请求的接口。如果需要,接口可以定义 出一个方法以设定和返回对下家的引用。这个角色通常由一个Java抽象类或者Java接口实现。Handler类的聚合关系给出了具体子类对下家的引用,抽象方法handleRequest()规范了子类处理请求的操作。
2.具体处理者(ConcreteHandler)
具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家。由于具体处理者持有对下家的引用,因此,如果需要,具体处理者可以访问下家。
3.客户类(Client)
创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
本文可能并不完全按照上述角色描述编写,我理解的设计模式并不一定是按照规定的东西来编写,主要还是理解思想。
三. 需求
假设目前有一个入库需求如下图:
入库单据:
字段 | 备注 |
---|---|
id | String,主键 |
type | int,类型 |
goodsId | 物品主键 |
approveId | String,审批人 |
approveStatus | int,审批状态 |
status | int,单据状态 |
package com.gh.freemarker.handler;
/**
* 类功能描述:入库实体
**/
public class InStoreDTO {
/**
* 主键
*/
private String id;
/**
* 类型
*/
private Integer type;
/**
* 物品主键
*/
private String goodsId;
/**
* 审批人
*/
private String approveId;
/**
* 审批状态
*/
private Integer approveStatus;
/**
* 单据状态
*/
private Integer status;
//...省略get set方法。
}
单据入库流程:
- 单据类型=1直接入库,否则第二步。
- 单据如果是特殊流程,需要将单据交给A审批,否则第三步。
- 判断物品的入库等级是否满足条件,满足入库,否则将单据交给B审批。
A审批流程:
- A审批通过,直接入库。不通过结束。
B审批流程
- B审批通过,执行第六步,不通过结束。
- 判断物品的某个属性是否是特殊值,如果不是交给A审批。否则入库。
由于是案例需求,所以其中第3步和第6步描述并不具体,可以理解为是调用一个方法。
1.思考
上述需求可能并不是特别复杂(真实需求会很复杂),但是如果我们直接编程的话,会有很多的 if-else代码块。虽然也可以实现,但是不是很好维护,假设以后会在流程中间添加一个或多个节点判断,那时候我们面对一堆if-else会很头疼。
所以本文的责任链模式就可以使用起来了,我们可以将每一个判断节点抽象为一个具体处理者,具体的处理者只需要关注自己的业务逻辑以及跳转上游(判断条件为Y)或者下游(判断条件为N)的具体处理者。如果以后需求变更,那么我们只需要添加或者修改上下游的具体处理者即可。
、
2.实现
通过上述思考,可以看到大致有三个流程:入库流程、A或者B审批流程,我们可以抽象为两个方法,一个是入库方法,一个是审批的方法。
具体处理者请看下面具体类。
2.0 创建处理对象接口
所有要处理的对象必须实现该类,所以上面的InStoreDTO 必须实现该类
public interface IHandlerRequest {
//定义唯一key,用作流程过程查找
String getHandlerId();
}
/**
* 类功能描述:入库实体
**/
public class InStoreDTO implements IHandlerRequest {
/**
* 主键
*/
private String id;
/**
* 类型
*/
private Integer type;
/**
* 物品主键
*/
private String goodsId;
/**
* 审批人
*/
private String approveId;
/**
* 审批状态
*/
private Integer approveStatus;
/**
* 单据状态
*/
private Integer status;
//...省略get set方法。
@Override
public String getHandlerId() {
return getId();
}
2.1 创建抽象处理者
/**
* 类功能描述:抽象处理者
**/
public abstract class AbstractHandler<T extends IHandlerRequest> {
/**
* Y 分支处理者
*/
protected AbstractHandler<T> yes;
/**
* N 分支处理者
*/
protected AbstractHandler<T> no;
/**
* 处理入口,公共方法
* @param obj 要被处理的对象
* @param tHandlerManager
* @return
*/
public boolean handler(T obj, HandlerManager<T> tHandlerManager){
AbstractHandler<T> nextHandler = null;
if(customHandler(obj)){
nextHandler = getYes();
}else{
nextHandler = getNo();
}
if(nextHandler != null){
tHandlerManager.registered(obj,nextHandler);
return nextHandler.handler(obj, tHandlerManager);
}
return true;
}
/**
* 具体处理者的处理业务方法
* @param obj 要被处理的对象
* @return
*/
protected abstract boolean customHandler(T obj);
protected AbstractHandler<T> getYes() {
return yes;
}
protected void setYes(AbstractHandler<T> yes) {
this.yes = yes;
}
protected AbstractHandler<T> getNo() {
return no;
}
protected void setNo(AbstractHandler<T> no) {
this.no = no;
}
}
2.2 具体处理者–> 类型判断节点
/**
* 类功能描述: 类型=1 走yes 处理者InStoreHandler,反之走no处理者SpecialProcessHandler
**/
public class TypeHandler extends AbstractHandler<InStoreDTO> {
public TypeHandler(){
setYes(new InStoreHandler());
setNo(new SpecialProcessHandler());
}
@Override
protected boolean customHandler(InStoreDTO obj) {
return 1 == obj.getType();
}
}
2.3 具体处理者–> 入库节点
public class InStoreHandler extends AbstractHandler<InStoreDTO> {
//没有下一个节点了,所以不用构造函数
@Override
protected boolean customHandler(InStoreDTO obj) {
System.out.println(String.format("%s入库成功",obj.getId()));
return true;
}
}
2.4 具体处理者–> 特殊流程节点
/**
* 类功能描述:特殊流程处理者:如果是则交给A审批,反之交给物品权限等级处理者
**/
public class SpecialProcessHandler extends AbstractHandler<InStoreDTO> {
private static final Random random = new Random();
public SpecialProcessHandler(){
setYes(new AApproveHandler());
setNo(new GoodsAccessLevelHandler());
}
@Override
protected boolean customHandler(InStoreDTO obj) {
System.out.println(String.format("%s特殊流程查询",obj.getId()));
//模拟业务
return random.nextInt(100) %2 == 0;
}
}
2.5 具体处理者–> A审批节点
/**
* 类功能描述: 生成A审批的单据信息
**/
public class AApproveHandler extends AbstractHandler<InStoreDTO> {
@Override
protected boolean customHandler(InStoreDTO obj) {
//修改单据审批信息,由A审批
obj.setApproveId("A");
obj.setApproveStatus(0);
return true;
}
}
2.6 具体处理者–> 物品权限等级节点
/**
*
* 类功能描述: 物品权限等级处理,查询物品的权限是否高于 业务要求的等级,高直接入库,反之交给B审批
**/
public class GoodsAccessLevelHandler extends AbstractHandler<InStoreDTO> {
public GoodsAccessLevelHandler(){
setYes(new InStoreHandler());
setNo(new BApproveHandler());
}
@Override
protected boolean customHandler(InStoreDTO obj) {
System.out.println(String.format("根据【%s】查询权限等级",obj.getGoodsId()));
return searchLevel(obj.getGoodsId());
}
/**
* 模拟业务:如果是1 证明高,反之低
* @param goodsId 物品主键
* @return
*/
private boolean searchLevel(String goodsId) {
return goodsId.equals("1");
}
}
2.7 具体处理者–> B审批节点
/**
*
* 类功能描述: 生成B审批的单据信息
**/
public class BApproveHandler extends AbstractHandler<InStoreDTO> {
//没有下一个节点了,所以不用构造函数,需要走审批接口
@Override
protected boolean customHandler(InStoreDTO obj) {
//修改单据审批信息,由B审批
obj.setApproveId("B");
obj.setApproveStatus(0);
return true;
}
}
2.8 具体处理者–> 审批拒绝节点
public class ApproveRejectHandler extends AbstractHandler<InStoreDTO> {
//没有下一个节点了,所以不用构造函数
@Override
protected boolean customHandler(InStoreDTO obj) {
//修改单据审批信息
obj.setApproveStatus(2);
return true;
}
}
2.9 具体处理者–> 物品的属性是特殊值节点
/**
* 类功能描述:物品特殊属性处理者:如果是则交给A审批,反之入库
**/
public class GoodsSpecialPropertiesHandler extends AbstractHandler<InStoreDTO> {
private static final Random random = new Random();
public GoodsSpecialPropertiesHandler(){
setYes(new AApproveHandler());
setNo(new InStoreHandler());
}
@Override
protected boolean customHandler(InStoreDTO obj) {
System.out.println(String.format("%s特殊属性查询",obj.getGoodsId()));
//模拟业务
return random.nextInt(100) %2 == 0;
}
}
2.9 责任链管理者
我们可以创建一个管理者暴露给客户端,该管理者定义了 责任链的最开头的 具体处理者。handlers 定义了一个流程日志,可以持久化到数据库中,根据唯一key查询或者修改,如果流程复杂,并且客户需要知道执行的过程,也可以通过websocket实时推送前端,让用户更好的知道流程执行到那一步。(更加复杂的功能都可以在这里实现)
public class HandlerManager<T extends IHandlerRequest> {
//设置开始的处理者
private AbstractHandler<T> first;
private HandlerManager(AbstractHandler<T> first){
this.first = first;
}
/**
* 该单据的流程日志记录
*/
private Map<String, List<AbstractHandler<T>>> handlers = new HashMap<>();
public static <T extends IHandlerRequest> HandlerManager<T> getInstance(AbstractHandler<T> first){
return new HandlerManager<>(first);
}
public boolean handler(T obj){
registered(obj,first);
return first.handler(obj,this);
}
public void registered(T obj,AbstractHandler<T> abstractHandler){
String handlerId = obj.getHandlerId();
List<AbstractHandler<T>> abstractHandlers = handlers.computeIfAbsent(handlerId, k -> new LinkedList<>());
abstractHandlers.add(abstractHandler);
}
}
2.10 入库接口
/**
*
* 类功能描述:入库接口
**/
public interface IInStoreFlow {
/**
* 入库方法
* @param inStoreDTO 入库单
* @return boolean
*/
boolean inStore(InStoreDTO inStoreDTO);
/**
* 功能描述:审批入库单
* @param inStoreDTO 入库单
* @param approveId 审批人
* @param approveStatus 审批状态 1 成功 2 失败
* @return boolean
**/
boolean approveInStoreDTO(InStoreDTO inStoreDTO,String approveId,int approveStatus);
}
2.11 入库实现
/**
*
* 类功能描述:入库接口
**/
public class InStoreFlow implements IInStoreFlow {
/**
* 入库流程由 类型处理者为开始进入
* @param inStoreDTO 入库单
* @return
*/
@Override
public boolean inStore(InStoreDTO inStoreDTO) {
return HandlerManager.getInstance(new TypeHandler()).handler(inStoreDTO);
}
/**
* 如果A审批通过直接执行 入库流程, B审批通过 执行物品特殊属性处理,反之 审批拒绝处理。
* @param inStoreDTO 入库单
* @param approveId 审批人
* @param approveStatus 审批状态 1 成功 2 失败
* @return
*/
@Override
public boolean approveInStoreDTO(InStoreDTO inStoreDTO, String approveId, int approveStatus) {
AbstractHandler<InStoreDTO> abstractHandler;
if("A".equals(approveId) && approveStatus == 1){
abstractHandler = new InStoreHandler();
}else if("B".equals(approveId) && approveStatus == 1) {
abstractHandler = new GoodsSpecialPropertiesHandler();
}else {
abstractHandler = new ApproveRejectHandler();
}
return HandlerManager.getInstance(abstractHandler).handler(inStoreDTO);
}
}
具体调用待补充
。。。
到此该需求相关的所有类创建完毕,主要介绍该模式的思想。
如果需求变动,需要添加一个判断,我们只需要添加一个具体的处理者,并添加到对应的Y或者N分支上即可。
总结
待补充…