设计模式之责任链模式

责任链模式(Chain of Responsibility Pattern)

【情景模式】

在处理MQTT消息的时候,需要根据不同的Topic进行不同的处理,平时我们可以直接使用简单工厂。如下:

//消息处理接口
public interface MessageHandler {
    void handleMessage(String message);
}

定义具体的消息处理类:

public class Topic1Handler implements MessageHandler {
    @Override
    public void handleMessage(String message) {
        System.out.println("处理test1消息");
    }
}
public class Topic2Handler implements MessageHandler {

    @Override
    public void handleMessage(String message) {
        System.out.println("处理Test2消息");
    }
}
public class Topic3Handler implements MessageHandler {

    @Override
    public void handleMessage(String message) {
        System.out.println("处理Test3消息");
    }
}

定义简单工厂,根据不同的topic返回不同的对象:

public class MessageHandlerFactory {

    private static Topic1Handler topic1Handler = new Topic1Handler();

    private static Topic2Handler topic2Handler = new Topic2Handler();

    private static Topic3Handler topic3Handler = new Topic3Handler();


    public static MessageHandler createMessageHandler(String topic) {
        switch (topic) {
            case "/topic/topic1/":
                return topic1Handler;
            case "/topic/topic2/":
                return topic2Handler;
            case "/topic/topic3/":
                return topic3Handler;
            default:
                throw new UnsupportedOperationException("无法处理的topic");
        }
    }
}

使用方法如下:

public class Main {

    public static void main(String[] args) {
        MessageHandler messageHandler=MessageHandlerFactory.createMessageHandler("/topic/topic1/");
        messageHandler.handMessage("message");
    }
}

以上做法非常简单,但是存在一个小问题,那就是如果需要处理10几个不同的topic,还有简单工厂合适么?你会看到在这里类中,10多个case,简单工厂便变得不简单。

此时我们可以换一种处理方式:

首先定义一个抽象类,抽象类中定义了处理逻辑,如果当前类能处理这个消息,则直接处理次消息,如果无法处理此消息,则转给下一个处理类,

public abstract class MessageHandler {

    private MessageHandler nextHandler;

    protected abstract void handMessageInterval(String message);

    protected void handMessage(String topic, String message) {
        if (match(topic)) {
            handMessageInterval(message);
        } else if (nextHandler != null) {
            nextHandler.handMessage(topic,message);
        } else {
            throw new UnsupportedOperationException("无法处理的Topic");
        }
    }

    void setNextHandler(MessageHandler handler) {
        this.nextHandler = handler;
    }

    abstract boolean match(String topic);
}

然后定义各个具体处理类,他们继承自抽象类:

public class Topic1Handler extends MessageHandler{

    @Override
    protected void handMessageInterval(String message) {
        System.out.println("处理topic1消息");
    }

    @Override
    boolean match(String topic) {
        return "/topic/topic1/".equals(topic);
    }
}
public class Topic2Handler extends MessageHandler {
    @Override
    protected void handMessageInterval(String message) {
        System.out.println("处理Topic2消息");
    }

    @Override
    boolean match(String topic) {
        return "/topic/topic2/".equals(topic);
    }
}
public class Topic3Handler extends MessageHandler {

    @Override
    protected void handMessageInterval(String message) {
        System.out.println("处理Topic3消息");
    }

    @Override
    boolean match(String topic) {
        return "/topic/topic3/".equals(topic);
    }
}

使用方法如下:

public class Main {

    public static void main(String[] args) {
        Topic1Handler topic1Handler=new Topic1Handler();
        Topic2Handler topic2Handler=new Topic2Handler();
        Topic3Handler topic3Handler=new Topic3Handler();

        topic1Handler.setNextHandler(topic2Handler);
        topic2Handler.setNextHandler(topic3Handler);


        topic1Handler.handMessage("/topic/topic1/","message");

    }
}


可以看到使用责任链可以将判断分散到每个子类中,这样即使子类再多,也不会让代码变得臃肿。

上面的代码便是责任链模式的使用。

责任链模式在各个系统中都有过使用,虽然责任链比较好用,但是责任链有一个问题在于如何将每个链节点整合起来成为一个真正的链。

Tomcat:

责任链模式在Tomcat中被应用于对每一个Request进行前置处理。

定义处理接口

//处理接口
public interface Filter {
    public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain chain);

}

我们可以doFilter中还包含一个FiltterChain参数

