职责链(Chain of Responsibility)模式在航空货运中的运用实例

时间:2023-03-09 13:28:49
职责链(Chain of Responsibility)模式在航空货运中的运用实例

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

业务场景:

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

变化点:

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

目标:

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

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

类图:

职责链(Chain of Responsibility)模式在航空货运中的运用实例

RateCluase 为运价条款基本信息

Airwaybill 为运单基本信息

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

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

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

代码:

入口参数

 /***********************************************************************
* Module: AirwayBill.java
* Author: jimmy
* Purpose: Defines the Class AirwayBill
***********************************************************************/ package murate.test.ratefinder.dto; public class AirwayBill {
/**
* 运单前缀
*
*/
private String awbPre;
/**
* 运单号
*
*/
private String awbNo;
/**
* 始发站
*
*/
private String origin;
/**
* 目的站
*
*/
private String dest;
/**
* 代理人帐号
*
*/
private String agentNumber;
/**
* 品名代码
*
*/
private String commodityCode;
/**
* 特货代码
*
*/
private String specialHandlingCode; public String getAwbPre() {
return awbPre;
} public void setAwbPre(String awbPre) {
this.awbPre = awbPre;
} public String getAwbNo() {
return awbNo;
} public void setAwbNo(String awbNo) {
this.awbNo = awbNo;
} public String getOrigin() {
return origin;
} public void setOrigin(String origin) {
this.origin = origin;
} public String getDest() {
return dest;
} public void setDest(String dest) {
this.dest = dest;
} public String getAgentNumber() {
return agentNumber;
} public void setAgentNumber(String agentNumber) {
this.agentNumber = agentNumber;
} public String getCommodityCode() {
return commodityCode;
} public void setCommodityCode(String commodityCode) {
this.commodityCode = commodityCode;
} public String getSpecialHandlingCode() {
return specialHandlingCode;
} public void setSpecialHandlingCode(String specialHandlingCode) {
this.specialHandlingCode = specialHandlingCode;
} }
 /***********************************************************************
* Module: RateCluase.java
* Author: jimmy
* Purpose: Defines the Class RateCluase
***********************************************************************/ package murate.test.ratefinder.dto; /**
* 运价条款
*
* 2014-12-24 杨俊明 0.1
*
*/
public class RateCluase { /**
* 条款Id
*
*/
private Long clauseId; /**
* 条款名称
*
*/
private String clauseName; /**
* 运单前缀
*/
private String awbPre; /**
* 运单号
*/
private String awbNo; /**
* 始发站
*
*/
private String origin; /**
* 目的站
*
*/
private String dest; /**
* 代理人帐号
*
*/
private String agentNumber; /**
* 品名代码
*
*/
private String commodityCode; /**
* 特货代码
*
*/
private String specialHandlingCode; public Long getClauseId() {
return clauseId;
} public void setClauseId(Long clauseId) {
this.clauseId = clauseId;
} public String getClauseName() {
return clauseName;
} public void setClauseName(String clauseName) {
this.clauseName = clauseName;
} public String getOrigin() {
return origin;
} public void setOrigin(String origin) {
this.origin = origin;
} public String getDest() {
return dest;
} public void setDest(String dest) {
this.dest = dest;
} public String getAgentNumber() {
return agentNumber;
} public void setAgentNumber(String agentNumber) {
this.agentNumber = agentNumber;
} public String getCommodityCode() {
return commodityCode;
} public void setCommodityCode(String commodityCode) {
this.commodityCode = commodityCode;
} public String getSpecialHandlingCode() {
return specialHandlingCode;
} public void setSpecialHandlingCode(String specialHandlingCode) {
this.specialHandlingCode = specialHandlingCode;
} public String getAwbPre() {
return awbPre;
} public void setAwbPre(String awbPre) {
this.awbPre = awbPre;
} public String getAwbNo() {
return awbNo;
} public void setAwbNo(String awbNo) {
this.awbNo = awbNo;
} public String toString() {
return clauseName;
} }

