2022.3.23更新
组合模式在很久之前只看过一次,一直没用过,理解的也不是很透彻,最近又看了看,我改造出来的这个设计模式与组合模式可以说是一模一样,所以我造了一个组合模式?不管怎么说,这篇文章写完之后,我对组合模式有了一个很清晰的理解。
不知道有多少是冲着文章题目点进来的,设计模式一共23种,哪里蹦出来个任务树模式?
任务树(TaskTree)模式是我为了解决一个级联操作,在责任链模式和策略模式的基础上改造而来的。
为什么不用现有的设计模式
起初我有考虑过使用责任链模式或者观察者模式,但我面临的问题可以抽象成一个树状的结构,孩子节点需要用到双亲节点产生的数据,类似于一个除根节点外,所有节点入度都为1的有向无环图。显然,种非线性的结构用责任链这种线性结构处理起来并不方便。而每一处理节点所用的数据,是经过上一级处理过的,并不是所有处理节点都使用相同数据,因此使用观察者模式也不合适,更何况观察者模式的不同观察者之间并不存在一种先后次序关系,无法传递中间数据,所以就有了“任务树模式”。
为什么叫任务树
因为树中的每个节点都要执行相应的子任务,孩子节点使用双亲节点产生的结果,兄弟节点之间使用从上级传来的、相同的数据(感觉描述怪怪的,稍后看代码应该就清晰了),最终这颗树完成的是一个任务,所以称它为“任务树”。
与观察者模式、责任链模式对比
责任链模式是将请求者和处理者解耦,一个请求进入责任链后,动态选择在某个能处理这个请求的节点执行,也就是说链中的有些节点时不会真正执行的,而且一个请求进入之后可能知到链尾也没有得到处理。
任务树模式是根据实际需求去构建一个树,请求进入后,树中的每个节点都会按相应顺序执行。
观察者模式中,所有观察者收到的通知是一样的,观察者之间没有鲜明的层级关系,所有观察者可以看作是同时执行,而任务树中有鲜明的层次关系,只有必要的父级节点执行完成,相应的孩子节点才会执行。
具体实现
先上一张类图,这里直接把任务树和策略模式结合到一起了(画的比较随意,可能不是很标准)
其中Handler可以有多个,实现抽象类TaskTree,是任务树的节点类,用于构建任务树;Strategy接口、Strategy类、Context类属于策略模式,Strategy类有多个,实现Strategy接口,提供构建不同任务树的具体策略。
以学院、专业、班级、用户、考试信息级联删除数据为例,实现一个任务树。假设删除学院信息时,CollegeHandler要给根据学院id,查询出该学院下有哪些专业,然后传给MajorHadnler,删除学院数据;MajorHandler根据上级传来的数据查询专业下有哪些班级,传递给ClazzHandler和ExamHandler,然后删除专业数据;ExamHandler根据班级数据,查询有哪些开始信息,然后删除考试信息;ClazzHandler要查询出班级下有哪些学生,传递给UserHandler,然后删除班级数据;UserHandler根据传来的学生id删除学生数据。单纯语言描述不太直观,放个图:
TaskTree
import java.util.List;
public abstract class TaskTree {
private TaskTree child; // 孩子节点
private TaskTree sibling; // 兄弟节点
// 节点处理过程中要用到的数据
protected Long id;
protected List ids;
protected Boolean result; // 处理结果
public TaskTree() {
this.id = null;
this.child = null;
this.ids = null;
this.sibling = null;
this.result = true;
}
public TaskTree(Long id, List ids) {
this.id = id;
this.ids = ids;
this.result = true;
this.sibling = null;
this.child = null;
}
public TaskTree child() {
return this.child;
}
public TaskTree sibling() {
return this.sibling;
}
public TaskTree setChild(TaskTree node) {
this.child = node;
return node;
}
public TaskTree setSibling(TaskTree node) {
this.sibling = node;
return node;
}
// 传播函数,用于向孩子节点、兄弟节点广播数据
protected Boolean propagation(TaskTree sibling, TaskTree child,
Long id, List ids,
List nextIds, Boolean result) {
if(sibling != null && child != null)
return child.doDelete(null, nextIds, result) && sibling.doDelete(id, ids, result);
else if(child != null) return child.doDelete(null, nextIds, result);
else if(sibling != null) return sibling.doDelete(id, ids, result);
else return result;
}
// 业务函数,具体在Handler中实现
public abstract Boolean doDelete(Long id, List ids, Boolean result);
}
Handler
Hadnler共有5个,分别对应处理学院、专业、班级、用户、考试信息。例子中几个类的实现大同小异,这里就只贴一个ClazzHandler的代码,具体代码见文末下载链接。
import cn.weingxing.task_tree.TaskTree;
import java.util.ArrayList;
import java.util.List;
public class ClazzHandler extends TaskTree {
public ClazzHandler() {
super();
}
public ClazzHandler(Long id, List ids) {
super(id, ids);
}
@Override
public Boolean doDelete(Long id, List ids, Boolean result) {
TaskTree sibling = super.sibling();
TaskTree child = super.child();
System.out.println("\n\nClazzHandler:");
// 如果是根节点,这里是个例子,具体如何判断要根据实际业务来定
if (ids == null) {
System.out.println("根据ID查询下级所需id数据:" + id);
List<Integer> nextIds = new ArrayList<Integer>();
nextIds.add(2122);
nextIds.add(3313);
// 模拟操作成功,记录结果
result = result && true;
System.out.println("根据ID查询并删除班级数据:" + id);
return super.propagation(sibling, child, id, ids, nextIds, result);
} else {
// 不是根节点
System.out.println("根据ids查询下级所需数据");
List<Integer> nextIds = new ArrayList<Integer>();
nextIds.add(2612);
nextIds.add(3351);
nextIds.add(2253);
// 模拟操作成功,记录结果
result = result && true;
System.out.println("根据上级传来的ids删除多条班级数据:");
for(Object i : ids) {
System.out.print(i + "\t");
}
return super.propagation(sibling, child, id, ids, nextIds, result);
}
}
}
使用任务树
实际应用中,可能需要构建不同的任务树,可以和策略模式结合,进一步解耦,这里只做一个演示,就不使用策略模式了,直接在主函数中建立一个任务树。
import cn.weingxing.task_tree.handler.*;
public class Main {
public static void main(String[] args) {
TaskTree tree = new CollegeHandler(); // 保留树根
// 构建任务树
tree.setChild(new MajorHandler())
.setChild(new ClazzHandler())
.setChild(new ExamHandler())
.setSibling(new UserHandler());
System.out.println("\n\n最终结果:" + tree.doDelete(1L, null, true));
}
}
运行结果
CollegeHandler:
根据ID查询下级id数据:1
根据ID查询并删除学院数据:1
MajorHandler:
根据ids查询下级所需数据
根据上级传来的ids删除多条专业数据:
222 333
ClazzHandler:
根据ids查询下级所需数据
根据上级传来的ids删除多条班级数据:
2212 3231 2223
ExamHandler:
根据ids查询下级所需数据
根据上级传来的ids删除多条考试数据:
2612 3351 2253
UserHandler:
根据ids查询下级所需数据
根据上级传来的ids删除多条用户数据:
2612 3351 2253
最终结果:true
其中如果某一节点执行结果为false,那最终结果也将会是false,如果这个任务是原子操作,结果为false时我们就可以愉快地进行回滚了。
总结
- 以上面的例子为例,这样实现之后,如果又引入了新的数据表,比如用户成绩表,在删除用户时要级联删除,那么只要新建一个Handler即可,无需对原有代码做过多修改,提高了可扩展性,降低了对象之间的耦合度,可以根据需要增加新的请求处理类,满足开闭原则。
- 各个Handler只负责自己的工作,符合单一职责原则
- 只需要维护各节点的孩子、兄弟指针,避免了大片的if...else...语句
缺点:
- 构建任务树之前要理清先后逻辑
- 树过于庞大时会影响系统性能
- 任务树构建不当时会导致循环调用
代码下载:点击查看
上海seo
看起来很复杂呢。。