美文网首页
9. 迭代器模式 & 组合模式

9. 迭代器模式 & 组合模式

作者: bit_拳倾天下 | 来源:发表于2021-03-06 23:02 被阅读0次

    1. 迭代器模式

    什么是迭代器模式?

    它提供一种顺序访问一个聚合对象中各个元素,而又不暴露其内部的细节。

    没错,Java 的 Iterator 接口就是用这种模式实现的。我们用迭代器遍历时候并不需要知道不同的迭代器内部是如何实现的,我们利用迭代器遍历时,循环用 hasNext() 判断是否有下一个元素,用 next() 取出元素接口即可。

    不就是遍历么,直接循环不就行了?

    但是,想想这样一种场景:
    开发中有两个类,A 类内部有一个聚合对象用集合实现的,B 类内部的一个聚合对象使用数组实现的,系统某个地方需要调用这两个类的聚合对象进行遍历。

    这还不简单,分别做两次循环不就行了。 例子

    但是,要是再来一个 C 类、D 类呢?

    这时,迭代器模式的用处就体现出来了。把遍历的部分抽取出来,交给对应的迭代器。这样遍历的代码就可以复用了,如果后续又有 C、D 类等等,再实现新的迭代器。 实现迭代器 然后在代码中,只需要少量的操作,就可以完成遍历,至于迭代器如何迭代,使用者完全不用操心,他只要知道如何使用 hasNext()、next() 就行了。这样,遍历相关工作就解耦了。 使用者

    优点

    1. 将遍历逻辑和业务逻辑解耦,使用者不用关心如何迭代;迭代器允许使用者游走于聚合对象的每个元素,但又不会暴露内部细节
    2. 迭代器屏蔽了类型问题,使用者不用过分担心聚合类型是数组、集合、队列还是堆栈,只需要调用迭代器即可
    3. 使用者方便扩展

    而且,大多数聚合对象,Java已经帮我们实现好了,使用起来得心应手^U^

    2. 组合模式

    什么是组合模式?

    组合模式允许将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象和对象组合。

    简单来说,就是在树形结构中,“叶子结点” 和 “非叶子结点” 类型可能不同,但是又希望能够用同样的方式处理。
    一般情况下,普通的树形结构

    class TreeNode{
      List<TreeNode> children
    }
    

    如果树形结构都是完全用 TreeNode 实现的,甚至连子类都没有,那也就用不到组合模式,因为只有一个 TreeNode,不涉及不一致问题。

    但是如果,树结构不止是用 TreeNode,还用到不同的子类,在遍历和操作时需要区别对待,那随着类型的增多,这肯定是个麻烦事。如果能够屏蔽由于节点类型不同而产生的问题就好了!这时用组合模式就派上用场了。伪代码:

    //超类
    abstract class TreeNode{
      String name;
      /**
       *所有子类都会用到但又有所区别,可以声明称抽象方法
       *由子类各自实现
       */
      commonMethod();
      /**
       *不是所有子类都能用到的方法,就给方法默认实现;
       *需要该方法的子类自行实现,不需要该方法的子类则可以不实现
       */
      List<TreeNode> getChildren(){
        //默认实现
        return null;
      }
    }
    
    class Leaf implements TreeNode{
      //叶子节点不需要children
      conmonMethod(){
        //叶子方法
        System.out.print("我是叶子" + name);
      }
    }
    
    class Branch implements TreeNode{
      //叶子节点需要children
      List<TreeNode> children;
      conmonMethod(){
        //非叶子方法
        System.out.print("我是枝干" + name);
      }
      List<TreeNode> getChildren(){
        //实现
        return children;
      }
    }
    

    假设现在有这么个 TreeNode 对象,已经添加过 Branch、Leaf 两种类型的子节点,现在想通过 commonMethod() 方法打印所有节点名称

      treeNode.commonMethod();
    

    现在只能打印根节点名称,需要对 Branch 的 commonMethod 进行改造,让其能够递归打印,Leaf 的就不用了。

      conmonMethod(){
        //叶子方法
        System.out.print("我是叶子" + name);
        Iterator it = children.iterator();
        while(it.hasNext()){
          TreeNode node = (TreeNode) it.next();
          node.commonMethod();
        }
      }
    

    这样就可已完成一个内部迭代,实现打印所有的节点名称了。但是还可有一个问题,如果想在外边迭代,比如说想遍历这个 treeNode 的所有节点,做一些特殊的业务,这种需求就不能写在内部方法了,应该交由调用者遍历。

    按照我的想法,就在调用者中写一个方法,将根节点 treeNode 传进去,然后利用 getChildren 和递归遍历,好像没啥毛病,但是取到节点后所做的操作就都得放到这个方法中,遍历和业务会耦合在一起。如果业务经常变的话,还是像迭代器那样,用一个取一个比较好,至于取来做什么就不是迭代器该考虑得了。

    所以还是要实现一个能够递归的迭代器(从书上摘的):

    CompositeIterator implements Iterator{
      Stack stack = new Stack();
      public CompositeIterater(Iterator iterator){
        stack.push(iterator);
      }
      public Object next(){
        if(hasNext()){
          Iterator iterator = (Iterator) stack.peek();
          TreeNode node = (TreeNode) iterator.next();
          if(node instanceof Branch){
            stack.push(node.createIterator());
          }
          return node;
        }else{
          return null;
        }
      }
      public boolean hasNext(){
        if(stack.empty()){
          return false;
        }else{
          Iterator iterator = (Iterator) stack.peek();
          if(!iterator.hasNext()){
            stack.pop();
            return hasNext();
          }else{
            return true;
          }
        }
      }
    
    }
    

    由于涉及到多个迭代器,需要对迭代器进行存取,按照俺的习惯,肯定不管三七二十一,放个 List 来存取迭代器,貌似也可以实现,但是不如 Stack 这么方便。以后遇到这类问题还是应该稍稍思考一下。
    再给 Branch 增加一个创建迭代器的方法就行了:

      createIterator(){
        return new CompositeIterator(children.iterator());
      }
    

    这样就可以遍历用迭代器遍历了:

    Iterator it = treeNode.createIterator();
    //遍历代码..........
    
    hasNext() 流程
    next() 流程

    相关文章

      网友评论

          本文标题:9. 迭代器模式 & 组合模式

          本文链接:https://www.haomeiwen.com/subject/nldsqltx.html