职责链(Chain of Responsibility)模式在航空货运中的运用实例
设计模式这东西,基本上属于“看懂一瞬间,用会好几年”。只有实际开发中,当某一模式很好的满足了业务需求时,才会有真切的感觉。借用一句《闪电侠》中,绿箭侠教导闪电侠的台词:“不是你碰巧遇到了它(指闪电事故),而是它选择你”。
业务场景:
航空公司内部对于货运单的价格管理,通常会颁发若干类型的运价文件,典型的有:SpotRate(一票一议)、ContractRate(合同运价)、PublicRate(IATA公布运价)等等,一票运单判断该用何种运价时,通常会按一定的顺序在这几类运价中依次匹配查找,如果匹配成功,则直接返回,使用查找结果中的费率做为计算依据。
变化点:
不同的航空公司,内部管理体制不同,支持的运价种类也不同,包括查找运价的顺序也可能略有差异。
目标:
为了能尽量少加班,少改代码,要求系统最好能方便的应对这些变化。职责链模式正是为该类场景而生,园友飞林沙已经详解解读了这一模式,参见其博文:
重温设计模式(三)——职责链模式(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的配置,形成一条有规则的"链"即可。
职责链(Chain of Responsibility)模式在航空货运中的运用实例的更多相关文章
- C++设计模式实现--职责链(Chain of Responsibility)模式
一. 概述 职责链模式: 使多个对象都有机会处理请求.从而避免请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止. 二. 举个样例 员工要求加薪 ...
- 设计模式C++描述----05.职责链(Chain of Responsibility)模式
一. 概述 职责链模式: 使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系.将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止. 二. 举个例子 员工要求加薪 ...
- atitit.(设计模式1)--—职责链(chain of responsibility)最佳实践O7 转换日期
atitit.设计模式(1)---职责链模式(chain of responsibility)最佳实践O7 日期转换 1. 需求:::日期转换 1 2. 能够选择的模式: 表格模式,责任链模式 1 3 ...
- 设计模式(十三) 职责链(chain of responsibility)
软件领域中的设计模式为开发人员提供了一种使用专家设计经验的有效途径.设计模式中运用了面向对象编程语言的重要特性:封装.继承.多态,真正领悟设计模式的精髓是可能一个漫长的过程,需要大量实践经验的积累.最 ...
- Java设计模式(14)责任链模式(Chain of Responsibility模式)
Chain of Responsibility定义:Chain of Responsibility(CoR) 是用一系列类(classes)试图处理一个请求request,这些类之间是一个松散的耦合, ...
- 设计模式(十四)Chain of Responsibility模式
Chain of Responsibility模式就是当外部请求程序进行某个处理,但程序暂时无法直接决定由哪个对象负责处理时,就需要推卸责任.也就是说,当一个人被要求做什么事时,如果他可以做就自己做, ...
- 《图解设计模式》读书笔记6-2 Chain of Responsibility模式
目录 1. 简介 2. 示例程序 类图 代码 3. 模式的角色和类图 角色 类图 4. 思路拓展 1. 简介 Chain of Responsibility模式是责任链模式,模式的核心就是转移责任.就 ...
- Chain of Responsibility模式
熟悉VC/MFC的都知道,VC是“基于消息,事件驱动”,消息在VC开发中起着举足轻重的作用.MFC提供了消息的处理的链式处理策略,处理消息的请求将沿着预定好的路径依次进行处理.消息的发送者并不知道该消 ...
- 基于.net 职责链来实现 插件模式
插件式的例子 QQ电脑管家,有很多工具列表,点一下工具下载后就可以开始使用了 eclipse ,X Server 等等 插件式的好处 插件降低框架的复杂性,把扩展功能从框架中剥离出来 让第三方有机会来 ...
随机推荐
- HTML5气泡悬浮框(已经加上完整文件)
源文件链接:http://pan.baidu.com/s/1pKHlNSn 设计气泡悬浮框 1.在网页设计中,气泡悬浮框常常用于页面中为某些对象显示提示信息,恰当地使用气泡悬浮框能够使网页布局更加完美 ...
- Ajax最详细的参数解析和场景应用
4.1.定义和用法 AJAX即“Asynchronous Javascript And XML”(异步JavaScript和XML),是指一种创建交互式网页应用的网页开发技术. AJAX = 异步 J ...
- 机器数据的价值 - Web 访问日志和数据库审计日志
计算机数据 大量的数据流,不断增长的来源,蕴含着巨大的价值 在 Splunk,我们大量谈及计算机数据.这些数据是指在数据中心.“物联网”和互联设备世界中运行的所有系统产生的数据.其中包括支撑组织的应用 ...
- 字符输入流Reader简要概括
字符输入流Reader组成结构 本篇将对JAVA I/O流中的字符输入流Reader做个简单的概括: 总得来说,每个字符输入流类都有一个对应的用途,如下: 字符流基类:Reader 字节流转字符流:I ...
- Linux使用汇总贴
1. 服务开机启动 比如: update-rc.d ssh enable 2. 修改hostname [基于ubutun] hostname newname vi /etc/hostnamevi /e ...
- android中的广播接收实现总结
1 首先根据广播应用内接收和应用外接收,分两个类进行管理[1] LocalBroadcastManager,应用内广播管理类[2] BroadcastManager 广播管理类(部分应用内,应用 ...
- JavaScript Patterns 4.9 Configuration Objects
Configuration Objects Passing a large number of parameters is not convenient. A better approach is t ...
- ORACLE AWR报告数据的导入导出实践
关于AWR的快照数据可以导出.导入,一直没有亲手实践过.今天动手测试了一下如何导出.导入AWR数据,将AWR的数据从一测试服务器,导入到另外一台测试服务器. SQL> @?/rdbms/admi ...
- node js学习(二)——REPL(交互式解释器)
1.简介 Node.js REPL(Read Eval Print Loop:交互式解释器) 表示一个电脑的环境,类似 Window 系统的终端或 Unix/Linux shell,我们可以在终端中输 ...
- windows下 MySQL手动安装与卸载
下载文件以后进行解压 ,指定文件的具体位置 1.安装 选择路径下的mysqld --intall 指定服务名称 --设置配置文件 例子: C:\Users\Administrator\Desktop ...