动态任务树
@
§1 什么是任务(动态任务树)机制
§1.1 楔子
尝试想象如下场景:
你正在为家里准备晚饭,晚饭的菜单为炒个菜花、烧一条鱼、用以前炖的肉烩土豆,主食烙点饼。确认完菜单你就开始进行作业了。结果烧鱼的时候发现鱼还在冰箱中没有解冻,解冻后发现鱼在冻得时候忘了宰了,解冻后你获得的是连着鳞带着鳃,内脏完整一条;烹鱼时,忘了提前把料酒提前拿出来,等你拿出来时,鱼已经有一面被你煎糊了,炖鱼时发现盐没有了,你急火火的下楼去买;烩肉时,突然想起来肉是2个月之前炖的,等你拿出来看时发现,肉果然已经长毛了,但是土豆已经切成块下锅了,好在家里还有一块咖喱,于是肉烩土豆被你改成了咖喱炖土豆;最后饼成功的烙出来了,外酥里嫩,热气腾腾,然而吃饭时你妈告诉你:“傻孩子,现在还没出正月呢,忌讳吃饼的,多不吉利”。你不禁内牛满面:“我他么这是做饭吗,这是作孽啊”
是不是上面一段看着就这么蛋疼,为什么,是因为做饭前的准备工作没有做好。当然准确的说法是:没有提前确认好做饭这个事的可行性(能吃烙饼吗)以及先关的前置任务和准备工作。
在实际工作中,一些业务也要求确认可行性、前置任务和准备工作。
下面是一个真实的业务,只是使用业务不敏感的方式表达。
假设,有这么一种业务,我们先进行约定,用大写字母表示此业务中涉及到的一种业务对象,数字表示这种业务对象中的不同个体(快速理解方式:字母表示不同的表,数字用来区分表中不同的数据,可以理解为id),比如A-1,A-2,这就是同一种业务对象但是是两个个体。
现在,在这业务里,你需要删除某个对象。问题在于,删除数据A-1时,需要删除B-1,B-2,B-3;删除B-2时,需要考虑删除C-1,C-3,但C类对象删除时可能影响D类对象,如果影响,不能删。那么在极(e)端(xin)的情况下,这个展开无法界定最大层数。
肯定有同学的第一反应是对每个对象提供删除方法,而后云递归(笔者瞎起的名字,意为若干个方法循环调用最后起到了递归的效果。他其实就是递归,但有人纠结递归必须是方法A调用方法A)调用。
这确实可以解决一定的问题,但是,还有限制,要不怎么叫极(e)端(xin)的情况呢。首先,操作方希望知道比较准确的删除进度;其次,删除时有异步删除;最后,部分对象,一旦删除无法找回。此时,递归方式就废了。
§1.2 与流程机制的比较
动态任务树和流程同作为两种机制是有相似性的:
- 都可以处理复杂业务
- 任务树的节点和流程节点的实现在某些场景下是互通的
- 都比较容易扩展(这里指定是动态任务树和流程作为机制,可以轻易容纳他们原本容纳的业务的扩展)
而两者又存在区别:
- 适用场景不同。相较而言,动态任务树更适合处理 "要在执行才能由程序分析出来整个业务处理包含哪些内容",而流程在流程开始执行之前就可以完整的知道整个流程中所有的节点和完整的进度。前者要动态的解析出要干什么,后者一开始就知道要干什么,只需要按顺序(按条件)做就好
- 执行路径不同。按执行时逻辑的顺序而已,流程可能出现链状执行,环状执行,图状执行;动态任务树基本只可能出现树状执行(特殊情况可能出现链状)
- 设计初衷不同。流程机制仅仅是一种执行机制,它的设计初衷就是为了可以比较优雅的处理负载而多变的业务群;而动态任务树可以作为执行机制使用,也可以作为分析机制使用,可以使系统自动按某种方式(思路)将一个问题解析为多个问题,勉强算是P=NP思想的实现。
- 可预知性不同。流程因为是预先知道的,所以实现的流程和流程节点是作为模具存在的,而每一次业务调用则可以相对的作为"实例"存在。而动态任务树是一个完全根据已知条件对业务进行动态处理的机制,没有预设的模具。换句话说,如果将如何解决本次触发的业务问题视为模具的话,那么动态任务树连模具都是生成的。
§1.3 动态任务树的主要用法
-
解析,将需要处理的问题作为原始任务,通过动态任务树进行分析,分析的结果是处理当前任务所需要处理的所有条件和前置任务的树。
-
执行,从叶子节点逐渐完成整棵树的执行,整棵树的执行完成或终止即原始任务的执行完城和终止
-
校验,通过动态任务树分析每个步骤(子任务)的可行性
区分用法的意义:在很多情况下,分析问题和实际解决不见得是连贯的,不是规定好了用一个方式(机制)分析完问题就要继续用这个机制解决问题的,即达到动态任务树的阶段目的后,可以将后续工作交付给更合适的处理机制。
动态任务树形成后,一个任务的子一级任务都是可以并行执行的(不能并行执行说明有先后关系,应该分出先后),因此没有必要通过遍历树串行执行。另一方面,基于任务树的解析完成后,解释的结果完全是数据化的,完全可以通过批量扫描出相关任务触发执行,不用继续基于任务树执行。
动态任务树的复杂度在极端情况下是指数级的,因此应该限制使用场景,并提供良好的截断(在达成任务树目的或任务树后面的运算都是无意义的时,直接终止整颗任务树)功能
综上,业务的处理方式的优先级按降序应为:不用任务树>使用任务树解析>使用任务树校验>使用任务树执行
§2 P+思想
§2.1 原始的 P=NP 问题
首先介绍一下原始 P=NP问题,这是一个算法领域的问题,有精力的同学建议参考 P=NP 的牛逼扫盲贴,P=NP 的牛逼扫盲贴。
因为最近才开始写东西,若上面的引用不恰当或不礼貌,请看各位看官轻怼,怼后受累告诉我合理的引用方式。
不耐烦的同学看我下面的总结,注意,此帖子中所谓的P+思想的祖宗是这个原始的P=NP,但其实和它关系不大,万勿走火入魔。
算法复杂度:不知道算法复杂度的同学受累先出门左拐问度娘,只需要了解复杂度大概啥意思、有哪些级别以及复杂度表达式中n是啥意思就行了
多项式复杂度:O(1) O(n) O(logn) O(nk)(k是常数) 的复杂度被视为多项式复杂度,特点是n在复杂度表达式的底数的位置上。这意味着算法的复杂度至少在一定程度上可以接受。
多项式时间:在多项式复杂度下处理问题的时间。注意,这只是一个概念,不是一个特定的时间长度,下面遇到这个词时,直接把释义替换这个词读就行了,不要试图额外理解出什么来,比如“多项式时间到底是多长时间?”,它只是“在多项式复杂度下处理问题的时间”。
P问题:能在多项式复杂度下解决的问题。这有两层含义,第一,可以解决问题,第二在可以接受的复杂度的前提下解决(这问题本身不变,只在数据量增长时,所需的时间可以被接受)
NP问题:NP问题是指可以在多项式的时间里验证一个解的问题。NP问题的另一个定义是,可以在多项式的时间里猜出一个解的问题。
虽然完整的解决一个问题目前没有找到多项式复杂度的解法,但是如果我只想验证这个方法的某一个解却很简单,可以再多项式复杂度下验证。这意味我我可以在一个多项式复杂度的前提下猜出这个问题的解。用一种最low的说法,我虽然不能直接解决这个问题,但我知道这个问题的可能性无外乎可能性1,可能性2……可能性x,没有别的情况了。那么只要我能在多项式时间内穷举此问题的所有可能性并进行一一验证得出结果,那么这个问题就是NP的。
P∈NP:很明显,如果把所有的P问题都放在一起,所有的NP问题都放在一起,那么后者一定是包含前者的。你都可以直接用好的算法解决了,一一验证有啥难的。
P=NP:原始的P=NP的问题的焦点在于,所有P问题的集合和所有NP问题的集合是不是就是一个集合,即不存在属于NP问题但不是P问题的问题。
P=NP的意义:假设P=NP成立,这就意味着凡是能再多项式时间内被试出来结果的问题,都是潜藏的P问题,换句话说我们之所以只能靠试来解决NP问题是因为我们暂时没有找到更好的多项式复杂度级的解决这个问题的算法。也就是说,不是这个boss不可战胜,而是我们还需要练级。
看完以上内容就可以跳过这小节剩余的部分从 §2.3 继续看下面的部分了。有兴趣继续纠结这问题的同学可以继续看这小节,我继续给你们瞎JB编[手动滑稽]。
现在的整体趋势是认为P≠NP,先看下面一个高大上的概念
归约(约化):这是一个比较复杂的概念,我用一种可能存在理解偏差但好理解的方式进行表达。假设,现在存在两个问题,一个简单问题一个复杂问题,如果简单问题可以向复杂问题转换,我们就说这两个问题之间可以归约。
那么什么叫可以转化,我个人如是理解(必有误差,但大概其意思大约是对的),一个相对简单的问题经过一系列演变可以变成一个相对较复杂的问题,同时可以用于解决较复杂问题的方法经过一系列的转换(降级、消元、变形)也可以转变成用于解决简单问题的方法。
归约的意义:若问题A可以归约成问题B,那么问题A一定不如问题B复杂,同时可以解决简单问题A的办法一定不会比可以解决复杂问题B的方法更复杂。换句话说如果一个简单问题可以归约成一个复杂问题,并且找到某种复杂度的这个复杂问题的解法,那么一定存在至少一个解法用于解决对应的简单问题,并且其解法的复杂度不会超过那个复杂问题解法的复杂度。
对归约姑且举个例子:一个比较好理解的例子是“求正方形、矩形、平行四边形的面积”。很明显正方形面积是矩形面积的特例,矩形面积又是平行四边形面积的特例。求正方形面积可以归约为求矩形面积,求矩形面积可以归约为求平行四边形面积。这个很好理解吧,连这个都忘了的同学你们看着下图慢慢想,我去帮你们拦住你们的小学数学老师,我好像看到他去拿刀了。
归约的更近一步意义:从一个简单问题和它的简单解法出发,我们可以归约出一个更复杂但涵盖范围更广的问题,并找出复杂度可能更高但能解决这个范围更广的复杂问题的解法。并且结合上文,若这个复杂问题都能找到多项式复杂度下的解法,则简单问题一定也可以。
从归约引申出NPC问题:若一个NP问题,不断的向上归化,并用更复杂但具有更大适用范围的方法解决,最终可以归化为一个极端复杂包容所有NP问题的问题,这种问题就是NPC问题。其意义在于,如果NPC问题具有多项式复杂度的解,则其下所有NP问题都有多项式复杂度的解。用更通俗的表达,NP问题表示只能在多项式时间中猜出解的问题,P问题表示能再多项式时间中通过算法算出解的问题;如果NPC问题具有多项式复杂度的解,则表示所有NP问题都有多项式复杂度的解,有多项式复杂度的解的问题就是P问题,因此若NPC问题有多项式复杂度解则P=NP成立。
P≠NP的普遍认知:目前主流认为P≠NP,因为为一个NPC问题找到一个多项式复杂度的解实在无法想象,有点拉普拉斯妖的感觉了。即使这种牛逼问题有解,也很可能是一个指数级或阶乘级复杂度的解,因此不太具有意义。
PS:拉普拉斯妖--如果一个智者能知道某一时刻所有的力和所有物体的运动状态,那么未来就会像过去一样出现在他的面前。这个拉普拉斯口中全知全能的智者,后来被人称为“拉普拉斯妖”。然后这个东西被量子物理干翻了……额……好像是。
之所以加上上面一段不影响阅读的内容,一方面是尽量完整的介绍NP问题,另一方面是为了引出归约这个概念。
§2.2 关于归约&机制
有一种研发人员的工作模式可以被称为纯翻译式开发(面向需求翻译编程),即拿过需求后,只针对需求本身进行有限实现。
与之对比,比较好的方式是(尤其)对于复杂多变不稳定的需求或功能点,建立一套可以解决此类问题的机制并用这套机制反过来包容当前的需求。这么做到好处是:在接下来的一段时间内,这套机制可以满足一定程度的需求的扩展,并可能在机制无法满足新的需求的情况下暴露出方便扩展机制的接口,以继续可以包容新需求。
包容新需求的好处是显而易见的:
- 是风风火火的撸代码的工作量大,还是从容不迫的增改配置、数据的工作量大
- 并且更有意义的在于,前面两种情况下哪种模式可以更少的挖坑
- 从更开阔的视角看,两种模式哪一种可以保持团队成员心态不崩,更有利于团队持久稳定。
尽量规避翻译式开发,在需要时考虑需求的向上抽取,是中高级程序员(一家之言)应该具备的一种意识和能力,虽然不同阶段可能在这种能力上表现出不同的火候。
所谓向上抽取需求的本质其实是归约思想:用一个更复杂的但适用范围更广的问题代替当前容易解决的、适用范围比较局限的、但在未来很有可能变复杂的简单问题,并用这个问题的解决方式容纳目前简单问题的解决方式(这算是找到理论基础了,这逼格立刻就高了,然后你看和原始P=NP关系确实不大)。
当上述的复杂问题复杂到一定程度时,这个问题就会自然的演变成一种机制,解决这个复杂问题就等于实现一种业务相关的机制。
在有必要的情况下(更准确说是有可行性的条件下尽可能),使用实现机制的研发思路代替快速简单暴力的生怼需求(这相当于是生产线和手工制作的区别),有利于产品研发成本、研发质量、研发速度的控制,提高产品和研发团队及其中人员的稳定性。尤其是在控制质量和维持产品稳定性上应该具有显著效果。
归约(向上抽取需求)是程序设计能力(在多种实现方式的情况下,为什么非要这么撸代码)的重要组成部分。这种能力和意识(至少在未来会)被显示和隐式的重视,并成为中高级/资深业务型(或称设计型、业务设计型,强调程序设计能力,与技术深入型和架构型区分)工程师、项目/技术经理的一个重要衡量标准。
这是因为:
一方面,这是研发人员自身的一种价值。随技术、市场、行业发展,能满足“正常完成需求”这一要求的研发人员越来越多,那就更强调对项目、业务、团队有用的闪光点。
另一方面,这是研发团队的一种需求。随技术的不断演进,越来越多的公司在业务扩展上的技术成本会越来越小,这意味着越来越多的公司会越来越容易的在自己的产品中扩展越来越多的功能(或需求)。成本小了必然会促进业务扩展,但业务扩展后体现在项目里不是没有代价的,比如业务复杂度上升、运维成本增加、项目可维护性降低。若控制不当导致这些代价愈演愈烈,开发团队最终会主动或被迫寻求解决方式。当这种诉求越来越多时,越来越多的团队会希望至少业务一定程度的扩展不会造成上述问题更大程度的升级,他们当然希望一些研发人员的实现可以容纳一定程度的业务迭代,并具有良好的稳定性和可扩展性。
最后,上述梦呓跑题了
§2.3 P=np(咱这的Low版P=NP,注意大小写)
强行拔高逼格完毕,言归正传。咱们目前说的P+思想和原始版本P=NP问题虽然有一定联系,但有明显的本质区别。区别在于:P=NP问题是算法层面的问题,而咱目前说的是业务实现层面的问题。
业务实现层面的问题意味着,根本不会出现复杂的“NP”问题,甚至算法中所谓的NPC问题。想象一下,产品同学提出了这么一种需求,它无法在用户可接受的时间里直接处理,顶多在这个时间里猜/验证出处理结果,甚至这个需求是一大坨不可直接处理问题的集结体。
如果这种需求出现了,那么如何解决先放一边,业务/需求的合理性和可行性才是首先需要验证的问题,并且你是不是想先打产品一顿。
为了区分和原始的P=NP的区别,把它的书面形式记录为 P=np
P=np的含义为:
- 对一个简单问题(需求)p,进行若干次(n)演变、堆叠、扩展,它会变成一个足够复杂的问题P
- 对一个复杂问题P,可以拆解为若干个或若干层(n)简单问题组合、串联
- 如果想解决一个复杂问题P,一个通用且可操作性的思路是拆解出组成这个问题P的各个简单问题p,并按照简单问题p的串联顺序和组合关系顺次解决,最终解决复杂问题P
上述含义同时可以反向使用,通过简单需求归约出简单需求的复杂版,整体思路就是将问题合理的复杂化,使用变量替代目前确定的场景。
这当然不是一本正经的扯淡。其意义在于,若当前业务场景下,某条件是确定的,但很可能在可预期的时间内迭代为不确定的。那么将这个条件直接设想为可变的,在此基础上实现功能,则真的到了变动的时候,就可以做到微微一下,绝对不抽。
这里只有三个东西需要注意。首先,时间/精力够不够,不够的话优先保证进度正常,不能因此耽误正常的项目进度,或影响其他人的进度。从初衷上讲,把需求不复杂化在实现的目的是使它质量更高、更健壮、更有前瞻性,而不是作为“我把这玩意搞砸了”的借口。其次,预期的迭代点准不准。如果准确,这是具有前瞻性的程序设计、业务实现,如果偏差太大,比较容易形成过度设计。保证预期准确的最好方式,就是和自己的产品、项目经理、teamleader保持积极、良好且准确的沟通。如果沟通的过程中,他们反复告诉你不要多想,按需求做时,应该先反思是不是自己的预期是不靠谱的,并及时调整。但若他们多次告诉你按需求做,而后面在迭代时其迭代点就是你之前与其沟通的那些,但迭代后的需求往往造成原本的实现因不兼容而需要相当程度的重做时,你需要衡量自己的忍耐力。最后,复杂化需求并实现后,项目的操作方式不能和原始设计有明显偏差,除非和产品等同学沟通后,他们允许。
§2.4 P=np+1
在P=np的基础上,可以引申出 P=np+1 问题,若将P视某个问题,将p视为从P中拆解出的第一级子问题,则1代表P本身。如下图,假设问题p是从问题P中拆分出来的第一级子问题,p下有没有更小的问题在这个场景下不关心,P上有没有更上级的问题这个场景下也不关心。那么现在又两种可能的场景:
- 解决完三个问题p的同时,问题P也就解决了。这其实就是P=np
- 解决完三个问题p,只意味这解决问题P的前期准备已经完成了,问题P本事还需要切实的处理
§2.5 P=np与P=np+1
假设我们将一个问题通过P=np或P=np+1的思路分析完成,则二者最直接的区别在于,前者只有所有的叶子节点需要处理,后者所有节点都需要处理。
在实际问题中其实是区分:上级问题是完全由下级问题组成的还是下级问题仅仅是解决商机问题的前置条件,通常的,后者可能性更大一些。
在极少的情况下,P=np+1可以转化为P=np,但这要求1和np的先后关系可以解除。目前我能想到的唯一的通用转化思路是,在一定的场景中通过解决最终一致性的手段将1降为p的平级,并通过多次触发节点实现转变(但细琢磨还是蛋疼,有人想到了请怼我)。
而在绝大部分情况下,若某个逻辑的执行以其他逻辑执行完成为前提条件,则这种先后关系并不容易或并不适合破除。
§2.6 P+思想
这个其实没什么可说的了,P=np与P=np+1统称为P+思想。
P+思想的意义在于:
- 分析某个具体问题(P)的解决方案时,可以使用此思想将负责问题进行拆解,形成若干个问题和若干个问题之间的联系两个方面,并通过分析这些内容逐渐形成解决原始问题的思路和原始方法
- 分析如何实现某需求(我要把这个需求实现到什么程度)时,若确定需要对需求进行归约。思路是在当前需求固定的细节处增加变量,使它不确定,而后在这个基础上分析,目的是提前容纳需求变更
- 实现功能时,通过此思想可以讲一个大段完整的业务逻辑转化成功能片段(组件)和片段(组件)的组合两个方面。后者的优势在于,这种实现方式往往更容易扩展、优化、调整,使业务相关代码具有更好的可持续迭代性。
- 执行功能或逻辑时,可以用于处理如何使用逻辑片段和其联系实现完整功能(注意和上面一点不同,这里处理的是如何在程序运行中动态的分析问题,动态执行完整逻辑;上面是在开发前如何设计完整逻辑)
概括的讲这是一种可以帮助分析问题解决问题的思维指导。
§3 动态任务树机制的几个普遍概念和问题
§3.1 动态任务树的分类
一般按照使用目的区分,可以分为解析任务树、执行任务树、校验任务树、复合任务树
按节点类型(节点与子节点的关系)区分,可以分为组合树(节点是子节点的组合,可称为n树)和前置树(子节点只是节点的前置,节点本身也有需要处理的内容,可以称为1树)。可能存在组合-前置树,即两种节点混用,暂未发现对应的实际需求,故存疑
§3.2 动态任务树的通用开发思路
动态任务树的开发可以归纳成任务树的生长和任务树的使用。
§3.2.1 动态任务树的生长
动态任务树的生长,本质是动态任务树节点的衍生;动态任务树生长的过程,本质是通过任务树对问题进行解析的过程;动态任务树生长的实现,是对P+思想的应用。
动态任务树节点如何进行衍生其实是由两个关键问题进行操控的:仅就当前正在分析的动态任务树节点来讲,在解决它的时候涉及到哪些主体和行为,之后重复分析这两个问题即可。
所谓主体,是指需要被操作的数据或对象,比如原始需求中出现的各种对象;所谓行为是能对主体进行的操作,比如增删改查、验证等。
明确解决某一个节点的主体和行为,并重复这个过程,能否解决任务树的生成问题?答案是,不可以。
主体和行为只是最关键的部分,还有一些算是辅助的问题需要分析(随机制完善,辅助问题可能会扩展):
- 任务树的节点是n型的还是1型的
- 任务树节点的子节点之间是不是还存在先后顺序(但不构成上下级,因为若认为只要不能同时执行就需要安排父子关系,则对于较复杂业务,任务树的生长结果可能会非常爆炸)
主体和对主体的行为可以组合出任务树的节点(反过来想可能更好理解,任务树的一个节点里定义的是不是就是对一个什么东西做什么操作)因此说它们是关键的,辅助问题其实是确定了任务树节点的组合关系,这两个部分才能完整的描述任务树的生长。前者明确的任务树生长时都长出了什么节点,后者明确了新长出来的节点长在了哪。
§3.2.2 动态任务树的执行
实际上,并不推荐直接执行任务树。一方面这意味着树的遍历,效率不太可能高,另一方面在树生长的过程中,完全可以将节点进行持久化并在树外进行反向消减,即从叶子开始逐层解决,而这可以通过设计数据字段达成。
§3.2.3 动态任务树的截断
前面说了,动态任务树只是一种在业务进行中动态解析并生成相关结果或具体执行方案的一种手段,或者更准确一点,一种不得已的手段。这么说是因为它确实是一套可以包容很多业务的机制,但是机制的效率实在不高,因为它的复杂度在极限情况下是指数级的。
因此,我们需要注意两点。
首先,如果没有必要,不要使用此机制。具体的来讲:只有当当前需求无法在代码阶段书写完整流程,只能明确分析方法;或在未来不可避免的出现这种场景;或当前需求虽然可以有完整流程但细节情况太多,以致于逐个开发会使代码臃肿逻辑不清;针对业务中各个步骤单独分析远比对业务本身进行整体分析有优势时,才需要使用动态任务树。
除此之外,即时停止无意义的动态任务树计算。如果,在某个阶段执行后,已经可以直接影响整颗任务树的状态,则应该马上终止整颗树的处理,并返回结果,这称之为动态任务树的截断。比如,分析树运行过程中,通过某一次节点衍生,发现被分析的业务不可行,应该截断树;校验树运行过程中,通过某一次节点衍生,可以确认校验树结果(比如,只要出现校验不通过的节点则整颗树校验不通过),应该截断;执行树运行过程中,某一节点处理结果直接影响整棵树的(比如某节点挂了,但业务中不允许),应该截断。
动态任务树的截断可以大致分为两类,生长时截断和执行时截断,前者用于解析树和部分校验树,后者通常是用于执行树。
截断可以通过标记方式实现,此标记在树生长或执行时会随时被检查。
§3.2.4 动态任务树节点行为
- 衍生前置校验
在当前节点进行衍生前进行的校验,通常用来校验当前节点存在的合理性或执行的合理性等
- 前置衍生
当前节点执行前,还能从当前节点衍生出子问题
- 衍生后置校验
在当前节点进行衍生后进行的衍生校验,通常用来校验衍生结果(可以比衍生前置校验提前一轮获取相关结果)。但若衍生出的节点数量不可控或校验逻辑复杂则应该在子节点的衍生前置校验中进行相关处理
- 执行前置校验
当前节点执行前的校验
- 执行
节点本身需要处理时,节点具有执行行为
综上,完整的任务树节点的行为可以如下描述:
分析当前节点是否应该存在(衍生前置校验),分析前置衍生,衍生后判断是否进行衍生后置校验,如果没有衍生进行执行前置校验,执行。
§3.2.5 常规设计套路(已经有demo级实现,但需要时间整理)
表设计
动态任务树总任务表,包括原始任务,信息,参数信息,任务树的状态(创建,分析,校验,执行,完成),业务号(bz_id),截断级别(存疑,和业务有关系)
动态任务树节点表,由总任务衍生的所有节点都在这个表里,每个节点除了常规的节点信息外还可以添加易用型字段,比如是否是叶子节点(动态任务树的执行是从叶子节点开始的),每个节点的层级,父节点id(用于从叶子开始逆向执行时查找上级),状态等
执行顺序
如果动态任务树是需要执行的,优先考虑在动态树外,通过处理动态任务树节点表的数据。首先获取任务树中的层级最大的未执行成功的节点,并进行处理并取得结果(完成或挂了),修改任务树节点状态。随后,依旧获取层级最大的未执行成功节点,此时取到的是倒数第二层。以此类推可以处理整颗任务树的执行。
关键对象设计
动态任务树的实现重点是动态任务树节点。针对不同的节点功能,宜开发专门的处理类:
可靠性校验器,用于校验就某一个节点来说,其存在是否合理,或者理解为如果这个节点真的执行了,是否带来不期待的影响
任务分析器(衍生器),用于拆解出新的节点,这些节点就是前置任务
执行校验器,可以根据树的数据判断当前节点是否可以开始执行(前置节点都处理完了)或已经完成(n型节点时完成了所有子任务)