不讲故事的设计模式-责任链模式

基本概念

在责任链模式中可以定义多个处理节点(Handler),当接收到客户端请求之后,该请求会依次经过每个处理节点,直到某个节点终止将它传递,或者所有节点都处理完为止。

这样设计的优点在于可以有效避免请求发送者与请求接收者之间的耦合关系,符合开闭原则,提高代码的扩展性。

责任链模式标准结构

角色 作用
Handler 定义一个处理请求的接口,主要包含抽象处理方法和后续Handler
Concrete Handler Handler的具体实现者,主要用于负责业务逻辑处理,也可以控制对后继Handler的传递行为

以下是一种按照责任链模式处理方式。

java 复制代码
public abstract class Handler {

    private Handler successor;

    public void setSuccessor(Handler successor) {
        this.successor = successor;
    }

    public Handler getSuccessor() {
        return successor;
    }

    public abstract void handleRequest(String msg);

}
java 复制代码
public class ConcreteHandlerA extends Handler {

    @Override
    public void handleRequest(String msg) {
        if (Objects.nonNull(msg) && !msg.isEmpty()) {
            System.out.println("ConcreteHandlerA pass");
            if (getSuccessor() != null) {
                getSuccessor().handleRequest(msg);
            }
        } else {
            System.out.println("msg 不能为空!");
        }
    }
}

public class ConcreteHandlerB extends Handler {

    @Override
    public void handleRequest(String msg) {
        if (msg.length() <= 16) {
            System.out.println("ConcreteHandlerB pass");
            if (getSuccessor() != null) {
                getSuccessor().handleRequest(msg);
            }
        } else {
            System.out.println("msg长度不能超过16!");
        }
    }
}
java 复制代码
public class Client {
    public static void main(String[] args) {
        Handler handler1 = new ConcreteHandlerA();
        Handler handler2 = new ConcreteHandlerB();
        handler1.setSuccessor(handler2);
        handler1.handleRequest("text");
    }
}

UML类图

责任链模式的扩展

很明显,上面的这种代码实现方式实在是不够优雅,每个具体的处理器类还得要处理对下一个处理器的调用,Client 也必须得熟悉每个处理器类之间的调用关系、顺序等,这些条件都很容易导致代码出现BUG

我们可以改成下面这种实现方式,是让每个处理器都能处理到请求,且能自己进行判定是否需要处理,不存在被某个处理器终止而不继续向后传递的情况。

java 复制代码
public interface IHandler {

    void handleRequest(FilterRequestDTO filterRequestDTO);

    default boolean accept(FilterRequestDTO filterRequestDTO) {
        return true;
    }
    
}
java 复制代码
public class HandlerChain {

    private List<IHandler> handlers = new ArrayList<>();

    public void addHandler(IHandler handler) {
        this.handlers.add(handler);
    }

    public void handle(FilterRequestDTO filterRequestDTO) {
        for (IHandler handler : handlers) {
            if (!handler.accept(filterRequestDTO)) {
                // handler不处理
                continue;
            }
            handler.handleRequest(filterRequestDTO);
        }
    }

}
java 复制代码
public class ConcreteHandler1 implements IHandler {
    @Override
    public void handleRequest(FilterRequestDTO filterRequestDTO) {
        System.out.println("ConcreteHandler1 handleRequest");
    }

    @Override
    public boolean accept(FilterRequestDTO filterRequestDTO) {
        String msg = filterRequestDTO.getMsg();
        return Objects.nonNull(msg) && !msg.isEmpty();
    }

}


public class ConcreteHandler2 implements IHandler {
    @Override
    public void handleRequest(FilterRequestDTO filterRequestDTO) {
        System.out.println("ConcreteHandler2 handleRequest");
    }

    @Override
    public boolean accept(FilterRequestDTO filterRequestDTO) {
        String msg = filterRequestDTO.getMsg();
        return msg.length() >= 16;
    }

}
java 复制代码
public class Client {
    public static void main(String[] args) {
        HandlerChain chain = new HandlerChain();
        chain.addHandler(new ConcreteHandler1());
        chain.addHandler(new ConcreteHandler2());
        FilterRequestDTO filterRequestDTO = new FilterRequestDTO();
        filterRequestDTO.setMsg("hello HandlerChain");
        chain.handle(filterRequestDTO);
    }
}

除此之外,我们还可以将continue改为break即可变为可终止责任链向下传递行为的方式。

java 复制代码
public class HandlerChain {

    private List<IHandler> handlers = new ArrayList<>();

    public void addHandler(IHandler handler) {
        this.handlers.add(handler);
    }