接口:

 /***********************************************************************
* Module: RateFinder.java
* Author: jimmy
* Purpose: Defines the Interface RateFinder
***********************************************************************/ package murate.test.ratefinder.service; import java.util.List; import murate.test.ratefinder.dto.AirwayBill;
import murate.test.ratefinder.dto.RateCluase; /**
* 运价查找接口
*
*/
public interface RateFinder {
/**
* 查找运价条款
*
* @param airwayBill
* 运单信息
* @param rateClauses
* 运单条款信息
* @return
*/
RateCluase find(AirwayBill airwayBill, List<RateCluase> rateClauses); RateFinder getNextFinder(); void setNextFinder(RateFinder value); }

3个实现类:

 /***********************************************************************
* Module: SpotRateFinder.java
* Author: jimmy
* Purpose: Defines the Class SpotRateFinder
***********************************************************************/ package murate.test.ratefinder.service.impl; import java.util.*; import org.springframework.util.StringUtils; import murate.test.ratefinder.dto.AirwayBill;
import murate.test.ratefinder.dto.RateCluase;
import murate.test.ratefinder.service.RateFinder; /**
* 一票一议运价查找
*
*/
public class SpotRateFinder implements RateFinder { RateFinder nextFinder; public RateCluase find(AirwayBill airwayBill, List<RateCluase> rateClauses) { for (RateCluase clause : rateClauses) {
// 模拟查找逻辑(只要单号匹配成功,就算通过,仅演示) if (StringUtils.isEmpty(clause.getAwbPre())
|| StringUtils.isEmpty(clause.getAwbNo())
|| StringUtils.isEmpty(airwayBill.getAwbPre())
|| StringUtils.isEmpty(airwayBill.getAwbNo())) {
continue;
}
if (clause.getAwbPre().equals(airwayBill.getAwbPre())
&& clause.getAwbNo().equals(airwayBill.getAwbNo())) {
// 找到了,直接返回
return clause;
}
} // 否则,交给下一个Finder继续查找
return nextFinder.find(airwayBill, rateClauses); } public RateFinder getNextFinder() {
return nextFinder;
} public void setNextFinder(RateFinder value) {
nextFinder = value;
} }
 /***********************************************************************
* Module: ContractRateFinder.java
* Author: jimmy
* Purpose: Defines the Class ContractRateFinder
***********************************************************************/ package murate.test.ratefinder.service.impl; import java.util.*; import org.springframework.util.StringUtils; import murate.test.ratefinder.dto.AirwayBill;
import murate.test.ratefinder.dto.RateCluase;
import murate.test.ratefinder.service.RateFinder; /**
* Contract运价查找者
*
*/
public class ContractRateFinder implements RateFinder {
RateFinder nextFinder; public RateCluase find(AirwayBill airwayBill, List<RateCluase> rateClauses) { for (RateCluase clause : rateClauses) { // 模拟查找逻辑(只要代理人帐号匹配成功,就算通过,仅演示) if (StringUtils.isEmpty(clause.getAgentNumber())
|| StringUtils.isEmpty(clause.getAgentNumber())) {
continue;
} if (clause.getAgentNumber().equals(airwayBill.getAgentNumber())) {
// 找到了,直接返回
return clause;
}
} // 否则,交给下一个Finder继续查找
return nextFinder.find(airwayBill, rateClauses); } public RateFinder getNextFinder() {
return nextFinder;
} public void setNextFinder(RateFinder value) {
nextFinder = value;
} }
 /***********************************************************************
* Module: PublicRateFinder.java
* Author: jimmy
* Purpose: Defines the Class PublicRateFinder
***********************************************************************/
package murate.test.ratefinder.service.impl; import java.util.*; import org.springframework.util.StringUtils; import murate.test.ratefinder.dto.AirwayBill;
import murate.test.ratefinder.dto.RateCluase;
import murate.test.ratefinder.service.RateFinder; /**
* 公布运价查找者
*
*/
public class PublicRateFinder implements RateFinder {
RateFinder nextFinder; public RateCluase find(AirwayBill airwayBill, List<RateCluase> rateClauses) { for (RateCluase clause : rateClauses) {
// 模拟查找逻辑(只要始发站、目的站匹配,就算通过,仅演示) if (StringUtils.isEmpty(clause.getOrigin())
|| StringUtils.isEmpty(clause.getDest())
|| StringUtils.isEmpty(airwayBill.getOrigin())
|| StringUtils.isEmpty(airwayBill.getDest())) {
continue;
} if (clause.getOrigin().equals(airwayBill.getOrigin())
&& clause.getDest().equals(airwayBill.getDest())) {
// 找到了,直接返回
return clause;
}
} if (nextFinder == null) {
return null;
} // 否则,交给下一个Finder继续查找
return nextFinder.find(airwayBill, rateClauses); } public RateFinder getNextFinder() {
return nextFinder;
} public void setNextFinder(RateFinder value) {
nextFinder = value;
}
}

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

