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

文章目录

  • 基本概念
  • 责任链模式标准结构
  • 责任链模式的扩展
    • 仿照Servlet Filter的实现方式
  • 责任链模式的应用场景
    • 业务场景
    • 开源框架中的应用
  • 责任链模式的缺点
  • 关于设计模式乱用的现象

基本概念

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

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

责任链模式标准结构

不讲故事的设计模式-责任链模式_第1张图片

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

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

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);

}
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!");
        }
    }
}


public class Client {
    public static void main(String[] args) {
        Handler handler1 = new ConcreteHandlerA();
        Handler handler2 = new ConcreteHandlerB();
        handler1.setSuccessor(handler2);
        handler1.handleRequest("text");
    }
}

UML类图
不讲故事的设计模式-责任链模式_第2张图片

责任链模式的扩展

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

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

public interface IHandler {

    void handleRequest(FilterRequestDTO filterRequestDTO);

    default boolean accept(FilterRequestDTO filterRequestDTO) {
        return true;
    }
    
}
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);
        }
    }

}
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;
    }

}

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即可变为可终止责任链向下传递行为的方式。

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职责可以包含鉴权、参数校验、限流、写日志等。

不讲故事的设计模式-责任链模式_第3张图片

public interface Filter {

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

    void doFilter(FilterRequestDTO filterRequestDTO, FilterChain filterChain);

}
public interface FilterChain {

    void doFilter(FilterRequestDTO filterRequestDTO);

}
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();
    }
}
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();
    }
}
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();
    }
}
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());
    }

}

不讲故事的设计模式-责任链模式_第4张图片

责任链模式的应用场景

业务场景

商品报价

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

开源框架中的应用

Spring Interceptor

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

HandlerInterceptor相当于Handler.

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

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. 责任链模式一般需要客户端了解并保证链路上每个处理节点执行逻辑的合理性,因此从整体上看,会增加一定的复杂性,每个处理节点都有自己特定的处理逻辑和条件判断,客户端需要了解这些逻辑并保证正确地设置节点之间的顺序和依赖关系。如果节点设置错误或者逻辑不合理,可能会导致请求无法得到正确处理或者出现意外的结果。

对比直接用分支条件语句

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

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. 精通者:如果跨过新手阶段的关键在于多写多练的话,那么要跨过胜任者阶段则要多思考了,得道的关键在于领悟。

你可能感兴趣的:(设计模式之道,java,servlet,开发语言)