行为型模式
目录
1、责任链模式
为了避免请求发送者与多个请求处理者耦合在一起,于是将所有请求的处理者通过前一对象记住其下一个对象的引用而连成一条链;当有请求发生时,可将请求沿着这条链传递,直到有对象处理它为止。
职责链模式主要包含以下角色。
- 抽象处理者(Handler)角色:定义一个处理请求的接口,包含抽象处理方法和一个后继连接。
- 具体处理者(Concrete Handler)角色:实现抽象处理者的处理方法,判断能否处理本次请求,如果可以处理请求则处理,否则将该请求转给它的后继者。
- 客户类(Client)角色:创建处理链,并向链头的具体处理者对象提交请求,它不关心处理细节和请求的传递过程。
注意:责任链模式也叫职责链模式。
在责任链模式中,客户只需要将请求发送到责任链上即可,无须关心请求的处理细节和请求的传递过程,请求会自动进行传递。所以责任链将请求的发送者和请求的处理者解耦了。
- 意图:避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
- 主要解决:职责链上的处理者负责处理请求,客户只需要将请求发送到职责链上即可,无须关心请求的处理细节和请求的传递,所以职责链将请求的发送者和请求的处理者解耦了。
- 何时使用:在处理消息的时候以过滤很多道。
- 如何解决:拦截的类都实现统一接口。
- 关键代码:Handler 里面聚合它自己,在 HandlerRequest 里判断是否合适,如果没达到条件则向下传递,向谁传递之前 set 进去。
1.1 责任链模式UML图
1.2 日常生活中看责任链模式与应用实例
在现实生活中,常常会出现这样的事例:一个请求有多个对象可以处理,但每个对象的处理条件或权限不同。例如,公司员工请假,可批假的领导有部门负责人、副总经理、总经理等,但每个领导能批准的天数不同,员工必须根据自己要请假的天数去找不同的领导签名,也就是说员工必须记住每个领导的姓名、电话和地址等信息,这增加了难度。即是存在着请求发送者(员工请假)与多个请求处理者(部门负责人、副总经理、总经理等)耦合在一起的问题。
- 红楼梦中的"击鼓传花"。
- JS 中的事件冒泡。
- JAVA WEB 中 Apache Tomcat 对 Encoding 的处理,Struts2 的拦截器,jsp servlet 的 Filter。
1.3 Java代码实现
将如上的员工请假的例子,Java代码实现如下:
/**
* @author tbb
* 管理者
*/
public abstract class Superintendent
{
abstract void handleLeave(Request request);
public Superintendent getSuperior() {
return superior;
}
public void setSuperior(Superintendent superior) {
this.superior = superior;
}
private Superintendent superior;
}
/**
* @author tbb
* 主任
*/
public class Director extends Superintendent
{
@Override
void handleLeave(Request request)
{
if (request.getAskForLeaveDay() <= 3)
{
System.out.println("主任开始处理....");
System.out.println(request.getAskForLeaveReason() + "已批准");
}
else
{
System.out.println("主任处理不了,需上级处理....");
if (getSuperior() != null)
{
getSuperior().handleLeave(request);
}
}
}
}
/**
* @author tbb
* 经理
*/
public class Manager extends Superintendent
{
@Override
void handleLeave(Request request)
{
if (request.getAskForLeaveDay() <= 5)
{
System.out.println("经理开始处理....");
System.out.println(request.getAskForLeaveReason() + "已批准");
}
else
{
System.out.println("经理处理不了,需上级处理....");
if (getSuperior() != null)
{
getSuperior().handleLeave(request);
}
}
}
}
/**
* @author tbb
* 人力资源部门
*/
public class HRDepartment extends Superintendent
{
@Override
void handleLeave(Request request)
{
if (request.getAskForLeaveDay() > 5)
{
System.out.println("人力资源部门开始处理....");
System.out.println(request.getAskForLeaveReason() + "已批准");
}
else if(request.getAskForLeaveDay() >= 300)
{
System.out.println("请假天数超过300天,无法批准....");
}
}
}
/**
* @author tbb
* 请假信息类
*/
public class Request
{
private String staffName;
private String askForLeaveReason;
private int askForLeaveDay;
public String getAskForLeaveReason() {
return askForLeaveReason;
}
public void setAskForLeaveReason(String askForLeaveReason) {
this.askForLeaveReason = askForLeaveReason;
}
public int getAskForLeaveDay() {
return askForLeaveDay;
}
public void setAskForLeaveDay(int askForLeaveDay) {
this.askForLeaveDay = askForLeaveDay;
}
public Request(String staffName, String askForLeaveReason, int askForLeaveDay) {
super();
this.staffName = staffName;
this.askForLeaveReason = askForLeaveReason;
this.askForLeaveDay = askForLeaveDay;
}
public String getStaffName() {
return staffName;
}
public void setStaffName(String staffName) {
this.staffName = staffName;
}
}
public class Test
{
public static void main(String[] args)
{
HRDepartment hrDepartment = new HRDepartment();
Manager manager = new Manager();
Director director = new Director();
director.setSuperior(manager);
manager.setSuperior(hrDepartment);
System.out.println("<----张三员工向主任请假---->");
director.handleLeave(new Request("张三", "病假", 1));
System.out.println("<----李四员工向主任请假---->");
director.handleLeave(new Request("李四", "婚假", 5));
System.out.println("<----王五员工向主任请假---->");
director.handleLeave(new Request("王五", "产假", 180));
/*
<----张三员工向主任请假---->
主任开始处理>>>>>>>>
病假已批准
<----李四员工向主任请假---->
主任处理不了,需上级处理>>>>>>>>
经理开始处理>>>>>>>>
婚假已批准
<----王五员工向主任请假---->
主任处理不了,需上级处理>>>>>>>>
经理处理不了,需上级处理>>>>>>>>
人力资源部门开始处理>>>>>>>>
产假已批准
*/
}
}
2、责任链模式在源码中的应用
2.1 Dubbo源码中责任链模式体现
Dubbo构建过滤器链
生产者和消费者最终执行对象都是过滤器链路最后一个节点,整个链路包含多个过滤器进行业务处理。我们看看生产者和消费者默认过滤器链路。
# 生产者过滤器链路
EchoFilter > ClassloaderFilter > GenericFilter > ContextFilter >
TraceFilter > TimeoutFilter > MonitorFilter > ExceptionFilter > AbstractProxyInvoker
# 消费者过滤器链路
ConsumerContextFilter > FutureFilter > MonitorFilter > DubboInvoker
ProtocolFilterWrapper作为链路生成核心通过匿名类方式构建过滤器链路,我们以消费者构建过滤器链路为例
public class ProtocolFilterWrapper implements Protocol {
private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
// invoker = DubboInvoker
Invoker<T> last = invoker;
// 查询符合条件过滤器列表
List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);
if (!filters.isEmpty()) {
for (int i = filters.size() - 1; i >= 0; i--) {
final Filter filter = filters.get(i);
final Invoker<T> next = last;
// 构造一个简化Invoker
last = new Invoker<T>() {
@Override
public Class<T> getInterface() {
return invoker.getInterface();
}
@Override
public URL getUrl() {
return invoker.getUrl();
}
@Override
public boolean isAvailable() {
return invoker.isAvailable();
}
@Override
public Result invoke(Invocation invocation) throws RpcException {
// 构造过滤器链路
Result result = filter.invoke(next, invocation);
if (result instanceof AsyncRpcResult) {
AsyncRpcResult asyncResult = (AsyncRpcResult) result;
asyncResult.thenApplyWithContext(r -> filter.onResponse(r, invoker, invocation));
return asyncResult;
} else {
return filter.onResponse(result, invoker, invocation);
}
}
@Override
public void destroy() {
invoker.destroy();
}
@Override
public String toString() {
return invoker.toString();
}
};
}
}
return last;
}
@Override
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
// RegistryProtocol不构造过滤器链路
if (Constants.REGISTRY_PROTOCOL.equals(url.getProtocol())) {
return protocol.refer(type, url);
}
Invoker<T> invoker = protocol.refer(type, url);
return buildInvokerChain(invoker, Constants.REFERENCE_FILTER_KEY, Constants.CONSUMER);
}
}
2.2 Netty源码中责任链模式体现
Netty的责任链设计
netty的pipeline设计,就采用了责任链设计模式, 底层采用双向链表的数据结构, 将链上的各个处理器串联起来
客户端每一个请求的到来,netty都认为,pipeline中的所有的处理器都有机会处理它,因此,对于入栈的请求,全部从头节点开始往后传播,一直传播到尾节点(来到尾节点的msg会被释放掉)
netty的责任链模式中的组件
- 责任处理器接口
- pipeline中的处理器都它的具体实现
- 添加删除责任处理器的接口
- 上下文
- 通过这个上下文,可以获得需要的数据,属性
- 责任终止机制
- pipeline中的每一个节点,都可以终止事件的传播
netty的责任处理器接口
责任处理器接口, pipeline中的所有的handler的顶级抽象接口,它规定了所有的handler统一要有添加,移除,异常捕获的行为
public interface ChannelHandler {
// todo 当handler被添加到真实的上下文中,并且准备处理事件时被调用
// todo handler 被添加进去的回调
void handlerAdded(ChannelHandlerContext ctx) throws Exception;
// todo 是 handler 被移出的后的 回调
void handlerRemoved(ChannelHandlerContext ctx) throws Exception;
@Deprecated
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
@Inherited
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Sharable {
// no value
}
}
netty对责任处理接口,做了更细粒度的划分, 处理器被分成了两种, 一种是站处理器
ChannelInboundHandler
,另一种是出站处理器ChannelOutboundHandler
,这两个接口都继承自ChannelHandler
添加删除责任处理器的接口
netty中所有的处理器最终都在添加在pipeline上,所以,添加删除责任处理器的接口的行为 netty在channelPipeline中的进行了规定
public interface ChannelPipeline
extends ChannelInboundInvoker, ChannelOutboundInvoker, Iterable<Entry<String, ChannelHandler>> {
ChannelPipeline addFirst(String name, ChannelHandler handler);
ChannelPipeline addFirst(EventExecutorGroup group, String name, ChannelHandler handler);
ChannelPipeline addLast(String name, ChannelHandler handler);
ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler);
ChannelPipeline addBefore(String baseName, String name, ChannelHandler handler);
...
上下文
pipeline中的handler被封装进了上下文中,如下, 通过上下文,可以轻松拿到当前节点所属的channel, 以及它的线程执行器
// todo AttributeMap -- 让ChannelHandlerContext 可以存储自定义的属性
// todo ChannelInboundInvoker -- 让ChannelHandlerContext 可以进行 InBound事件的传播,读事件,read 或者是 注册事件 active事件
// todo ChannelOutboundInvoker -- 让ChannelHandlerContext 可以传播写事件
public interface ChannelHandlerContext extends AttributeMap, ChannelInboundInvoker, ChannelOutboundInvoker {
// todo 获取ChannelHandlerContext所对应的这个Channel对象
Channel channel();
// todo 获取事件执行器
EventExecutor executor();
...
责任终止机制
- 在pipeline中的任意一个节点,只要我们不手动的往下传播下去,这个事件就会终止传播在当前节点
- 对于入站数据,默认会传递到尾节点,进行回收,如果我们不进行下一步传播,事件就会终止在当前节点,别忘记回收msg
- 对于出站数据,用header节点的使用unsafe对象,把数据写会客户端也意味着事件的终止
事件的传播
底层事件的传播使用的就是针对链表的操作
private AbstractChannelHandlerContext findContextInbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.next;
} while (!ctx.inbound);
return ctx;
}
3、责任链模式的优缺点
3.1 优点
- 降低耦合度。它将请求的发送者和接收者解耦。
- 简化了对象。使得对象不需要知道链的结构。
- 增强给对象指派职责的灵活性。通过改变链内的成员或者调动它们的次序,允许动态地新增或者删除责任。
- 增加新的请求处理类很方便。
3.2 缺点
- 不能保证请求一定被接收。
- 系统性能将受到一定影响,而且在进行代码调试时不太方便,可能会造成循环调用。
- 可能不容易观察运行时的特征,有碍于除错。
3.3 使用场景
- 有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
- 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
- 可动态指定一组对象处理请求。
3.4 注意事项
在 JAVA WEB 中遇到很多应用。
4、职责链模式存在以下两种情况
- 纯的职责链模式:一个请求必须被某一个处理者对象所接收,且一个具体处理者对某个请求的处理只能采用以下两种行为之一:自己处理(承担责任);把责任推给下家处理。
- 不纯的职责链模式:允许出现某一个具体处理者对象在承担了请求的一部分责任后又将剩余的责任传给下家的情况,且一个请求可以最终不被任何接收端对象所接收。
参考文章:
https://blog.csdn.net/atu1111/article/details/105558539/
https://blog.csdn.net/woshixuye/article/details/106607737
https://www.cnblogs.com/ZhuChangwu/p/11241304.html#2088978748
http://c.biancheng.net/view/1383.html
https://www.runoob.com/design-pattern/chain-of-responsibility-pattern.htm