您现在的位置是:首页 >技术教程 >【设计模式】责任链模式的设计与示例网站首页技术教程

【设计模式】责任链模式的设计与示例

千筠Wyman 2023-07-09 04:00:02
简介【设计模式】责任链模式的设计与示例

前言

责任链模式是一种行为设计模式,执行上它允许请求沿着一条处理链路依次向下传递,每个处理节点都能对当前状态的请求进行处理,满足一定条件后传递给下一个处理节点,亦或者直接结束这一次处理流程。
请添加图片描述
在现实生产环境中,需要用到责任链模式的场景还是很多的,比如:

  • 多层条件准入控制,如人事审批流程、权限检验、游戏通关判断等
  • 多环节拦截处理,如 Java 过滤器 Filter、组装生产链路等

在这些场景里,使用责任链模式的优势在于,当其中的某个环节需要进行新增、移除、修改时,可以只对单个节点进行操作,不会影响其他节点的执行过程,保证了整个责任链的稳定,代码更加容易维护和迭代。

模型结构设计

责任链模式初步设计 - 基础属性定义

在上述提到的应用场景中,多层条件准入控制涉及到条件判断,满足某些条件后可以直接中断链路流程,跳出服务;多环节拦截处理通常是一个全链路处理,历经所有环节后到达终点才算结束这一流程的操作。在模型设计上,前者也只是增加了一个对中间结果的判断操作,在整体结构上基本是一致的。基于这样的思路,可以给出责任链模式的初步设计。
请添加图片描述

这里为了结构的通用化,定义了一个上下文实体 ContextDO,里面可以根据实际的业务需求在内部定义成员变量,在整理责任链处理流程中,相关的上下文数据则保留在这个实体中并传递下去。

  • Interface 接口 Handler 定义了两个基本方法
    • handle:执行当前节点的行为
    • setNextHandler:指定当前节点的下一个责任节点
  • AbstractHandler: 为抽象类,内部定义了一个可继承的成员变量 next,用来记录当前节点的下一个责任节点。在该抽象类中,可以实现通用的处理流程。
  • HandlerOneHandlerTwo:具体的实现类,继承了抽象类 AbstractHandler,可根据具体需求重写 handler 方法。
  • Client:业务执行客户端,提供入口获取可执行的责任链并启动其执行流程。

责任链模式进一步设计 - 抽象通用方法

在上面责任链设计模式模型的基础上,各个具体实现类一般会有相同的 handle 动作,这个时候可以将这个执行的内容统一封装到抽象类 AbstractHandler 里面。同时,还可以新增一个方法 isComplete,判断执行动作后的状态是否已满足条件,从而决定从此结束责任链流程或者决定进入下一个处理节点。
请添加图片描述

如图,相同的执行动作被统一抽象封装到抽象类 AbstractHandler 的handle 方法中,另外内部单独封装出来一个 action 方法,供实现类对具体执行动作进行重写。同样新增的方法还有 preActionpostACtionisComplete 等。如果某个具体的 Handler 实现类有特殊的处理动作,则可以在实现类中再重写这些方法。

  • preAction:预处理(可选)。每个处理节点在执行处理流程前,可以对上下文数据进行一些预处理,如对前面已处理的对象进行去重、过滤等。
  • postAction:后置处理(可选)。执行处理流程后,对结果进行后置处理,如将当前流程处理的对象加入去重表,用以后续过滤等操作。
  • isComplete:完成条件判断。判断当前节点执行完成后,是否完成全链路目标,是则结束流程,否则继续下一个节点的动作。亦可以调换判断逻辑。

以下是一个简单的抽象类实现 demo:

@Data
public class ContextDO {
    /** 执行条件,可在 preAction 里更新 */
    private List<Object> conditions;
    
    /** 返回结果,每次执行 action 后新增 */
    private List<Object> resultList;
    
	/** 已处理对象,可在 postAction 里更新 */
    private Set<Object> existedObjSet;
    
    /** 目标请求数量 */
    private Integer querySize;
}

public abstract class AbstractHandler implements Handler {
	/** 下一个处理器节点 */
    protected Handler next;
    
