• 回顾一下组合模式。这种思想强调的是,对于递归的数据结构,例如权限树,文件树等,对节点的遍历、检测、删除等操作要使用递归而不是迭代。这样可以让代码更加易懂,更加利于维护。
  • 回顾一下访问者模式。它经常与组合模式结合起来使用,所谓“访问”,就是对树的操作。每种操作都可以有一个具体的访问者,例如删除操作访问者,检测操作访问者等。也就是说,对于一棵树的节点,我们不仅要规范他们的遍历方式(递归),也要将递归操作的逻辑做出抽象,就是利用访问者。
  • 下面以权限删除功能为例,演示如何在项目中使用这两种模式。

检测逻辑

  • 检测某个节点及其子节点是否与某个账号或者角色相关联。
  • 首先为访问者定义接口。
  1. public interface PriorityOperation<T> {
  2. /**
  3. * 执行这个操作
  4. * @param priority 权限
  5. * @return 处理结果
  6. * @throws Exception
  7. */
  8. T doExecute(Priority priority) throws Exception;
  9. }
  • 在权限树节点Priority中声明一个接收访问者并执行的方法。
  1. /**
  2. * 接收一个权限树访问者
  3. * @param operation 对权限树的访问者
  4. */
  5. public <T> T execute(PriorityOperation<T> operation) throws Exception {
  6. return operation.doExecute(this);
  7. }
  • 实现访问者接口,编写负责检查的访问者的逻辑(只展示核心代码)。注意,像这种检查型的访问者,一般返回值都是true/false,所以一定要保证这个类是原型模式。也就是每次从容器中取出来,都是一个新的实例对象。这是因为如果有两个线程同时执行删除权限操作,虽然可能不是一个节点,但如果他们都使用的是一个访问者,那么下述访问者的relateCheckResult属性可能不准确!
  1. @Component
  2. @Scope("prototype")
  3. public class RelatedCheckPriorityOperation implements PriorityOperation<Boolean> {
  4. /**
  5. * 访问权限树节点
  6. */
  7. @Override
  8. public Boolean doExecute(Priority node) throws Exception {
  9. //1.找出当前节点的所有子节点
  10. List<PriorityDO> priorityDOs = priorityDAO
  11. .listChildPriorities(node.getId());
  12. //2.遍历子节点,对每个子节点同样执行检查操作
  13. if(priorityDOs != null && priorityDOs.size() > 0) {
  14. for(PriorityDO priorityDO : priorityDOs) {
  15. Priority priorityNode = priorityDO.clone(Priority.class);
  16. priorityNode.execute(this);
  17. }
  18. }
  19. //只要任意一个节点与角色或账号有关联,relateCheckResult属性就为true
  20. if(relateCheck(node)) {
  21. this.relateCheckResult = true;
  22. }
  23. //递归出口
  24. return this.relateCheckResult;
  25. }
  26. /**
  27. * 检查权限是否被任何一个角色或者是账号关联了
  28. * @param node 权限树节点
  29. * @return 是否被任何一个角色或者是账号关联了,如果有关联则为true;如果没有关联则为false
  30. */
  31. private Boolean relateCheck(Priority node) throws Exception {
  32. Long roleRelatedCount = rolePriorityRelationshipDAO
  33. .countByPriorityId(node.getId());
  34. if(roleRelatedCount != null && roleRelatedCount > 0) {
  35. return true;
  36. }
  37. Long accountRelatedCount = accountPriorityRelationshipDAO
  38. .countByPriorityId(node.getId());
  39. if(accountRelatedCount != null && accountRelatedCount > 0) {
  40. return true;
  41. }
  42. return false;
  43. }
  44. public Boolean getRelateCheckResult() {
  45. return relateCheckResult;
  46. }
  47. }
  • PriorityServiceImpl中使用访问者,注意,从Spring容器中拿出来的访问者是一个原型而非单例。
  1. /** 检查这个权限以及其下任何一个子权限,是否被角色或者账号给关联着
  2. **/
  3. //原型的访问者
  4. RelatedCheckPriorityOperation relatedCheckOperation = context.getBean(
  5. RelatedCheckPriorityOperation.class);
  6. Boolean relateCheckResult = priority.execute(relatedCheckOperation);
  7. if(relateCheckResult) {
  8. return false;
  9. }

