职责链(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的配置,形成一条有规则的"链"即可。

 

posted @ 2014-12-24 10:35  菩提树下的杨过  阅读(883)  评论(0编辑  收藏  举报