设计模式——管道模式
管道(执行流)模型由 Pipeline(管道)/ Valve(阀门)/ Context(上下文) 组成
概念
我们把特定的业务,比如订单业务中的临时订单、订单提交以及订单支付等,抽象成一组Pipeline(管道);
拿生成临时订单业务来说,执行流程包括:1参数校验->2业务数据校验->3业务处理,这里的三段子流程是严格按照顺序执行的,我们用Valve(阀门)定义它们,每一个子流程即一个Valve;
在管道模式中,我们要处理的对象是一组业务数据,即概念中的Context(上下文),Context贯穿于整个执行流程。
意义
管道模式是多步流程业务很好的抽象,内部基于单链表实现顺序执行,具有强顺序性;
管道模式对于整体流程的拆分,使得业务的扩展性大大增强,当业务需求发生变化,只需要确定需要加入/删除的子流程位置即可,就像从单链表中增加/删除一个节点。
实现
模块结构
PipeLineContext 实现管道上下文的概念,内部记录一个所处阀门在管道中的索引;使用HashMap存储业务数据,用户流程进行时的数据传递。
public class PipeLineContext {
private PipeLineContext() {
}
@Getter
private int index;
@Getter
private Map<String, Object> context;
public PipeLineContext(int size) {
this.index = 0;
this.context = new HashMap<>(size);
}
public void put(String key, Object value) {
context.put(key, value);
}
public void get(String key) {
context.get(key);
}
@JSONField(serialize = false)
public int getAndIncrement() {
this.index++;
return index;
}
@Override
public String toString() {
return "{\"index\":\"" + index + "\", \"context\":\"" + JSON.toJSONString(context) + "\"}";
}
}
PipeLine 管道接口,包括添加阀门方法以及开启管道方法
public interface PipeLine {
/**
* 添加阀门
* @param valve 阀门
*/
void addValve(Valve valve);
/**
* 开启管道
* @param pipeLineContext 管道上下文
* @return FlowResult
*/
FlowResult start(PipeLineContext pipeLineContext);
}
Valve 阀门接口,阀门都需实现该接口或者该接口的扩展接口
public interface Valve {
/**
* 获取下一个阀门
* @return Valve 阀门
*/
Valve getNext();
/**
* 设置下一个阀门
* @param valve 阀门
*/
void setNext(Valve valve);
/**
* 执行管道
* @param pipeLineContext 管道上下文
* @return FlowResult
*/
FlowResult invoke(PipeLineContext pipeLineContext);
}
NormalPipeLine PipeLine接口通用实现
@Component
public class NormalPipeLine implements PipeLine {
private Valve head = null;
private Valve next = null;
@Override
public void addValve(Valve valve) {
if (head == null) {
head = valve;
valve.setNext(next);
} else {
Valve current = head;
while (current != null) {
if (current.getNext() == next) {
current.setNext(valve);
valve.setNext(next);
break;
}
current = current.getNext();
}
}
}
@Override
public FlowResult start(PipeLineContext pipeLineContext) {
if (pipeLineContext == null) {
return FlowResult.fail("pipeLineContext should be not null!");
}
if (head == null) {
return FlowResult.fail("there's no valve in current pipeLine!");
}
return head.invoke(pipeLineContext);
}
}
NormalValve Valve接口通用实现
public class NormalValve implements Valve {
protected Valve next = null;
@Override
public Valve getNext() {
return next;
}
@Override
public void setNext(Valve valve) {
this.next = valve;
}
@Override
public FlowResult invoke(PipeLineContext pipeLineContext) {
return processContinue(pipeLineContext);
}
protected FlowResult processContinue(PipeLineContext pipeLineContext) {
return next == null ? FlowResult.ok() : getNext().invoke(pipeLineContext);
}
}
Validator 订单-临时订单前置参数校验
@Slf4j
@Component
public class Validator extends NormalValve {
@Override
public FlowResult invoke(PipeLineContext pipeLineContext) {
pipeLineContext.put("param", "1");
return processContinue(pipeLineContext);
}
}
使用AOP织入阀门,跟踪执行流
@Slf4j
@Aspect
@Component
public class PipeLineAspect {
/**
* 定义阀门invoke切点
*/
@Pointcut(value = "this(com.nooice.order.common.pipeline.Valve) " +
"&& execution(* invoke(com.nooice.order.common.pipeline.model.PipeLineContext)) && args((pipeLineContext))",
argNames = "pipeLineContext")
public void valveInvokeCutOffPoint(PipeLineContext pipeLineContext) {
}
@Before(value = "valveInvokeCutOffPoint(pipeLineContext)", argNames = "point,pipeLineContext")
public void doBefore(JoinPoint point, PipeLineContext pipeLineContext) {
int currentIndex = pipeLineContext.getAndIncrement();
String className = point.getTarget().getClass().getName();
log.info("管道前置通知-{}号阀门({})进入执行, pipeLineContext={}", currentIndex, className, pipeLineContext.toString());
}
}
测试
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class PipelineTest {
@Autowired
private NormalPipeLine normalPipeLine;
@Autowired
private Validator validator;
@Autowired
private OrderPreviewValidator orderPreviewValidator;
@Autowired
private Processor processor;
@Test
public void testUserController() {
// 定义上下文
PipeLineContext pipeLineContext = new PipeLineContext(0);
pipeLineContext.put("index", "0");
// 增加阀门
normalPipeLine.addValve(validator); // 参数校验阀门
normalPipeLine.addValve(orderPreviewValidator); // 业务校验阀门
normalPipeLine.addValve(processor); // 业务处理阀门
// 管道执行
FlowResult flowResult = normalPipeLine.start(pipeLineContext);
log.info(JSON.toJSONString(flowResult));
}
}
管道前置通知-1号阀门(com.nooice.order.common.pipeline.validator.Validator)进入执行, pipeLineContext=管道前置通知-2号阀门(com.nooice.order.common.pipeline.validator.OrderPreviewValidator)进入执行, pipeLineContext={"index":"2", "context":"{"index":"0","param":"1"}"}
管道前置通知-3号阀门(com.nooice.order.common.pipeline.processor.Processor)进入执行, pipeLineContext={"index":"3", "context":"{"index":"0","param":"2"}"}
PipelineTest: {"code":1,"message":"成功"}
关注公众号
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
客服服务除了电话和工单还有哪些服务?阿里云服务方式整理
提到客服服务,大多用户想到的是电话咨询、工单服务,其实阿里云提供的服务方式有很多,不同的服务方式适合不同需求的用户,例如产品学习,售前咨询,故障排查及处理均可以采用不同的服务方式解决我们的问题。 在阿里云官网联系我们页面,阿里云将主要客服服务主要方式都放在了此页,下面对于不同的服务方式做个简单的介绍。 一、智能在线就是机器人服务,阿里云将常见的问题及答案都汇集在此,例如备案类问题、最新活动规则介绍等等,可以做到智能诊断,秒级解答,对于无法搜索到答案的问题系统将为您推荐最合适的人工服务渠道。 二、自助中心提供常用自助工具,解决账号、财务、备案、定价等问题,例如找回账号密码、申请发票、域名转入,续费,实名认证等相关问题。 点我领取阿里云2000元代金券,(阿里云优惠券的作用:购买阿里云产品,最后支付结算的时候,阿里云优惠券可抵扣一部分费用。 三、帮助文档为客户提供阿里云所有产品的简介、购买、入门、操作等内容。用户可以通过此服务学习产知识、购买流程、实践指导教程等。 四、95187电话服务用户只需要根据对应的语言提示,即可找到相应的人工客服,例如95187 转1可提供售前咨询类问题。 五、预...
-
下一篇
Python在计算内存时应该注意的问题?
我之前的一篇文章,带大家揭晓了 Python 在给内置对象分配内存时的 5 个奇怪而有趣的小秘密。文中使用了sys.getsizeof()来计算内存,但是用这个方法计算时,可能会出现意料不到的问题。 文档中关于这个方法的介绍有两层意思: 该方法用于获取一个对象的字节大小(bytes) 它只计算直接占用的内存,而不计算对象内所引用对象的内存 也就是说,getsizeof() 并不是计算实际对象的字节大小,而是计算“占位对象”的大小。如果你想计算所有属性以及属性的属性的大小,getsizeof() 只会停留在第一层,这对于存在引用的对象,计算时就不准确。 例如列表 [1,2],getsizeof() 不会把列表内两个元素的实际大小算上,而只是计算了对它们的引用。 举一个形象的例子,我们把列表想象成一个箱子,把它存储的对象想象成一个个球,现在箱子里有两张纸条,写上了球 1 和球 2 的地址(球不在箱子里),getsizeof() 只是把整个箱子称重(含纸条),而没有根据纸条上地址,找到两个球一起称重。 1、计算的是什么? 我们先来看看列表对象的情况: 如图所示,单独计算 a 和 b 列表的结...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Linux系统CentOS6、CentOS7手动修改IP地址
- Docker容器配置,解决镜像无法拉取问题
- Windows10,CentOS7,CentOS8安装MongoDB4.0.16
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS7,CentOS8安装Elasticsearch6.8.6
- Eclipse初始化配置,告别卡顿、闪退、编译时间过长
- CentOS6,CentOS7官方镜像安装Oracle11G
- CentOS7,8上快速安装Gitea,搭建Git服务器
- 2048小游戏-低调大师作品


微信收款码
支付宝收款码