配置:

该万能的Spring出场了:

 <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"
default-autowire="byName"> <!-- spotrate->contract->public --> <!-- <bean id="firstFinder" class="murate.test.ratefinder.service.impl.SpotRateFinder">
<property name="nextFinder" ref="contractRateFinder" />
</bean> <bean id="contractRateFinder" class="murate.test.ratefinder.service.impl.ContractRateFinder">
<property name="nextFinder" ref="publicRateFinder" />
</bean> <bean id="publicRateFinder" class="murate.test.ratefinder.service.impl.PublicRateFinder"></bean>
--> <!-- contract->spotrate->public --> <bean id="firstFinder" class="murate.test.ratefinder.service.impl.ContractRateFinder">
<property name="nextFinder" ref="spotRateFinder" />
</bean> <bean id="spotRateFinder" class="murate.test.ratefinder.service.impl.SpotRateFinder">
<property name="nextFinder" ref="publicRateFinder" />
</bean> <bean id="publicRateFinder" class="murate.test.ratefinder.service.impl.PublicRateFinder"></bean> </beans>

测试代码:

 package murate.test;

 import java.util.ArrayList;
import java.util.List; import murate.test.ratefinder.dto.AirwayBill;
import murate.test.ratefinder.dto.RateCluase;
import murate.test.ratefinder.service.RateFinder; import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext; public class RateFinderTest { @Test
public void testFinder() { ApplicationContext ctx = new ClassPathXmlApplicationContext(
"spring-beans-test.xml"); RateFinder firstFinder = ctx.getBean("firstFinder", RateFinder.class); List<AirwayBill> awbs = getAwbList();
List<RateCluase> rateCluases = getRateClauses(); for (AirwayBill airwayBill : awbs) {
System.out.println(airwayBill.getAwbPre() + airwayBill.getAwbNo()
+ ":" + firstFinder.find(airwayBill, rateCluases));
} ((ClassPathXmlApplicationContext) ctx).close();
} /**
* 模拟所有运价条款
* @return
*/
private List<RateCluase> getRateClauses() {
List<RateCluase> rateCluases = new ArrayList<RateCluase>(); RateCluase spa = new RateCluase();
spa.setAwbPre("112");
spa.setAwbNo("00000000");
spa.setClauseName("SpotRate测试条款");
rateCluases.add(spa); RateCluase contract = new RateCluase();
contract.setAgentNumber("SHAXYZ");
contract.setClauseName("Contract测试条款 ");
rateCluases.add(contract); RateCluase publicClause = new RateCluase();
publicClause.setOrigin("PVG");
publicClause.setDest("LAX");
publicClause.setClauseName("Public测试条款 ");
rateCluases.add(publicClause); return rateCluases; } /**
* 模拟生成运单数据
* @return
*/
private List<AirwayBill> getAwbList() { //awb1预期匹配Contract条款(或SpotRate,视配置规定的查找顺序)
AirwayBill awb1 = new AirwayBill();
awb1.setAgentNumber("SHAXYZ");
awb1.setAwbPre("112");
awb1.setAwbNo("00000000"); //awb2预期匹配Public条款
AirwayBill awb2 = new AirwayBill();
awb2.setOrigin("PVG");
awb2.setDest("LAX");
awb2.setAwbPre("112");
awb2.setAwbNo("11111111"); //awb3预期匹配SpotRate条款
AirwayBill awb3 = new AirwayBill();
awb3.setAwbPre("112");
awb3.setAwbNo("22222222"); List<AirwayBill> awbList = new ArrayList<AirwayBill>();
awbList.add(awb1);
awbList.add(awb2);
awbList.add(awb3); return awbList; }
}

运行结果:

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

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

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

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