设计模式之责任链模式

/ 设计模式 / 没有评论 / 370浏览

今天我们介绍一下设计模式中的责任链模式。看到这个模式的名字,我们就知道这个设计模式和责任有关。提到责任按照百度百科中的解释就是做好自己的分内事。那站在我们软件设计角度来看,做好自己分内事也就是不同的类应当处理不同的业务。所以责任链模式通俗的讲也就是将一个请求交给一堆不同的类来处理,每个类只处理自己负责的任务,而自己不负责的任务交给其它的类处理,这样这些处理请求的类就无形之中形成了一个链条,所以该模式叫责任链模式(备注:这是我自己对责任链模式的理解,并不是官方对责任链模式的解释,官方的解释在后面的内容中在做介绍)。


在这一篇中我们还是和介绍其它设计模式方式一样,我们以一个具体的需求为例。通过对这个需求的分析,我们来介绍今天的责任链模式。

需求:在公司对外邮箱中常常有以下几类邮件。面试邮件、商务邮件、投诉邮件等。我们的需求是将这些不同类的邮件转发给不同的部门,例如面试邮件转发给人事部,商务邮件转发给市场部,投诉邮件转发给总裁办等。

我们通过简单的需求分析就会知道,上面的需求首先会抽象出一个邮件类来保存公司邮箱中的邮件信息,其次既然是将邮件转发给不同的部门,所以我们至少会抽象出一个方法来处理转发邮件的方法,既然方法已经抽象出来,那么当然也会抽象出这个处理邮件转发的类。我们先按照上面的分析编写一下代码。

Email:

/**
 * 抽象出邮件类
 */
public class Email {

    /**
     * 发送人
     */
    private String sender;

    /**
     * 邮件内容
     */
    private String content;

    /**
     * 邮件回复
     */
    private String answer;

    /**
     * 邮件状态
     */
    private String message;

    public String getSender() {
        return sender;
    }

