【设计模式】行为型模式其四: 迭代器模式

621 阅读6分钟

迭代器模式

什么是迭代器模式

它提供了一种方法来访问集合对象中的各个元素,而又不需要暴露该对象的内部表示。

image.png

分析:

电视机:存储电视频道的集合->聚合类(Aggregate Classes)

电视机遥控器:操作电视频道->迭代器(Iterator)

访问一个聚合对象中的元素但又不需要暴露它的内部结构

迭代器就好比一个可以切换集合元素的一个控制器(拥有遍历数据的能力)

聚合对象的两个职责:

  • 存储数据,聚合对象的基本职责
  • 遍历数据,既是可变化的,又是可分离的

迭代器对象

将遍历数据的行为从聚合对象中分离出来,封装在迭代器对象中

由迭代器来提供遍历聚合对象内部数据的行为,简化聚合对象的设计,更符合单一职责原则

迭代器模式的定义

迭代器模式:提供一种方法顺序访问一个聚合对象中各个元素,且不用暴露该对象的内部表示。

又名游标(Cursor)模式

通过引入迭代器,客户端无须了解聚合对象的内部结构即可实现对聚合对象中成员的遍历,还可以根据需要很方便地增加新的遍历方式(我们看电视只用按遥控器,不用管怎么切换的)

迭代器模式的结构

image.png

迭代器模式包含以下4个角色:

  • Iterator(抽象迭代器)
  • ConcreteIterator(具体迭代器)
  • Aggregate(抽象聚合类)
  • ConcreteAggregate(具体聚合类)

实例学习

某软件公司为某商场开发了一套销售管理系统,在对该系统进行分析和设计时,开发人员发现经常需要对系统中的商品数据、客户数据等进行遍历。

为了复用这些遍历代码,开发人员设计了一个抽象的数据集合类AbstractObjectList,将存储商品和客户等数据的类作为其子类,AbstractObjectList类结构如下图所示:

image.png

image.png

实例说明

AbstractObjectList类的子类ProductListCustomerList分别用于存储商品数据和客户数据。

通过分析,发现AbstractObjectList类的职责非常重,它既负责存储和管理数据,又负责遍历数据,违背了单一职责原则,实现代码将非常复杂。

因此,开发人员决定使用迭代器模式对AbstractObjectList类进行重构,将负责遍历数据的方法提取出来,封装到专门的类中,实现数据存储和数据遍历分离,还可以给不同的具体数据集合类提供不同的遍历方式。

现给出使用迭代器模式重构后的解决方案。

抽象聚合类

这里是对需要遍历的数据的一个抽象,里面定义基本的增删查

//抽象聚合类
public abstract class AbstractObjectList {
   protected List<Object> objects = new ArrayList<Object>();

   public AbstractObjectList(List<Object> objects) {
      this.objects = objects;
   }
   
   public void addObject(Object obj) {
      this.objects.add(obj);
   }
   
   public void removeObject(Object obj) {
      this.objects.remove(obj);
   }
   
   public List<Object> getObjects() {
      return this.objects;
   }
   
   //声明创建迭代器对象的抽象工厂方法
   public abstract AbstractIterator createIterator();
}

具体聚合类

具体聚合类,需要定义创建迭代器的方法,比如Java中的list内置迭代器Iterator,放在里面的话,创建对象可以直接一并创建了。

//商品数据类:具体聚合类
public class ProductList extends AbstractObjectList {
   public ProductList(List<Object> products) {
      super(products);
   }
   
   //实现创建迭代器对象的具体工厂方法
   public AbstractIterator createIterator() {
      return new ProductIterator(this);
   }
} 

抽象迭代器

这里需要定义迭代器的基本方法,比如后移,前移等。

//抽象迭代器
public interface AbstractIterator {
   public void next(); //移至下一个元素
   public boolean isLast(); //判断是否为最后一个元素
   public void previous(); //移至上一个元素
   public boolean isFirst(); //判断是否为第一个元素
   public Object getNextItem(); //获取下一个元素
   public Object getPreviousItem(); //获取上一个元素
}

具体迭代器