    @Override
    public void handle(ContextDO context) {
        try {
            preAction(context);
            action(context);
            postAction(context);            
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        /** 未达到完成目标,且还有下一个处理节点,则继续推进责任链任务 */
        if (!isComplete(context) && this.next != null) {
            this.next.handle(context);
        }
    }
    
    @Override
    public abstract void preAction(ContextDO context);
    
    @Override
    public void postAction(ContextDO context) {
        context.getExistedObjSet().addAll(context.getConditions);
    }
    
    @Override
    public void action(ContextDO context) {
        // do something and add new result into #context.resultList.
    }
    
    @Override
    public boolean isComplete(ContextDO context) {
        return context.getResultList() != null && context.getResultList().size() >= context.getQuerySize();
    }
    
    @Override
    public void setNextHandler(Handler next) {
        this.next = next;
    }
}

在 demo 中定义了一个示例的上下文实体 ContextDO,其字段分别可在 preActionactionpostACtionisComplete 中用到。根据业务的具体情况,可调整上下文需要的字段,或者重写抽象类中封装的各个方法。

责任链模式服务的调用

前面已经完成了责任链模式的设计,从代码中可以看出,责任链任务执行的入口就是首个处理节点的 handle 方法,调用该方法后,整个流程就能结合 isComplete 方法和指向下个处理节点的 next 推动任务的进行。下面介绍几种启动责任链服务的方式,也就是业务客户端的实现方式。

简单客户端 - 按需组合

正如上面 UML 设计图所示,我们可以定义一个 Client 类,直接手动拼接需要组成的责任链节点,直接执行即可。

public class Client {
    public Object process(Object input) {
        ContextDO context = new ContextDO();
        // do something. 将 input 的内容封装到 context 中
        
        AbstractHandler handler1 = new HandlerOne();
        AbstractHandler handler2 = new HandlerTwo();
        AbstractHandler handler3 = new HandlerThree();
        AbstractHandler handler4 = new HandlerFour();

        handler1.setNextHandler(handler2);
        handler2.setNextHandler(handler3);
        handler3.setNextHandler(handler4);
        
        return handler1.hander(context);
    }
}

这种方式的优点在于代码可读性强,责任链的组合比较灵活,能否满足不同业务的定制化需求;而缺点在于,每一次调用服务的时候,都需要重新创建对象,完成后则立即释放资源,如果请求较多,则会一直重复这个操作,白白损耗了性能。因此,当责任链的组成相对固定时,我们完全可以定义一个稳定的、全局可引用的责任链,这里就可以结合工厂模式的思想了。

责任链工厂模式 - 统一装配

责任链工厂模式本质上与上面按需组合的简单客户端实现是相同的,只不过更多的结合 Spring 的一些特性,如自动装配、服务代理等,使代码更加整洁,更具拓展性和可维护性。

这里采用自动装配加上 @Order 注解来组装责任链,通过责任链工厂提供统一的责任链服务。

@Order(1)
@Service
public class HandlerOne extends AbstractHandler {
    // ...
}

@Order(2)
@Service
public class HandlerTwo extends AbstractHandler {
    // ...
}

责任链工厂中实现对责任链各节点的组装,并提供一个统一的管理器,供业务方直接启用服务。

@Service
public class ChainFactory {
    
    private AbstractHandler headHandler;
    
    @Resource
    private List<AbstractHandler> handlerList;
    
    @PostConstruct
    public void init() {
        handlerList.sort(AnnotationAwareOrderComparator.INSTANCE);
        int size = handlerList.size();
        for (int i = 0; i < size - 1; i++) {
            handlerList.get(i).setNextHandler(handlerList.get(i + 1));
        }
        headHandler = handlerList.get(0);
    }
    
    public AbstractHandler getHeadHandler() {
        return this.handHandler;
    }
}

客户端可以通过以上工厂直接调用方法 ChainFactory.getHeadHandler() 获取到责任链头节点,随后执行 handle 方法即可启用相关服务了。

责任链模式更多拓展 - 工厂策略模式

上述的 demo 代码将继承了抽象类 AbstractHandler 的所有子类都加载到列表后,并根据每个实现类 @Order 注解的值进行排序,组装成责任链,将头节点通过 getter 方法对外开放。如果业务出现更多不同业务场景的责任链,或者同样的业务场景里,需要提供不同的组合方案,则可以在上述基础上做进一步拓展。

假如需要支持更多不一样的场景,可以粗暴地去实现新的抽象类,在新的抽象类中支持新的业务场景,对应的也需要有新的责任链工厂对外开放接口。

假如是同样的业务场景需要支持不同的节点组合方案,即在同一个工厂内需要提供不同的责任链,此时可以在工厂中使用策略模式。每一种策略对应一种责任链组合方案,在责任链工厂中通过一个映射表来维护所有的责任链方案。而不同责任链方案的初始化,可以通过配置文件进行定义,这样的可读性、可维护性最高,灵活度也最高。这里就不再提供具体的实现 demo 了。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。