zoukankan      html  css  js  c++  java
  • 职责链(Chain of Responsibility)模式在航空货运中的运用实例

    设计模式这东西,基本上属于“看懂一瞬间,用会好几年”。只有实际开发中,当某一模式很好的满足了业务需求时,才会有真切的感觉。借用一句《闪电侠》中,绿箭侠教导闪电侠的台词:“不是你碰巧遇到了它(指闪电事故),而是它选择你”。

    业务场景:

    航空公司内部对于货运单的价格管理,通常会颁发若干类型的运价文件,典型的有:SpotRate(一票一议)、ContractRate(合同运价)、PublicRate(IATA公布运价)等等,一票运单判断该用何种运价时,通常会按一定的顺序在这几类运价中依次匹配查找,如果匹配成功,则直接返回,使用查找结果中的费率做为计算依据。

    变化点:

    不同的航空公司,内部管理体制不同,支持的运价种类也不同,包括查找运价的顺序也可能略有差异。

    目标:

    为了能尽量少加班,少改代码,要求系统最好能方便的应对这些变化。职责链模式正是为该类场景而生,园友飞林沙已经详解解读了这一模式,参见其博文:

    重温设计模式(三)——职责链模式(chain of responsibility)

    类图:

    RateCluase 为运价条款基本信息

    Airwaybill 为运单基本信息

    这二个类的实例,主要做为查找运价的入口参数

    RateFinder为统一接口,find方法为查找运价,nextFinder的setter/getter用于指定下一个查找者

    XXXRateFinder为具体的实现类,为了简化问题,这里只列了3种基本的实现(实际情况远比这复杂)

    代码:

    入口参数

      1 /***********************************************************************
      2  * Module:  AirwayBill.java
      3  * Author:  jimmy
      4  * Purpose: Defines the Class AirwayBill
      5  ***********************************************************************/
      6 
      7 package murate.test.ratefinder.dto;
      8 
      9 public class AirwayBill {
     10     /**
     11      * 运单前缀
     12      * 
     13      */
     14     private String awbPre;
     15     /**
     16      * 运单号
     17      * 
     18      */
     19     private String awbNo;
     20     /**
     21      * 始发站
     22      * 
     23      */
     24     private String origin;
     25     /**
     26      * 目的站
     27      * 
     28      */
     29     private String dest;
     30     /**
     31      * 代理人帐号
     32      * 
     33      */
     34     private String agentNumber;
     35     /**
     36      * 品名代码
     37      * 
     38      */
     39     private String commodityCode;
     40     /**
     41      * 特货代码
     42      * 
     43      */
     44     private String specialHandlingCode;
     45 
     46     public String getAwbPre() {
     47         return awbPre;
     48     }
     49 
     50     public void setAwbPre(String awbPre) {
     51         this.awbPre = awbPre;
     52     }
     53 
     54     public String getAwbNo() {
     55         return awbNo;
     56     }
     57 
     58     public void setAwbNo(String awbNo) {
     59         this.awbNo = awbNo;
     60     }
     61 
     62     public String getOrigin() {
     63         return origin;
     64     }
     65 
     66     public void setOrigin(String origin) {
     67         this.origin = origin;
     68     }
     69 
     70     public String getDest() {
     71         return dest;
     72     }
     73 
     74     public void setDest(String dest) {
     75         this.dest = dest;
     76     }
     77 
     78     public String getAgentNumber() {
     79         return agentNumber;
     80     }
     81 
     82     public void setAgentNumber(String agentNumber) {
     83         this.agentNumber = agentNumber;
     84     }
     85 
     86     public String getCommodityCode() {
     87         return commodityCode;
     88     }
     89 
     90     public void setCommodityCode(String commodityCode) {
     91         this.commodityCode = commodityCode;
     92     }
     93 
     94     public String getSpecialHandlingCode() {
     95         return specialHandlingCode;
     96     }
     97 
     98     public void setSpecialHandlingCode(String specialHandlingCode) {
     99         this.specialHandlingCode = specialHandlingCode;
    100     }
    101 
    102 }
    View Code
      1 /***********************************************************************
      2  * Module:  RateCluase.java
      3  * Author:  jimmy
      4  * Purpose: Defines the Class RateCluase
      5  ***********************************************************************/
      6 
      7 package murate.test.ratefinder.dto;
      8 
      9 /**
     10  * 运价条款
     11  * 
     12  * 2014-12-24 杨俊明 0.1
     13  * 
     14  */
     15 public class RateCluase {
     16 
     17     /**
     18      * 条款Id
     19      * 
     20      */
     21     private Long clauseId;
     22 
     23     /**
     24      * 条款名称
     25      * 
     26      */
     27     private String clauseName;
     28 
     29     /**
     30      * 运单前缀
     31      */
     32     private String awbPre;
     33 
     34     /**
     35      * 运单号
     36      */
     37     private String awbNo;
     38 
     39     /**
     40      * 始发站
     41      * 
     42      */
     43     private String origin;
     44 
     45     /**
     46      * 目的站
     47      * 
     48      */
     49     private String dest;
     50 
     51     /**
     52      * 代理人帐号
     53      * 
     54      */
     55     private String agentNumber;
     56 
     57     /**
     58      * 品名代码
     59      * 
     60      */
     61     private String commodityCode;
     62 
     63     /**
     64      * 特货代码
     65      * 
     66      */
     67     private String specialHandlingCode;
     68 
     69     public Long getClauseId() {
     70         return clauseId;
     71     }
     72 
     73     public void setClauseId(Long clauseId) {
     74         this.clauseId = clauseId;
     75     }
     76 
     77     public String getClauseName() {
     78         return clauseName;
     79     }
     80 
     81     public void setClauseName(String clauseName) {
     82         this.clauseName = clauseName;
     83     }
     84 
     85     public String getOrigin() {
     86         return origin;
     87     }
     88 
     89     public void setOrigin(String origin) {
     90         this.origin = origin;
     91     }
     92 
     93     public String getDest() {
     94         return dest;
     95     }
     96 
     97     public void setDest(String dest) {
     98         this.dest = dest;
     99     }
    100 
    101     public String getAgentNumber() {
    102         return agentNumber;
    103     }
    104 
    105     public void setAgentNumber(String agentNumber) {
    106         this.agentNumber = agentNumber;
    107     }
    108 
    109     public String getCommodityCode() {
    110         return commodityCode;
    111     }
    112 
    113     public void setCommodityCode(String commodityCode) {
    114         this.commodityCode = commodityCode;
    115     }
    116 
    117     public String getSpecialHandlingCode() {
    118         return specialHandlingCode;
    119     }
    120 
    121     public void setSpecialHandlingCode(String specialHandlingCode) {
    122         this.specialHandlingCode = specialHandlingCode;
    123     }
    124 
    125     public String getAwbPre() {
    126         return awbPre;
    127     }
    128 
    129     public void setAwbPre(String awbPre) {
    130         this.awbPre = awbPre;
    131     }
    132 
    133     public String getAwbNo() {
    134         return awbNo;
    135     }
    136 
    137     public void setAwbNo(String awbNo) {
    138         this.awbNo = awbNo;
    139     }
    140 
    141     public String toString() {
    142         return clauseName;
    143     }
    144 
    145 }
    View Code

    接口:

     1 /***********************************************************************
     2  * Module:  RateFinder.java
     3  * Author:  jimmy
     4  * Purpose: Defines the Interface RateFinder
     5  ***********************************************************************/
     6 
     7 package murate.test.ratefinder.service;
     8 
     9 import java.util.List;
    10 
    11 import murate.test.ratefinder.dto.AirwayBill;
    12 import murate.test.ratefinder.dto.RateCluase;
    13 
    14 /**
    15  * 运价查找接口
    16  * 
    17  */
    18 public interface RateFinder {
    19     /**
    20      * 查找运价条款
    21      * 
    22      * @param airwayBill
    23      *            运单信息
    24      * @param rateClauses
    25      *            运单条款信息
    26      * @return
    27      */
    28     RateCluase find(AirwayBill airwayBill, List<RateCluase> rateClauses);
    29 
    30     RateFinder getNextFinder();
    31 
    32     void setNextFinder(RateFinder value);
    33 
    34 }
    View Code

    3个实现类:

     1 /***********************************************************************
     2  * Module:  SpotRateFinder.java
     3  * Author:  jimmy
     4  * Purpose: Defines the Class SpotRateFinder
     5  ***********************************************************************/
     6 
     7 package murate.test.ratefinder.service.impl;
     8 
     9 import java.util.*;
    10 
    11 import org.springframework.util.StringUtils;
    12 
    13 import murate.test.ratefinder.dto.AirwayBill;
    14 import murate.test.ratefinder.dto.RateCluase;
    15 import murate.test.ratefinder.service.RateFinder;
    16 
    17 /**
    18  * 一票一议运价查找
    19  * 
    20  */
    21 public class SpotRateFinder implements RateFinder {
    22 
    23     RateFinder nextFinder;
    24 
    25     public RateCluase find(AirwayBill airwayBill, List<RateCluase> rateClauses) {
    26 
    27         for (RateCluase clause : rateClauses) {
    28             // 模拟查找逻辑(只要单号匹配成功,就算通过,仅演示)
    29 
    30             if (StringUtils.isEmpty(clause.getAwbPre())
    31                     || StringUtils.isEmpty(clause.getAwbNo())
    32                     || StringUtils.isEmpty(airwayBill.getAwbPre())
    33                     || StringUtils.isEmpty(airwayBill.getAwbNo())) {
    34                 continue;
    35             }
    36             if (clause.getAwbPre().equals(airwayBill.getAwbPre())
    37                     && clause.getAwbNo().equals(airwayBill.getAwbNo())) {
    38                 // 找到了,直接返回
    39                 return clause;
    40             }
    41         }
    42 
    43         // 否则,交给下一个Finder继续查找
    44         return nextFinder.find(airwayBill, rateClauses);
    45 
    46     }
    47 
    48     public RateFinder getNextFinder() {
    49         return nextFinder;
    50     }
    51 
    52     public void setNextFinder(RateFinder value) {
    53         nextFinder = value;
    54     }
    55 
    56 }
    View Code
     1 /***********************************************************************
     2  * Module:  ContractRateFinder.java
     3  * Author:  jimmy
     4  * Purpose: Defines the Class ContractRateFinder
     5  ***********************************************************************/
     6 
     7 package murate.test.ratefinder.service.impl;
     8 
     9 import java.util.*;
    10 
    11 import org.springframework.util.StringUtils;
    12 
    13 import murate.test.ratefinder.dto.AirwayBill;
    14 import murate.test.ratefinder.dto.RateCluase;
    15 import murate.test.ratefinder.service.RateFinder;
    16 
    17 /**
    18  * Contract运价查找者
    19  *
    20  */
    21 public class ContractRateFinder implements RateFinder {
    22     RateFinder nextFinder;
    23 
    24     public RateCluase find(AirwayBill airwayBill, List<RateCluase> rateClauses) {
    25 
    26         for (RateCluase clause : rateClauses) {
    27 
    28             // 模拟查找逻辑(只要代理人帐号匹配成功,就算通过,仅演示)
    29 
    30             if (StringUtils.isEmpty(clause.getAgentNumber())
    31                     || StringUtils.isEmpty(clause.getAgentNumber())) {
    32                 continue;
    33             }
    34 
    35             if (clause.getAgentNumber().equals(airwayBill.getAgentNumber())) {
    36                 // 找到了,直接返回
    37                 return clause;
    38             }
    39         }
    40 
    41         // 否则,交给下一个Finder继续查找
    42         return nextFinder.find(airwayBill, rateClauses);
    43 
    44     }
    45 
    46     public RateFinder getNextFinder() {
    47         return nextFinder;
    48     }
    49 
    50     public void setNextFinder(RateFinder value) {
    51         nextFinder = value;
    52     }
    53 
    54 }
    View Code
     1 /***********************************************************************
     2  * Module:  PublicRateFinder.java
     3  * Author:  jimmy
     4  * Purpose: Defines the Class PublicRateFinder
     5  ***********************************************************************/
     6 package murate.test.ratefinder.service.impl;
     7 
     8 import java.util.*;
     9 
    10 import org.springframework.util.StringUtils;
    11 
    12 import murate.test.ratefinder.dto.AirwayBill;
    13 import murate.test.ratefinder.dto.RateCluase;
    14 import murate.test.ratefinder.service.RateFinder;
    15 
    16 /**
    17  * 公布运价查找者
    18  * 
    19  */
    20 public class PublicRateFinder implements RateFinder {
    21     RateFinder nextFinder;
    22 
    23     public RateCluase find(AirwayBill airwayBill, List<RateCluase> rateClauses) {
    24 
    25         for (RateCluase clause : rateClauses) {
    26             // 模拟查找逻辑(只要始发站、目的站匹配,就算通过,仅演示)
    27 
    28             if (StringUtils.isEmpty(clause.getOrigin())
    29                     || StringUtils.isEmpty(clause.getDest())
    30                     || StringUtils.isEmpty(airwayBill.getOrigin())
    31                     || StringUtils.isEmpty(airwayBill.getDest())) {
    32                 continue;
    33             }
    34 
    35             if (clause.getOrigin().equals(airwayBill.getOrigin())
    36                     && clause.getDest().equals(airwayBill.getDest())) {
    37                 // 找到了,直接返回
    38                 return clause;
    39             }
    40         }
    41 
    42         if (nextFinder == null) {
    43             return null;
    44         }
    45 
    46         // 否则,交给下一个Finder继续查找
    47         return nextFinder.find(airwayBill, rateClauses);
    48 
    49     }
    50 
    51     public RateFinder getNextFinder() {
    52         return nextFinder;
    53     }
    54 
    55     public void setNextFinder(RateFinder value) {
    56         nextFinder = value;
    57     }
    58 }
    View Code

    注:链的最后一个节点,要有保底处理,即 PublicRateFinder 类42-44 行的处理,否则到“链”的最后一个节点,就会出错了。

    配置:

    该万能的Spring出场了:

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans"
     3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
     4     xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
     5     xmlns:context="http://www.springframework.org/schema/context"
     6     xsi:schemaLocation="
     7      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
     8      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
     9      http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
    10      http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    11      http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
    12     default-autowire="byName">
    13 
    14     <!-- spotrate->contract->public -->
    15     
    16     <!-- <bean id="firstFinder" class="murate.test.ratefinder.service.impl.SpotRateFinder">
    17         <property name="nextFinder" ref="contractRateFinder" />
    18     </bean>
    19 
    20     <bean id="contractRateFinder" class="murate.test.ratefinder.service.impl.ContractRateFinder">
    21         <property name="nextFinder" ref="publicRateFinder" />
    22     </bean>
    23 
    24     <bean id="publicRateFinder" class="murate.test.ratefinder.service.impl.PublicRateFinder"></bean>
    25  -->
    26 
    27     <!-- contract->spotrate->public -->
    28     
    29      <bean id="firstFinder" class="murate.test.ratefinder.service.impl.ContractRateFinder">
    30         <property name="nextFinder" ref="spotRateFinder" />
    31     </bean>
    32 
    33     <bean id="spotRateFinder" class="murate.test.ratefinder.service.impl.SpotRateFinder">
    34         <property name="nextFinder" ref="publicRateFinder" />
    35     </bean>
    36 
    37     <bean id="publicRateFinder" class="murate.test.ratefinder.service.impl.PublicRateFinder"></bean>
    38  
    39 
    40 </beans>
    View Code

    测试代码:

     1 package murate.test;
     2 
     3 import java.util.ArrayList;
     4 import java.util.List;
     5 
     6 import murate.test.ratefinder.dto.AirwayBill;
     7 import murate.test.ratefinder.dto.RateCluase;
     8 import murate.test.ratefinder.service.RateFinder;
     9 
    10 import org.junit.Test;
    11 import org.springframework.context.ApplicationContext;
    12 import org.springframework.context.support.ClassPathXmlApplicationContext;
    13 
    14 public class RateFinderTest {
    15 
    16     @Test
    17     public void testFinder() {
    18 
    19         ApplicationContext ctx = new ClassPathXmlApplicationContext(
    20                 "spring-beans-test.xml");
    21 
    22         RateFinder firstFinder = ctx.getBean("firstFinder", RateFinder.class);
    23 
    24         List<AirwayBill> awbs = getAwbList();
    25         List<RateCluase> rateCluases = getRateClauses();
    26 
    27         for (AirwayBill airwayBill : awbs) {
    28             System.out.println(airwayBill.getAwbPre() + airwayBill.getAwbNo()
    29                     + ":" + firstFinder.find(airwayBill, rateCluases));
    30         }
    31 
    32         ((ClassPathXmlApplicationContext) ctx).close();
    33     }
    34 
    35     /**
    36      * 模拟所有运价条款
    37      * @return
    38      */
    39     private List<RateCluase> getRateClauses() {
    40         List<RateCluase> rateCluases = new ArrayList<RateCluase>();
    41 
    42         RateCluase spa = new RateCluase();
    43         spa.setAwbPre("112");
    44         spa.setAwbNo("00000000");
    45         spa.setClauseName("SpotRate测试条款");
    46         rateCluases.add(spa);
    47 
    48         RateCluase contract = new RateCluase();
    49         contract.setAgentNumber("SHAXYZ");
    50         contract.setClauseName("Contract测试条款 ");
    51         rateCluases.add(contract);
    52 
    53         RateCluase publicClause = new RateCluase();
    54         publicClause.setOrigin("PVG");
    55         publicClause.setDest("LAX");
    56         publicClause.setClauseName("Public测试条款 ");
    57         rateCluases.add(publicClause);
    58 
    59         return rateCluases;
    60 
    61     }
    62 
    63     /**
    64      * 模拟生成运单数据
    65      * @return
    66      */
    67     private List<AirwayBill> getAwbList() {
    68 
    69         //awb1预期匹配Contract条款(或SpotRate,视配置规定的查找顺序)
    70         AirwayBill awb1 = new AirwayBill();
    71         awb1.setAgentNumber("SHAXYZ");
    72         awb1.setAwbPre("112");
    73         awb1.setAwbNo("00000000");
    74 
    75         //awb2预期匹配Public条款
    76         AirwayBill awb2 = new AirwayBill();
    77         awb2.setOrigin("PVG");
    78         awb2.setDest("LAX");
    79         awb2.setAwbPre("112");
    80         awb2.setAwbNo("11111111");
    81 
    82         //awb3预期匹配SpotRate条款
    83         AirwayBill awb3 = new AirwayBill();
    84         awb3.setAwbPre("112");
    85         awb3.setAwbNo("22222222");
    86 
    87         List<AirwayBill> awbList = new ArrayList<AirwayBill>();
    88         awbList.add(awb1);
    89         awbList.add(awb2);
    90         awbList.add(awb3);
    91 
    92         return awbList;
    93 
    94     }
    95 }
    View Code

    运行结果:

    11200000000:Contract测试条款
    11211111111:Public测试条款
    11222222222:null

    如果把配置中,注释部分和未注释部分对换,即:更改查找顺序,则变成了

    11200000000:SpotRate测试条款
    11211111111:Public测试条款
    11222222222:null

    业务扩展:如果以后某航空公司又发明了一种新运价,增加RateFinder的实现类,然后在配置中,把新的处理类,挂到链中的适当位置即可。反之,如果某一类运价,不再使用了,还是修改配置,把这个节点从链中摘除。至于查找顺序的修改,通过nextFinder的配置,形成一条有规则的"链"即可。

  • 相关阅读:
    js实现各种复制到剪贴板的方法
    PowerDesigner生成数据字典
    oracle实用sql之将逗号分割的字符串分割多个列
    ROW_NUMBER() OVER()函数用法;(分组,排序),partition by
    SQL Server 2008中的CTE递归查询得到一棵树
    【GoLang】GoLang 错误处理 -- 使用 error is value 的思路处理,检查并处理error
    【GoLang】golang 报管理工具 Godep 介绍
    【GoLang】GoLang 错误处理 -- 使用异常的思路进行处理
    【GoLang】GoLang 官方 对 error 处理的意见
    【GoLang】panic defer recover 深入理解
  • 原文地址:https://www.cnblogs.com/yjmyzz/p/Chain-of-Responsibility-Design-Mode-in-Practice-With-Aerial-Cargo-Field.html
Copyright © 2011-2022 走看看