这里我们写的是具体的商品迭代器

肯定要将需要迭代的数据的引用传入,这样才能读取到数据

//商品迭代器:具体迭代器
public class ProductIterator implements AbstractIterator {
   private List<Object> products;
   private int cursor1; //定义一个游标,用于记录正向遍历的位置
   private int cursor2; //定义一个游标,用于记录逆向遍历的位置
   
   public ProductIterator(ProductList list) {
      this.products = list.getObjects(); //获取集合对象
      cursor1 = 0; //设置正向遍历游标的初始值
      cursor2 = products.size() -1; //设置逆向遍历游标的初始值
   }
   
   public void next() {
      if(cursor1 < products.size()) {
         cursor1++;
      }
   }
   
   public boolean isLast() {
      return (cursor1 == products.size());
   }
   
   public void previous() {
      if (cursor2 > -1) {
         cursor2--;
      }
   }
   
   public boolean isFirst() {
      return (cursor2 == -1);
   }
   
   public Object getNextItem() {
      return products.get(cursor1);
   } 
      
   public Object getPreviousItem() {
      return products.get(cursor2);
   }  
}

客户端调用类

public class Client {
   public static void main(String args[]) {
      List<Object> products = new ArrayList<Object>();
      products.add("倚天剑");
      products.add("屠龙刀");
      products.add("断肠草");
      products.add("葵花宝典");
      products.add("四十二章经");
         
      AbstractObjectList list;
      AbstractIterator iterator;
      
      list = new ProductList(products); //创建聚合对象
      iterator = list.createIterator();  //创建迭代器对象
      
      System.out.println("正向遍历:");   
      while(!iterator.isLast()) {
         System.out.print(iterator.getNextItem() + ",");
         iterator.next();
      }
      System.out.println();
      System.out.println("-----------------------------");
      System.out.println("逆向遍历:");
      while(!iterator.isFirst()) {
         System.out.print(iterator.getPreviousItem() + ",");
         iterator.previous();
      }
   }
}

代码类

(1) AbstractObjectList:抽象聚合类
(2) ProductList:商品数据类,充当具体聚合类
(3) AbstractIterator:抽象迭代器
(4) ProductIterator:商品迭代器,充当具体迭代器
(5) Client:客户端测试类

输出及分析

正向遍历:
倚天剑,屠龙刀,断肠草,葵花宝典,四十二章经,

---------------------------

逆向遍历:
四十二章经,葵花宝典,断肠草,屠龙刀,倚天剑,

分析: 我在客户端新建产品聚合类, 然后调用创建迭代器方法,这样让迭代器创建时传入该聚合类,

这样迭代器就可以操作聚合类了。

  • 如果需要增加一个新的具体聚合类,只需增加一个新的聚合子类和一个新的具体迭代器类即可,原有类库代码无须修改,符合开闭原则
  • 如果需要更换一个迭代器,只需要增加一个新的具体迭代器类作为抽象迭代器类的子类,重新实现遍历方法即可,原有迭代器代码无须修改,也符合开闭原则
  • 如果要在迭代器中增加新的方法,则需要修改抽象迭代器的源代码,这将违背开闭原则

Java中的迭代器

image.png

  • Java的抽象迭代器 public interface Iterator { boolean hasNext(); E next(); void remove(); }

  • java的抽象聚合类 public interface Collection extends Iterable { …… boolean add(Object c); boolean addAll(Collection c); boolean remove(Object o); boolean removeAll(Collection c); boolean remainAll(Collection c); Iterator iterator(); …… }

  • 抽象聚合类比如AbstractList,里面内部类写了一个迭代器方法用于创建迭代器。 public Iterator iterator() { return new Itr(); }

  • 具体迭代器类写在聚合类的内部,当作内部类(可以去AbstractList里面去看一个叫Itr的内部类)。

模式优缺点

模式优点

  • 支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式
  • 简化了聚合类
  • 由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,符合开闭原则

模式缺点

  • 在增加新的聚合类时需要对应地增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性
  • 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展。在自定义迭代器时,创建一个考虑全面的抽象
  • 迭代器并不是一件很容易的事情