定义责任链接口

public interface FilterChain {

    public void doFilter(ServletRequest request, ServletResponse response);

}

实现责任链:

public final class ApplicationFilterChain implements FilterChain{

    private ApplicationFilterConfig[] filters = new ApplicationFilterConfig[0];

    private int pos = 0;

    @Override
    private void doFilter(ServletRequest request,
                                  ServletResponse response) {

        // Call the next filter if there is one
        if (pos < n) {
            ApplicationFilterConfig filterConfig = filters[pos++];
            Filter filter = filterConfig.getFilter();
           filter.doFilter(request, response, this);
            return;
        }

        // We fell off the end of the chain -- call the servlet instance
         servlet.service(request, response);
    }

   void addFilter(ApplicationFilterConfig filterConfig) {

        // Prevent the same filter being added multiple times
        for(ApplicationFilterConfig filter:filters)
            if(filter==filterConfig)
                return;

        if (n == filters.length) {
            ApplicationFilterConfig[] newFilters =
                new ApplicationFilterConfig[n + INCREMENT];
            System.arraycopy(filters, 0, newFilters, 0, n);
            filters = newFilters;
        }
        filters[n++] = filterConfig;

    }
}

责任节点的实现:

public class SessionInitializerFilter implements Filter {

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){
        //业务逻辑,。初始化session
        ((HttpServletRequest)request).getSession();
        //交给下一个节点
        chain.doFilter(request, response);
    }
}

可以看到,Tomcat使用的是将责任链和责任节点分为两个接口,责任链是真正对外开放的类,而内部责任节点每次处理完成后都会通知责任链进行下一个处理,直到遍历完责任链中所有节点。

Tomcat的这种方式就相当于通过一个游标(ops)标记当前执行的节点,每完成一个节点,游标就加一(ops++)。

但是这种方式如果是放在我们经常使用的Spring框架中得稍加注意,因为在Spring中每个类默认都是单例的,如果并发请求责任链,那么游标就会出错。

因此,在Spring中,我们可以通过Spring的依赖出入,每写一个节点,就注入上一个节点并管理起来。

public class SessionInitializerFilter implements Filter {
    @Autowired
    private final CommonFilter commonFilter;

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain){
        //业务逻辑,。初始化session
        ((HttpServletRequest)request).getSession();
        //交给下一个节点
        commonFilter.doFilter(request, response);
    }
}

也或者修改下Tomcat的方式,直接在责任链类中遍历所有的节点:

   @Override
    private void doFilter(ServletRequest request,
                                  ServletResponse response) {
        for(Filter fileter:filters){
            //如果需要提前结束可以这样写,否则直接遍历不用判断
            if(filter.match()){
                filter.doFilter(request,response);
                break;
            }else{
                continue;
            }
        }
    }

以上示例代码均在:https://github.com/dengchengchao/design-pattern


责任链模式

责任链模式是典型的用来解决多个if-else分支的模式。

定义:

使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系,将这些对象连成一条链,并沿着这条链传递该请求,直到有对象处理它为止。

责任链模式属于行为型模式

UML

image

责任链模式角色:

◊ Handler:抽象处理者
​ handler 定义了处理请求的接口,handler知道,下一个处理者是谁,如果自己无法处理请求,就转给下一个处理者。

◊ concreteHandler :具体的处理者
​ 具体的处理者是处理请求的具体角色。具体处理者接到请求后,可以选择将请求处理掉,或者将请求传给下家

◊ Client

请求者角色,就是向第一个具体的handler发送请求的角色,并连接好责任链。

应用场景

责任链模式主要是应用在同一个请求,可能会有很多分支来处理这个请求,责任模式分散了判断请求的代码。同时责任链模式可以提前中断请求,可以不中断请求直接遍历所有处理节点。

责任链模式还有中用法是层层处理,就像流水线一样,第一层处理这个请求后交给第二层处理。

优点

  • 责任链模式能让各个类更加专注自己的责任,也只用关心自己的责任

  • 责任链模式可以动态修改处理节点

缺点

  • 推卸责任也可能导致处理延迟
    我们可以责任链模式需要在责任链上传播责任,直至找到合适的处理对象。这样提高了程序的灵活性,但同时也出现了处理的延迟,因为有一个寻找的过程。所以需要低延迟的情况下,就不应该使用责任链模式
  • 不容易观察运行时的特征,有碍于排查错误