    public void handle(FilterRequestDTO filterRequestDTO) {
        for (IHandler handler : handlers) {
            if (!handler.accept(filterRequestDTO)) {
                // handler不处理
                break;
            }
            handler.handleRequest(filterRequestDTO);
        }
    }

}

仿照Servlet Filter的实现方式

Java Servlet规范中定义的Filter组件,就是一个责任链模式的实际运用场景,Filter职责可以包含鉴权、参数校验、限流、写日志等。

java 复制代码
public interface Filter {

    default boolean accept(FilterRequestDTO filterRequestDTO) {
        return true;
    }

    void doFilter(FilterRequestDTO filterRequestDTO, FilterChain filterChain);

}
java 复制代码
public interface FilterChain {

    void doFilter(FilterRequestDTO filterRequestDTO);

}
java 复制代码
public class BasePriceFilter implements Filter {
    @Override
    public boolean accept(FilterRequestDTO filterRequestDTO) {
        return true;
    }

    @Override
    public void doFilter(FilterRequestDTO filterRequestDTO, FilterChain filterChain) {
        if (accept(filterRequestDTO)) {
            System.out.println("base price 业务逻辑处理");
        }
        filterChain.doFilter(filterRequestDTO);
    }

    public static BasePriceFilter create() {
        return new BasePriceFilter();
    }
}
java 复制代码
public class DiscountPriceFilter implements Filter {
    @Override
    public boolean accept(FilterRequestDTO filterRequestDTO) {
        return true;
    }

    @Override
    public void doFilter(FilterRequestDTO filterRequestDTO, FilterChain filterChain) {
        if (accept(filterRequestDTO)) {
            System.out.println("discount price 业务逻辑处理");
        }
        filterChain.doFilter(filterRequestDTO);
    }

    public static DiscountPriceFilter create() {
        return new DiscountPriceFilter();
    }
}
java 复制代码
public final class PriceFilterChain implements FilterChain {

    private List<Filter> filters;

    private int filterSize;

    private int pos = 0;

    @Override
    public void doFilter(FilterRequestDTO filterRequestDTO) {
        if (pos < filterSize) {
            Filter filter = filters.get(pos++);
            filter.doFilter(filterRequestDTO, this);
        } else {
            System.out.println("处理完了");
        }
    }

    public PriceFilterChain(List<Filter> filters) {
        this.filters = filters;
        this.filterSize = filters.size();
    }
}
java 复制代码
public class FilterChainManager {

    private FilterChain filterChain;

    private void init() {
        List<Filter> filters = new ArrayList<>();
        filters.add(BasePriceFilter.create());
        filters.add(DiscountPriceFilter.create());
        this.filterChain = new PriceFilterChain(filters);
    }

    public void process(FilterRequestDTO filterRequestDTO) {
        filterChain.doFilter(filterRequestDTO);
    }

    public static void main(String[] args) {
        FilterChainManager filterChainManager = new FilterChainManager();
        filterChainManager.init();
        filterChainManager.process(new FilterRequestDTO());
    }

}

责任链模式的应用场景

业务场景

商品报价

通常一个商品在整个报价链路中会涉及到多种价格的计算,包括:基础价格、商家报价、平台报价、折扣价等,这其中每一种价格都有自己的计算逻辑,且有些价格是需要在前一个价格的基础上叠加计算,最终得出消费者到手价,所以可以运用责任链模式来处理。

开源框架中的应用

Spring Interceptor

除了前面提到的Servlet Filter之外,类似的还有Spring Interceptor

HandlerInterceptor相当于Handler.

java 复制代码
public interface HandlerInterceptor {

	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return true;
	}

	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}

	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}

}

HandlerExecutionChain接口相当于HandlerChain

java 复制代码
public class HandlerExecutionChain {

	private final Object handler;
	private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
	private int interceptorIndex = -1;

	public void addInterceptor(HandlerInterceptor interceptor) {
		this.interceptorList.add(interceptor);
	}
	
	boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
		for (int i = 0; i < this.interceptorList.size(); i++) {
			HandlerInterceptor interceptor = this.interceptorList.get(i);
			if (!interceptor.preHandle(request, response, this.handler)) {
				triggerAfterCompletion(request, response, null);
				return false;
			}
			this.interceptorIndex = i;
		}
		return true;
	}

	void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv)
			throws Exception {
		for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
			HandlerInterceptor interceptor = this.interceptorList.get(i);
			interceptor.postHandle(request, response, this.handler, mv);
		}
	}

	void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
		for (int i = this.interceptorIndex; i >= 0; i--) {
			HandlerInterceptor interceptor = this.interceptorList.get(i);
			try {
				interceptor.afterCompletion(request, response, this.handler, ex);
			}
			catch (Throwable ex2) {
				logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
			}
		}
	}
}