    public void setSender(String sender) {
        this.sender = sender;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getAnswer() {
        return answer;
    }

    public void setAnswer(String answer) {
        this.answer = answer;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    @Override
    public String toString() {
        return "Email{" +
                "sender='" + sender + '\'' +
                ", content='" + content + '\'' +
                ", answer='" + answer + '\'' +
                ", message='" + message + '\'' +
                '}';
    }
}

Handle:

/**
 * 抽象邮件转发类
 */
public class Handle {

    /**
     * 抽象转发邮件方法
     * @param emails
     */
    public void forward(List<Email> emails) {
        for (Email email : emails) {
            // 面试邮件
            if("mianshi".equals(email.getSender())) {
                email.setMessage("已转发人事部");
            }
        }
    }
}

Main:

/**
 * 测试类
 */
public class Main {

    public static void main(String[] args) {
        // 由于我们处理的是一批的邮件,所以这里我们组装多条不同的邮件信息来模拟从数据库中查询
        List<Email> emails = new ArrayList<Email>();

        // 面试邮件
        Email interview = new Email();
        interview.setSender("mianshi");
        interview.setContent("面试");

        // 商务邮件
        Email business = new Email();
        business.setSender("shangwu");
        business.setContent("务");

        // 投诉邮件
        Email complaints = new Email();
        complaints.setSender("tousu");
        complaints.setContent("投诉");

        emails.add(interview);
        emails.add(business);
        emails.add(complaints);

        System.out.println(String.format("邮件转发前:【%s】", emails));

        // 转发邮件
        Handle handle = new Handle();
        handle.forward(emails);

        System.out.println(String.format("邮件转发后:【%s】", emails));

    }

}

日志:

邮件转发前:【[Email{sender='mianshi', content='面试', answer='null', message='null'}, Email{sender='shangwu', content='商务', answer='null', message='null'}, Email{sender='tousu', content='投诉', answer='null', message='null'}]】
邮件转发后:【[Email{sender='mianshi', content='面试', answer='null', message='已转发人事部'}, Email{sender='shangwu', content='商务', answer='null', message='null'}, Email{sender='tousu', content='投诉', answer='null', message='null'}]】

我们现在简单的处理了人事部邮件的请求,而其它邮件的请求我们没有做任何处理,所以其它邮件没有做任何变化。如们我们现在想要处理商务邮件的话,那么我们比较简单只需要在Handle类中在添加一个else判断就可以了。既然我们已经介绍过了设计模式,所以我们就要有设计模式的思想,上面的代码虽然可以实现需求,但这样的代码是不方便扩展的。因为每次修改时,都可能会对其它已经编写好的功能产生影响,所以在设计模式中不推荐这样做。那怎么做呢?我想这里就不用我多说了,答案一定是多态,也就是用父子类的方式代替if else的方式。因为之前已经多次介绍了,所以这里就不在做过多的介绍了,我们直接看代码。

Forward:

/**
 * 邮件转发统一接口
 */
public interface Forward {

    /**
     * 邮件统一转发方法
     *
     * @param emails
     */
    public void handle(List<Email> emails);
}

InterviewForward:

/**
 * 面试转发处理类
 */
public class InterviewForward implements Forward {


    public void handle(List<Email> emails) {
        for (Email email : emails) {
            // 面试邮件
            if ("mianshi".equals(email.getSender())) {
                email.setMessage("已转发人事部");
            }
        }
    }
}

BusinessForward:

/**
 * 商务邮件处理类
 */
public class BusinessForward implements Forward {


    public void handle(List<Email> emails) {
        for (Email email : emails) {
            // 商务邮件
            if ("shangwu".equals(email.getSender())) {
                email.setMessage("已转发市场部");
            }
        }
    }
}

Handle:

/**
 * 抽象邮件转发类
 */
public class Handle {

    /**
     * 因为可能会有多个处理类所以这里用数组
     */
    private Forward[] forwards = {new InterviewForward(), new BusinessForward()};

    /**
     * 抽象转发邮件方法
     *
     * @param emails
     */
    public void forward(List<Email> emails) {

        for (Forward forward : forwards) {
            // 调用不同处理类的转发邮件
            forward.handle(emails);
        }
    }
}

由于我们测试类没有任何改动,所以我们现在直接看日志输出:

邮件转发前:【[Email{sender='mianshi', content='面试', answer='null', message='null'}, Email{sender='shangwu', content='商务', answer='null', message='null'}, Email{sender='tousu', content='投诉', answer='null', message='null'}]】
邮件转发后:【[Email{sender='mianshi', content='面试', answer='null', message='已转发人事部'}, Email{sender='shangwu', content='商务', answer='null', message='已转发市场部'}, Email{sender='tousu', content='投诉', answer='null', message='null'}]】

这样我们相比上一版代码来说,程序扩展要方便多了。如果我们要添加投诉邮件处理类时,我们只要将这个类继承Forward接口然后在Handle类中添加该处理类即可。但如果这样的话, 有一点不方便我们处理,因为每次新增处理类时,都要修改Handle类,如果我们有多个客户端同时使用这个工具类时,则会出现问题,因为无法保证不同的客户端使用同一样的处理规则。为了解决这样的问题,我们将上面的代码做一下修改,允许客户端可以指定想要的处理类。

Handle:

/**
 * 抽象邮件转发类
 */
public class Handle {

    private List<Forward> forwards = new ArrayList<Forward>();

    /**
     * 添加处理类
     *
     * @param forward
     */
    public void addForward(Forward forward) {
        forwards.add(forward);
    }


    /**
     * 抽象转发邮件方法
     *
     * @param emails
     */
    public void forward(List<Email> emails) {

        for (Forward forward : forwards) {
            // 调用不同处理类的转发邮件
            forward.handle(emails);
        }
    }
}

Main:

/**
 * 测试类
 */
public class Main {

    public static void main(String[] args) {
        // 由于我们处理的是一批的邮件,所以这里我们组装多条不同的邮件信息来模拟从数据库中查询
        List<Email> emails = new ArrayList<Email>();

        // 面试邮件
        Email interview = new Email();
        interview.setSender("mianshi");
        interview.setContent("面试");

        // 商务邮件
        Email business = new Email();
        business.setSender("shangwu");
        business.setContent("商务");

        // 投诉邮件
        Email complaints = new Email();
        complaints.setSender("tousu");
        complaints.setContent("投诉");

        emails.add(interview);
        emails.add(business);
        emails.add(complaints);

        System.out.println(String.format("邮件转发前:【%s】", emails));

        // 转发邮件
        Handle handle = new Handle();
        handle.addForward(new InterviewForward());
        handle.addForward(new BusinessForward());
        handle.forward(emails);

        System.out.println(String.format("邮件转发后:【%s】", emails));

    }

}

日志:

邮件转发前:【[Email{sender='mianshi', content='面试', answer='null', message='null'}, Email{sender='shangwu', content='商务', answer='null', message='null'}, Email{sender='tousu', content='投诉', answer='null', message='null'}]】
邮件转发后:【[Email{sender='mianshi', content='面试', answer='null', message='已转发人事部'}, Email{sender='shangwu', content='商务', answer='null', message='已转发市场部'}, Email{sender='tousu', content='投诉', answer='null', message='null'}]】

这样我们的客户端可能随意的指定我们想要的处理类了。但上面的的代码一个地方可能优化一下,就是我们可以通过一个小技巧,让客户端可以支持链式调用。下面为具体的代码。

Handle:

/**
 * 抽象邮件转发类
 */
public class Handle {

    private List<Forward> forwards = new ArrayList<Forward>();

    /**
     * 添加处理类
     *
     * @param forward
     */
    public Handle addForward(Forward forward) {
        forwards.add(forward);
        return this;
    }


    /**
     * 抽象转发邮件方法
     *
     * @param emails
     */
    public void forward(List<Email> emails) {

        for (Forward forward : forwards) {
            // 调用不同处理类的转发邮件
            forward.handle(emails);
        }
    }
}

Main:

/**
 * 测试类
 */
public class Main {

    public static void main(String[] args) {
        // 由于我们处理的是一批的邮件,所以这里我们组装多条不同的邮件信息来模拟从数据库中查询
        List<Email> emails = new ArrayList<Email>();

        // 面试邮件
        Email interview = new Email();
        interview.setSender("mianshi");
        interview.setContent("面试");

        // 商务邮件
        Email business = new Email();
        business.setSender("shangwu");
        business.setContent("商务");

        // 投诉邮件
        Email complaints = new Email();
        complaints.setSender("tousu");
        complaints.setContent("投诉");

        emails.add(interview);
        emails.add(business);
        emails.add(complaints);

        System.out.println(String.format("邮件转发前:【%s】", emails));

        // 转发邮件
        Handle handle = new Handle();
        handle.addForward(new InterviewForward()).addForward(new BusinessForward());
        handle.forward(emails);

        System.out.println(String.format("邮件转发后:【%s】", emails));

    }

}

日志:

邮件转发前:【[Email{sender='mianshi', content='面试', answer='null', message='null'}, Email{sender='shangwu', content='商务', answer='null', message='null'}, Email{sender='tousu', content='投诉', answer='null', message='null'}]】
邮件转发后:【[Email{sender='mianshi', content='面试', answer='null', message='已转发人事部'}, Email{sender='shangwu', content='商务', answer='null', message='已转发市场部'}, Email{sender='tousu', content='投诉', answer='null', message='null'}]】

上述内容就是设计模式中的责任链模式,下面我们看一下官方的解释:使多个对象都有机会处理同一个请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。