删除逻辑

  • 看懂了检测逻辑,删除逻辑就如出一辙了。
  • 实现访问者接口,编辑递归删除的访问逻辑。
  1. @Component
  2. @Scope("prototype")
  3. public class RemovePriorityOperation implements PriorityOperation<Boolean> {
  4. /**
  5. * 权限管理模块的DAO组件
  6. */
  7. @Autowired
  8. private PriorityDAO priorityDAO;
  9. /**
  10. * 访问权限树节点
  11. * @param node 权限树节点
  12. */
  13. @Override
  14. public Boolean doExecute(Priority node) throws Exception {
  15. List<PriorityDO> priorityDOs = priorityDAO
  16. .listChildPriorities(node.getId());
  17. if(priorityDOs != null && priorityDOs.size() > 0) {
  18. for(PriorityDO priorityDO : priorityDOs) {
  19. Priority priorityNode = priorityDO.clone(Priority.class);
  20. priorityNode.execute(this);
  21. }
  22. }
  23. removePriority(node);
  24. return true;
  25. }
  26. /**
  27. * 删除权限
  28. * @param node 权限树节点
  29. */
  30. private void removePriority(Priority node) throws Exception {
  31. priorityDAO.removePriority(node.getId());
  32. }
  33. }
  • 在PriorityServiceImpl中使用。
  1. // 递归删除当前权限以及其下所有的子权限
  2. RemovePriorityOperation removeOperation = context.getBean(
  3. RemovePriorityOperation.class);
  4. priority.execute(removeOperation);

优化

  • 还可以对各个访问者抽取一个抽象基类。以类目相关的访问者为例。也就是抽取了遍历孩子的逻辑。利用了模板方法模式。
  1. public abstract class AbstractCategoryOperation<T> implements CategoryOperation<T> {
  2. /**
  3. * 类目管理DAO组件
  4. */
  5. protected CategoryDAO categoryDAO;
  6. /**
  7. * 构造函数
  8. * @param categoryDAO 类目管理DAO组件
  9. */
  10. public AbstractCategoryOperation(CategoryDAO categoryDAO) {
  11. this.categoryDAO = categoryDAO;
  12. }
  13. /**
  14. * 执行类目操作
  15. * @param category 类目树节点
  16. */
  17. @Override
  18. public T doExecute(Category category) throws Exception {
  19. //先对孩子进行访问
  20. doExecuteForChildren(category);
  21. //再执行自己的逻辑
  22. doRealExecute(category);
  23. return getResult();
  24. }
  25. /**
  26. * 递归对子类目执行当前操作
  27. * @param category 当前类目
  28. * @throws Exception
  29. */
  30. private void doExecuteForChildren(Category category) throws Exception {
  31. List<CategoryDO> children = categoryDAO.listChildren(
  32. category.getCategoryId());
  33. if(children != null && children.size() > 0) {
  34. for(CategoryDO child : children) {
  35. Category childCategory = new Category(child.getId());
  36. childCategory.execute(this);
  37. }
  38. }
  39. }
  40. /**
  41. * 执行实际的操作
  42. * @param category 类目
  43. * @throws Exception
  44. */
  45. protected abstract void doRealExecute(Category category) throws Exception;
  46. /**
  47. * 获取操作的执行结果
  48. * @return 操作的执行结果
  49. * @throws Exception
  50. */
  51. protected abstract T getResult() throws Exception;
  52. }

总结

之后我们如果再想对权限树中的某个节点进行递归操作,直接实现访问者接口,编写访问逻辑,把它以原型的模式放在Spring容器中,随用随取,然后让节点实例直接执行访问逻辑即可。