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

 

目录
相关文章
|
6月前
|
设计模式 安全 Java
设计模式之责任链 Chain Of Responsibility
设计模式之责任链 Chain Of Responsibility
40 1
|
11月前
|
存储 区块链
BSC链质押代币模式系统开发详情与模式
随着时间的推移,智能合约代码也越来越容易操控,越来越容易整合各类契约关系
机房合作-职责链模式实现用户登陆
机房合作-职责链模式实现用户登陆
56 1
机房合作-职责链模式实现用户登陆
|
运维 安全 算法
TRX链/BSC链/ARB链智能合约系统开发方案逻辑丨详细项目丨规则玩法丨案例详情丨源码出售
需求分析:与客户沟通,了解其业务需求和期望,明确系统的功能和性能要求。确定在哪个链上进行开发(TRX链、BSC链还是ARB链)。
|
存储 安全 区块链
TRX波场链DeFi质押模式系统开发|TRX波场链DAPP系统开发模式
随着区块链技术的发展和应用,Web3.0日益成为热门话题
|
安全 区块链
ARB链丨OP链丨马蹄链智能合约 dapp 系统开发(成熟技术)
跳出横向的角度来看待Web3.0的方式和方法,真正以一种全新的视角来看待它
|
存储 安全 区块链
ARB链丨OP链丨马蹄链智能合约DAPP系统开发(成熟技术)
分布式云计算是一项创新且迅速发展的技术,有潜力彻底改变Web3行业
|
监控 算法 区块链
Metaforce佛萨奇2.0系统开发(马蹄链)源码部署
共识机制是指在区块链网络中public boolean equals
|
存储 自然语言处理 数据管理
「数字化生产」PDM和PIM:有什么区别?
「数字化生产」PDM和PIM:有什么区别?
|
SQL 设计模式 架构师
责任链、领域模型和事务的恩怨情仇
责任链、领域模型和事务的恩怨情仇
309 1
责任链、领域模型和事务的恩怨情仇