---
# 磕叨
在公司做项目是见到前辈们写的一段任务链的代码,大概如下
```
Runnable task = new TaskA(new TaskB(new TaskC(new taskD())));
task.run();
```
taskA执行run调用并完成TaskA声明的任务逻辑之后,内部会自动调用构造参数传入的TaskB的run方法,过程类似TaskA,TaskB完成之后一样会调用参数传入的task,直到最后一个没有带下一个task类传入的任务完成,即完成一个管道式调用。
爱思考的我在想,可用,不好用重用,于是动手改改。
---
# 准备
经过一段时间开发后,有了一个常用的工具类,方便快速开发,但是这里用到的东西很少,还是要说明一下,这里用到一个我称作ecommon的包,当然我只用了两个很基础的额部分。这两个部分完全可以用你自己的实现,是非常简单的。
- 函数接口
jdk8之后很方便让我们写出lambda,但是我觉得理解起来不直观,于是自己重写了 12 个接口,按参数个数和返回类型可以直接根据函数名直接选出你要的。具体在 https://github.com/kimffy24/EJoker/treehttps://img.qb5200.com/download-x/dev/ejoker-common/src/main/java/pro/jiefzz/ejoker/common/system/functional , IVoid打头的就是无返回的,后面的数字就是要带多少个参数,参数和返回类型全部都是泛型。1-6个参数已经能包括大部分情况了,需要更多参数的情况完全可以自定义一个上下文传递过去。
- 字符串填充类
类似`String.format`,但是我不用正则,而是类似slf4j那种,`log.info("This is a template, keyA={}, keyB={}", "valueA", "valueB")` , 类似这种占位填充。我的实现在 https://github.com/kimffy24/EJoker/blobhttps://img.qb5200.com/download-x/dev/ejoker-common/src/main/java/pro/jiefzz/ejoker/common/system/helper/StringHelper.java 的fill方法中。你也可以用 `String.format` 代替。
# 思路简述
我们先明确,jdk8以下的情况不作考虑。
pipeline我更多的印象是来自终端上的应用
![命令终端中使用管道](https://img2020.cnblogs.com/blog/1704011/202003/1704011-20200322030753951-253545695.png)
pipeline是单向的,上个task的输出作为下个task的输入,直到没有下一个task,最后一个task的作用就应该是你期望的。且`后续任务只关心前者的输出结果,对于的他是谁,怎么做的,是不关心的`。记为 `Point1`
这个特点是我视为管道与切面或职责链模式的区别所在。
首先,我们得有第一推动,让管道流能有个开始,再就是有中间task,他必定是能接收到上一个任务的输出的,并且,可能有自带参数,并且有自己的输出,最后,有latest的task 与中间task区别在于他不用返回了,latest一般是以副作用的形式实现我们的企图的,如上图的 `wc -l` 作为最后一个任务是直接把结果打印到屏幕上,而不是返回一个变量给我们读取。根据java的强类型属性,以及刚刚一段的分析,可以得知,有3种类型的任务,开始任务,中间任务,最后任务,并且中间任务的个数是不限的,所有任务至于相邻的任务有一个管理点,那就是 ` 前者的输出类型与后者的输入类型一致 ` (网文中大部分说自己实现的pipeline的模式都是传递Object类型,到各个子任务中自己强转到需要的类型的,不说好与不好,但我肯定不喜欢)。这个特性记为 `Point2`。
而且,每个子任务,本身是可以带参数的,这是一个需要支持的点。像上图命令中的管道,每个子命令(除第一个)都是同时接受前一个命令的输出作为输入,且自带参数的。但是java在这里其实并不灵活,因此我们约定 `后续任务的第一个参数就是前一个任务的输入` , 这个约定是直接影响到我们的代码实现的。这个特性记为 `Point3`。
另外,管道的入口唯一的,一定是从开始任务往后流的。如果入口不一样,那么就是像个不同的管道,他们的意图以及输入输出的期望都是不同的。这个特性记为 `Point4`。
最后,在java中使用,我肯定不能像终端那种,错了重敲命令就是了,所以需要异常控制以及做一些相邻任务承上启下的时刻做点什么,例如日子打印,断言等。这个算附加题。
# 提起键盘撸
(因为我已经写完并测试完了,所以我就反过来解析我是怎么想的了)
这里以Runnable接口作为基础接口。给出其中一个测试的例子
![](https://img2020.cnblogs.com/blog/1704011/202003/1704011-20200322030735113-1079892834.png)
这里初始任务是给出一个日期,中间任务是拼接成人类友好的1句话,最终任务是直接打印到屏幕。(现实中要实现这样一句话,当然是直接撸啦。这里只是为了演示),看看Pipeline初始任务的定义
![](https://img2020.cnblogs.com/blog/1704011/202003/1704011-20200322030725911-1345377620.png)
先不看其他属性,看构造方法,传入一个 `IFunction
` ,按照准备一节的定义,他是一个返回类型为声明泛型R,且无参数输入的闭包函数(或称作lambda表达式)。对照上面PipelineTest中就是那个 `() -> { return new Date(); }` , (得益于jdk8的类型推断,在 `new Pipeline<>` 构造时,不用再声明其泛型,编译器能根据闭包函数的return类型推断出这里是个Date类型)。`next`, `end` 是指明管道的下接任务,这可以看出管道是极其类似于任务链/职责链的(需要注意`next`和`end`同时`只能有个一个存在`)。hook是异常管理以及任务间承接时做一个切面方法的,argCxt是记下传递参数,方便hook中的方法使用(这个是因为java需要的,跟管道模式并没有关系)。
再看add方法的一个重载,添加并返回中间task
![](https://img2020.cnblogs.com/blog/1704011/202003/1704011-20200322030715837-1066405785.png)
add方法传入一个`IFunction1