- 回顾一下组合模式。这种思想强调的是,对于递归的数据结构,例如权限树,文件树等,对节点的遍历、检测、删除等操作要使用递归而不是迭代。这样可以让代码更加易懂,更加利于维护。
- 回顾一下访问者模式。它经常与组合模式结合起来使用,所谓“访问”,就是对树的操作。每种操作都可以有一个具体的访问者,例如删除操作访问者,检测操作访问者等。也就是说,对于一棵树的节点,我们不仅要规范他们的遍历方式(递归),也要将递归操作的逻辑做出抽象,就是利用访问者。
- 下面以权限删除功能为例,演示如何在项目中使用这两种模式。
检测逻辑
- 检测某个节点及其子节点是否与某个账号或者角色相关联。
- 首先为访问者定义接口。
public interface PriorityOperation<T> {/*** 执行这个操作* @param priority 权限* @return 处理结果* @throws Exception*/T doExecute(Priority priority) throws Exception;}
- 在权限树节点
Priority中声明一个接收访问者并执行的方法。
/*** 接收一个权限树访问者* @param operation 对权限树的访问者*/public <T> T execute(PriorityOperation<T> operation) throws Exception {return operation.doExecute(this);}
- 实现访问者接口,编写负责检查的访问者的逻辑(只展示核心代码)。注意,像这种检查型的访问者,一般返回值都是
true/false,所以一定要保证这个类是原型模式。也就是每次从容器中取出来,都是一个新的实例对象。这是因为如果有两个线程同时执行删除权限操作,虽然可能不是一个节点,但如果他们都使用的是一个访问者,那么下述访问者的relateCheckResult属性可能不准确!
@Component@Scope("prototype")public class RelatedCheckPriorityOperation implements PriorityOperation<Boolean> {/*** 访问权限树节点*/@Overridepublic Boolean doExecute(Priority node) throws Exception {//1.找出当前节点的所有子节点List<PriorityDO> priorityDOs = priorityDAO.listChildPriorities(node.getId());//2.遍历子节点,对每个子节点同样执行检查操作if(priorityDOs != null && priorityDOs.size() > 0) {for(PriorityDO priorityDO : priorityDOs) {Priority priorityNode = priorityDO.clone(Priority.class);priorityNode.execute(this);}}//只要任意一个节点与角色或账号有关联,relateCheckResult属性就为trueif(relateCheck(node)) {this.relateCheckResult = true;}//递归出口return this.relateCheckResult;}/*** 检查权限是否被任何一个角色或者是账号关联了* @param node 权限树节点* @return 是否被任何一个角色或者是账号关联了,如果有关联则为true;如果没有关联则为false*/private Boolean relateCheck(Priority node) throws Exception {Long roleRelatedCount = rolePriorityRelationshipDAO.countByPriorityId(node.getId());if(roleRelatedCount != null && roleRelatedCount > 0) {return true;}Long accountRelatedCount = accountPriorityRelationshipDAO.countByPriorityId(node.getId());if(accountRelatedCount != null && accountRelatedCount > 0) {return true;}return false;}public Boolean getRelateCheckResult() {return relateCheckResult;}}
- 在
PriorityServiceImpl中使用访问者,注意,从Spring容器中拿出来的访问者是一个原型而非单例。
/** 检查这个权限以及其下任何一个子权限,是否被角色或者账号给关联着**///原型的访问者RelatedCheckPriorityOperation relatedCheckOperation = context.getBean(RelatedCheckPriorityOperation.class);Boolean relateCheckResult = priority.execute(relatedCheckOperation);if(relateCheckResult) {return false;}
删除逻辑
- 看懂了检测逻辑,删除逻辑就如出一辙了。
- 实现访问者接口,编辑递归删除的访问逻辑。
@Component@Scope("prototype")public class RemovePriorityOperation implements PriorityOperation<Boolean> {/*** 权限管理模块的DAO组件*/@Autowiredprivate PriorityDAO priorityDAO;/*** 访问权限树节点* @param node 权限树节点*/@Overridepublic Boolean doExecute(Priority node) throws Exception {List<PriorityDO> priorityDOs = priorityDAO.listChildPriorities(node.getId());if(priorityDOs != null && priorityDOs.size() > 0) {for(PriorityDO priorityDO : priorityDOs) {Priority priorityNode = priorityDO.clone(Priority.class);priorityNode.execute(this);}}removePriority(node);return true;}/*** 删除权限* @param node 权限树节点*/private void removePriority(Priority node) throws Exception {priorityDAO.removePriority(node.getId());}}
- 在PriorityServiceImpl中使用。
// 递归删除当前权限以及其下所有的子权限RemovePriorityOperation removeOperation = context.getBean(RemovePriorityOperation.class);priority.execute(removeOperation);
优化
- 还可以对各个访问者抽取一个抽象基类。以类目相关的访问者为例。也就是抽取了遍历孩子的逻辑。利用了模板方法模式。
public abstract class AbstractCategoryOperation<T> implements CategoryOperation<T> {/*** 类目管理DAO组件*/protected CategoryDAO categoryDAO;/*** 构造函数* @param categoryDAO 类目管理DAO组件*/public AbstractCategoryOperation(CategoryDAO categoryDAO) {this.categoryDAO = categoryDAO;}/*** 执行类目操作* @param category 类目树节点*/@Overridepublic T doExecute(Category category) throws Exception {//先对孩子进行访问doExecuteForChildren(category);//再执行自己的逻辑doRealExecute(category);return getResult();}/*** 递归对子类目执行当前操作* @param category 当前类目* @throws Exception*/private void doExecuteForChildren(Category category) throws Exception {List<CategoryDO> children = categoryDAO.listChildren(category.getCategoryId());if(children != null && children.size() > 0) {for(CategoryDO child : children) {Category childCategory = new Category(child.getId());childCategory.execute(this);}}}/*** 执行实际的操作* @param category 类目* @throws Exception*/protected abstract void doRealExecute(Category category) throws Exception;/*** 获取操作的执行结果* @return 操作的执行结果* @throws Exception*/protected abstract T getResult() throws Exception;}
总结
之后我们如果再想对权限树中的某个节点进行递归操作,直接实现访问者接口,编写访问逻辑,把它以原型的模式放在Spring容器中,随用随取,然后让节点实例直接执行访问逻辑即可。
