【设计模式】行为型模式其一: 职责链模式

41 阅读7分钟

职责链模式

这是我们行为型模式的第一节,所以我们先了解了解行为型模式

行为型模式

行为型模式(Behavioral Pattern) 关注系统中对象之间的交互,研究系统在运行时对象之间的相互通信与协作,进一步明确对象的职责

行为型模式:不仅仅关注类和对象本身,还重点关注它们之间的相互作用职责划分

行为型模式分类

类行为型模式

  • 使用继承关系在几个类之间分配行为,主要通过多态等方式来分配父类与子类的职责

对象行为型模式

  • 使用对象的关联关系来分配行为,主要通过对象关联等方式来分配两个或多个类的职责

职责链概述

image.png

辅导员、系主任、院长、校长都可以处理奖学金申请表,他们构成一个处理申请表的链式结构,申请表沿着这条链进行传递,这条链就称为职责链

职责链可以是一条直线一个环或者一个树形结构最常见的职责链是直线型,即沿着一条单向的链来传递请求

环形链比如打牌,一轮一轮的出牌。

image.png

树状图比如上级领导发布命令,一层一层的向下传递,直到底层员工。

职责链模式的定义

  • 避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求。将接收请求的对象连接成一条链,并且沿着这条链传递请求直到有一个对象能够处理它为止
  • 客户端无须关心请求的处理细节以及请求的传递,只需将请求发送到链上,将请求的发送者和请求的处理者解耦

职责链模式的结构

image.png

职责链模式包含以下2个角色:

  • Handler(抽象处理者)
  • ConcreteHandler(具体处理者)

通过案例来学习

需求:

某企业的SCM(Supply Chain Management,供应链管理)系统中包含一个采购审批子系统。

该企业的采购审批是分级进行的,即根据采购金额的不同由不同层次的主管人员来审批,

主任可以审批5万元以下(不包括5万元)的采购单,

经理可以审批5万元至10万元(不包括10万元)的采购单,

董事长可以审批10万元至50万元(不包括50万元)的采购单

为了从程序简单, 只写了三个具体处理者

请求类

//采购单:请求类
//被处理的对象类

public class PurchaseRequest {
   private double amount;  //采购金额
   private int number;  //采购单编号
   private String purpose;  //采购目的

   public PurchaseRequest(double amount, int number, String purpose) {
      this.amount = amount;
      this.number = number;
      this.purpose = purpose;
   }
   public double getAmount(){
      return this.amount;
   }

   public int getNumber() {
      return number;
   }

   public String getPurpose() {
      return purpose;
   }
}

抽象处理类

既然是职责链,必须得要有抽象类,这样才能正确设置链条。

//审批者类:抽象处理者
//里面需要书写设置下家的方法抽象处理方法

public abstract class Approves {
   protected Approves successor; //定义后继对象
   protected String name; //审批者姓名
   public Approves(String name) {
      this.name = name;
   }
   //设置后继者
   public void setSuccessor(Approves successor) {
      this.successor = successor;
   }
   //抽象请求处理方法
   public abstract void processRequest(PurchaseRequest request);
}

具体处理者

具体处理者类需要有详细的处理方法,并且拥有条件判断,拥有自身处理能力和转发下家的能力。

//主任类:具体处理者

public class Director extends Approves {
   public Director(String name) {
      super(name);
   }
    //具体请求处理方法
   public void processRequest(PurchaseRequest request) {
      if (request.getAmount() < 50000) {
         System.out.println("主任" + this.name + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。");  //处理请求
      }
      else {
         this.successor.processRequest(request);  //转发请求
      }
   }
}

//经理类:具体处理者

public class Manager extends Approves {
   public Manager(String name) {
      super(name);
   }

  //具体请求处理方法
   public void processRequest(PurchaseRequest request) {
      if (request.getAmount() < 100000) {
         System.out.println("经理" + this.name + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。");  //处理请求
      }
      else {
         this.successor.processRequest(request);  //转发请求
      }
   }
}

//董事长类:具体处理者

public class President extends Approves {
   public President(String name) {
      super(name);
   }

   //具体请求处理方法
   public void processRequest(PurchaseRequest request) {
      if (request.getAmount() < 500000) {
         System.out.println("董事长" + this.name + "审批采购单:" + request.getNumber() + ",金额:" + request.getAmount() + "元,采购目的:" + request.getPurpose() + "。");  //处理请求
      }
      else {
         System.out.println("太贵了,不买");
      }
   }
}

客户端调用

public class Client {
   public static void main(String[] args) {
      Approves director, manager, present;
      director = new Director("化骨龙");
      manager = new Manager("傻不拉几");
      present = new President("阿松大");
      //创建职责链
      director.setSuccessor(manager);
      manager.setSuccessor(present);
      //创建采购单
      PurchaseRequest pr1 = new PurchaseRequest(45000,10001,"据为己有");
      director.processRequest(pr1);

      PurchaseRequest pr2 = new PurchaseRequest(60000,10002,"购买大保健");
      director.processRequest(pr2);

      PurchaseRequest pr3 = new PurchaseRequest(160000,10003,"钱太多了");
      director.processRequest(pr3);

      PurchaseRequest pr4 = new PurchaseRequest(800000,10004,"你不管");
      director.processRequest(pr4);
   }
}

输出及分析

主任化骨龙审批采购单:10001,金额:45000.0元,采购目的:据为己有。
经理傻不拉几审批采购单:10002,金额:60000.0元,采购目的:购买大保健。
董事长阿松大审批采购单:10003,金额:160000.0元,采购目的:钱太多了。
太贵了,不买

分析:

  1. 我们完成了基本的功能,里面的转发是由采购所需money来决定的,正确的完成了转发功能。
  2. 但是,这里我们在客户端进行了职责链的创建,这显然是不合理的,一般都是程序自动创建或读取配置文件来创建。
  3. 我们需要给客户端暴露一个接口,它不用管具体是怎么调用的,因此,需要改进代码。

对代码进行改进

XML文件

因为有构造函数,所以我需要给XML文件设置属性

<?xml version="1.0" encoding="UTF-8" ?>
<chain>
    <classname value="化骨龙">designpatterns.cor.Director</classname>
    <classname value="傻不拉几">designpatterns.cor.Manager</classname>
    <classname value="阿松大">designpatterns.cor.President</classname>
</chain>

XML文件读取类

该XML文件需要读取classname来获得具体处理者的完整类名,从而通过反射去生成对象,但是呢,我们这里使用的是有参构造函数,所以还要读取classname标签里的value属性。

并将value作为参数传入构造函数。

最后,返回一个职责链数组对象,启动类在启动时将链条构造好。

暴露出一个对象用于客户端调用。

public class XMLUtil {
    public static Object[] getChains(){
        Object[] objects;
        DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newDefaultInstance();
        DocumentBuilder documentBuilder = null;
        try {
            documentBuilder = documentBuilderFactory.newDocumentBuilder();
        } catch (ParserConfigurationException e) {
            throw new RuntimeException(e);
        }
        Document document = null;
        try {
            document = documentBuilder.parse("./chain.xml");
        } catch (SAXException | IOException e) {
            throw new RuntimeException(e);
        }

        NodeList classnames = document.getElementsByTagName("classname");

        objects = new Object[classnames.getLength()];

        for (int i = 0; i < classnames.getLength(); i++) {
            Node node = classnames.item(i);
            String className = node.getTextContent();
            String value = node.getAttributes().getNamedItem("value").getNodeValue();;
            try {
                Class<?> aClass = Class.forName(className);
                Object o = aClass.getConstructor(String.class).newInstance(value);
                objects[i] = o;
            } catch (ClassNotFoundException | InvocationTargetException | InstantiationException |
                     IllegalAccessException | NoSuchMethodException e) {
                throw new RuntimeException(e);
            }
        }
        return objects;
    }
}

客户端改造

注意: 链条的创建应该在服务端的启动类中创建,但是为了方便,我这里还是在客户端类中创建,大家能理解意思就行,实际操作时放在正确的位置。

public class Client {
   public static void main(String[] args) {
      //创建采购单
      PurchaseRequest pr1 = new PurchaseRequest(45000,10001,"据为己有");

      PurchaseRequest pr2 = new PurchaseRequest(60000,10002,"购买大保健");

      PurchaseRequest pr3 = new PurchaseRequest(160000,10003,"钱太多了");

      PurchaseRequest pr4 = new PurchaseRequest(800000,10004,"你不管");

      //创建职责链
      Object[] chains = XMLUtil.getChains();
      for (int i = 0; i < chains.length-1; i++) {
         Approves approve = (Approves) chains[i];
         Approves approveNext = (Approves) chains[i+1];
         approve.setSuccessor(approveNext);
      }
      Approves approve = (Approves) chains[0];

      approve.processRequest(pr1);
      approve.processRequest(pr2);
      approve.processRequest(pr3);
      approve.processRequest(pr4);
   }
}

输出及分析

可以发现结果正确输出:

主任化骨龙审批采购单:10001,金额:45000.0元,采购目的:据为己有。
经理傻不拉几审批采购单:10002,金额:60000.0元,采购目的:购买大保健。
董事长阿松大审批采购单:10003,金额:160000.0元,采购目的:钱太多了。
太贵了,不买

完成上面的改进之后,我们就不用再修改源代码,
当我们需要修改链条长度或添加链条,可以直接修改配置文件。

思考

其实链条的创建也可以不在启动类中,而是放在配置文件读取类中:

启动类中创建的好处:

  1. 代码分离,耦合度低,配置文件类与职责链类解耦。
  2. 职责分离 ,配置文件类只负责读取配置,启动类负责创建职责链。
  3. 易扩展。启动类可以被替换或者复用。

缺点:

  1. 逻辑不连贯
  2. 需要读取配置文件的返回值,编写麻烦

配置文件中创建的好处:

  1. 逻辑连贯,因为就在配置文件中创建了具体处理对象
  2. 不需要将读取的配置信息通过参数或者返回值传递给外部。

缺点:

  1. 配置文件类和职责链类耦合度高。
  2. 读取配置文件的类,却做了业务相关的事情,职责偏差。

模式优缺点

模式优点:

  • 使得一个对象无须知道是其他哪一个对象处理其请求,降低了系统的耦合度
  • 可简化对象之间的相互连接
  • 给对象职责的分配带来更多的灵活性
  • 增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在服务端重新建链即可

模式缺点

  • 不能保证请求一定会被处理
  • 对于比较长的职责链,系统性能将受到一定影响,在进行代码调试时不太方便
  • 如果建链不当,可能会造成循环调用,将导致系统陷入死循环

适用环境

  • 有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定
  • 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求
  • 可动态指定一组对象处理请求

比如权限验证,先验证你的身份,再允许你执行操作。