责任链模式的缺点

  1. 在实际业务应用中,责任链模式由于是串行执行的。每个请求都需要依次经过每个处理节点,直到找到能够处理的节点或者到达链路的末尾。在这个过程中,每个节点都会进行一定的处理操作,如RPC请求、数据库操作等,这些操作会消耗一定的时间和资源。如果链路比较长,那么这些消耗就会累积起来,导致整个系统的响应时间变长,性能下降。
  2. 责任链模式一般需要客户端了解并保证链路上每个处理节点执行逻辑的合理性,因此从整体上看,会增加一定的复杂性,每个处理节点都有自己特定的处理逻辑和条件判断,客户端需要了解这些逻辑并保证正确地设置节点之间的顺序和依赖关系。如果节点设置错误或者逻辑不合理,可能会导致请求无法得到正确处理或者出现意外的结果。

对比直接用分支条件语句

下面这个小案例也可以用来简单说明。

java 复制代码
public class Main {
    
    public static void main(String[] args) {
        String msg = "hello HandlerChain";

        if (Objects.nonNull(msg) && !msg.isEmpty()) {
            System.out.println("chain 1");
        }
        if (msg.length() >= 16) {
            System.out.println("chain 2");
        }
        
    }
}

如果像上面这样直接用if语句,代码看起来明显更加简洁直观,反观责任链模式将每个条件分支的处理逻辑封装在独立的处理节点中,反而增加了系统的复杂性和理解难度,特别是当链路比较长或者处理逻辑比较复杂时。

因此,是否使用责任链模式取决于具体的情况和需求。如果只需要处理少量的条件分支,并且逻辑也比较简单,则使用if语句可能更加合适。

当然,运用设计模式主要是为了解决代码的复用性、扩展性等问题,虽然直接使用if方式看起来简单了很多,但却不能满足开闭原则,更重要的是不能将其运用在框架中让使用者可以直接进行扩展。

关于设计模式乱用的现象

最后,再来聊聊关于设计模式乱用的问题,主要突出为以下两个阶段:

  1. 新手:这经常发生在刚接触设计模式不久的阶段,急于找地方使用的情况,开发人员不考虑实际的业务场景,完全是为了用设计模式而用设计模式,甚至是先想好要用什么样的设计模式,然后让业务逻辑尽量往这个模式上去套。
  2. 胜任者:过了新手阶段之后,此时你对设计模式也有一定使用经验了,开始意识到胡乱使用设计模式造成的问题了,懂得了理解业务场景才是关键,那还有什么问题呢?此时的阶段就好比术和道的区别,术是多变的,就像我们常说的23种设计模式一样,而道是不变的,无论哪种设计模式始终都是以几种设计原则为依据,正所谓万变不离其宗,设计模式的使用不应当局限于形式上,要能灵活变换。
  3. 精通者:如果跨过新手阶段的关键在于多写多练的话,那么要跨过胜任者阶段则要多思考了,得道的关键在于领悟。
相关推荐
编码浪子9 分钟前
构建一个rust生产应用读书笔记7-确认邮件2
开发语言·后端·rust
Ch.yang11 分钟前
【Spring】 Bean 注入 HttpServletRequest 能保证线程安全的原理
java·spring·代理模式
web1508509664112 分钟前
基于Mysql、JavaScript、PHP、ajax开发的MBTI性格测试网站(前端+后端)
java
昙鱼20 分钟前
springboot创建web项目
java·前端·spring boot·后端·spring·maven
eternal__day20 分钟前
数据结构(哈希表(中)纯概念版)
java·数据结构·算法·哈希算法·推荐算法
天之涯上上25 分钟前
JAVA开发 在 Spring Boot 中集成 Swagger
java·开发语言·spring boot
2402_8575834926 分钟前
“协同过滤技术实战”:网上书城系统的设计与实现
java·开发语言·vue.js·科技·mfc
白宇横流学长27 分钟前
基于SpringBoot的停车场管理系统设计与实现【源码+文档+部署讲解】
java·spring boot·后端
APP 肖提莫30 分钟前
MyBatis-Plus分页拦截器,源码的重构(重构total总数的计算逻辑)
java·前端·算法
kirito学长-Java32 分钟前
springboot/ssm太原学院商铺管理系统Java代码编写web在线购物商城
java·spring boot·后端