迭代器模式就是提供一种方法,顺序访问一个聚合对象中各个元素, 而又无须暴露该对象的内部表示。也就是说可以让我们以相同的方式,遍历不同的数据结构元素,这些数据结构包括;数组
、链表
、树
等,而用户在使用遍历的时候并不需要去关心每一种数据结构的遍历处理逻辑,从让使用变得统一易用。
不同种类的对象可能需要不同的遍历方式,所以我们对每一种类型的对象配一个迭代器,最后把多个迭代器合成一个进行调用。
一般,迭代器模式分为四个角色:
- 迭代器角色(Iterator):定义遍历元素所需要的方法,一般来说会有这么三个方法:取得下一个元素的方法next(),判断是否遍历结束的方法hasNext()),移出当前对象的方法remove(),
- 具体迭代器角色(Concrete Iterator):实现迭代器接口中定义的方法,完成集合的迭代。
- 容器角色(Aggregate): 一般是一个接口,提供一个iterator()方法,例如java中的Collection接口,List接口,Set接口等
- 具体容器角色(ConcreteAggregate):就是抽象容器的具体实现类,比如List接口的有序列表实现ArrayList,List接口的链表实现LinkList,Set接口的哈希列表的实现HashSet等
其实迭代器我们再java中也是熟面孔了,我们的arrayList中就有iterator:
定义迭代器:
public Iterator<E> iterator() {
return new Itr();
}
分层迭代,遍历arrayList直到arrayList的最后一个,返回对应的值:
private class Itr implements Iterator<E> {
/**
cursor:表示下一个元素的索引位置
lastRet:表示上一个元素的索引位置
expectModCount:预期被修改的次数
**/
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
//判断是否存在下一个
public boolean hasNext() {
//当cursor不等于size时,表示仍有索引元素
return cursor != size;
}
@SuppressWarnings("unchecked")
//返回下一个元素
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
//判断集合的修改次数是否合法。
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
//modCount用于记录集合被修改的次数
//每当集合内部结构发生变化(add,remove,set)时,modCount+1。
if (modCount != expectedModCount)
//expectedModCount记录当前集合修改的次数,初始化为集合的modCount值
throw new ConcurrentModificationException();
}
}
我们可以仿照他来写一个:
=========================================================================
我们选择两个菜单,一个食物菜单用ArrayList来存
一个饮料菜单用HashMap来存
并且通过迭代器遍历输出菜单。
=========================================================================
迭代器角色:
package IteratorPattern;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Iterator.java
* @Description 迭代器
* @createTime 2022年03月14日 14:52:00
*/
public interface Iterator<E> {
/**
* 是否存在下一个节点
* @return 存在true,不存在false
*/
boolean hasNext();
/**
* 下一个节点的值
* @return 下一个节点
*/
Object next();
}
可迭代接口:
package IteratorPattern;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Iterable.java
* @Description 可迭代接口
* @createTime 2022年03月14日 14:54:00
*/
public interface Iterable<E> {
/**
* 迭代器
* @return 迭代器
*/
Iterator <E> iterator();
}
容器接口:
package IteratorPattern;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Collection.java
* @Description 集合功能接口定义
* @createTime 2022年03月14日 15:02:00
*/
public interface Collection<E> extends Iterable<E> {
/**
* 增加节点
* @param e 节点
* @return 是否成功增加: true成功,false失败
*/
boolean add(E e);
}
定义菜单中的原素:
package IteratorPattern;
import java.math.BigDecimal;
import java.util.ArrayList;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Menu.java
* @Description 菜单元素
* @createTime 2022年03月14日 14:58:00
*/
public class MenuItem {
private String name;
private BigDecimal price;
public MenuItem(String name, BigDecimal price){
this.name=name;
this.price=price;
}
public void setPrice(BigDecimal price) {
this.price = price;
}
public BigDecimal getPrice() {
return price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
我们用Map存储数据,需要得到两个原素之间的联系用于遍历:
package IteratorPattern;
import java.util.Objects;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName MenuLink.java
* @Description 联系
* @createTime 2022年03月14日 15:58:00
*/
public class MenuLink {
private MenuItem firstMenu;
private MenuItem secondMenu;
public MenuLink(MenuItem menuItem1, MenuItem menuItem2) {
this.firstMenu = menuItem1;
this.secondMenu = menuItem2;
}
public MenuItem getFirstMenu() {
return firstMenu;
}
public MenuItem getSecondMenu() {
return secondMenu;
}
public void setFirstMenu(MenuItem firstMenu) {
this.firstMenu = firstMenu;
}
public void setSecondMenu(MenuItem secondMenu) {
this.secondMenu = secondMenu;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof MenuLink)) {
return false;
}
MenuLink menuLink = (MenuLink) o;
return Objects.equals(getFirstMenu(), menuLink.getFirstMenu()) && Objects.equals(getSecondMenu(), menuLink.getSecondMenu());
}
@Override
public int hashCode() {
return Objects.hash(getFirstMenu(), getSecondMenu());
}
}
我们通过实现容器接口,得到食物菜单和饮料菜单:
食物菜单(用ArrayList存):
package IteratorPattern;
import java.util.ArrayList;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName Menu.java
* @Description 食物菜单
* @createTime 2022年03月14日 15:00:00
*/
public class FoodMenu implements Collection<MenuItem>{
private ArrayList<MenuItem> menu;
public FoodMenu(ArrayList<MenuItem> menu) {
this.menu = menu;
}
@Override
public boolean add(MenuItem menuItem) {
return menu.add(menuItem);
}
@Override
public Iterator<MenuItem> iterator() {
return new Iterator<MenuItem>() {
private int location = 0;
@Override
public boolean hasNext() {
return location<menu.size();
}
@Override
public Object next() {
MenuItem menuItem = menu.get(location);
location++;
return menuItem;
}
};
}
}
饮料菜单(用MAP存):
package IteratorPattern;
import java.math.BigDecimal;
import java.util.*;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName DrinkMenu.java
* @Description 饮料菜单
* @createTime 2022年03月14日 15:37:00
*/
public class DrinkMenu implements Collection<MenuItem>{
private int index = 0;
private Map<Integer,MenuLink> menuLinkMap;
public DrinkMenu(Map<Integer,MenuLink> menuLinkMap){
this.menuLinkMap = menuLinkMap;
}
@Override
public boolean add(MenuItem menuItem) {
if (menuLinkMap.isEmpty()){
MenuLink menuLink = new MenuLink(menuItem,null);
index++;
return menuLinkMap.put(index,menuLink)!=null;
}else {
MenuLink menuLink = menuLinkMap.get(index);
menuLink.setSecondMenu(menuItem);
menuLinkMap.put(index,menuLink);
MenuLink newMenuLink = new MenuLink(menuItem,null);
index++;
return menuLinkMap.put(index,newMenuLink)!=null;
}
}
@Override
public Iterator<MenuItem> iterator() {
return new Iterator<MenuItem>() {
private int location = 0;
@Override
public boolean hasNext() {
return location<menuLinkMap.size();
}
@Override
public Object next() {
location++;
MenuLink menuLink = menuLinkMap.get(location);
return menuLink.getFirstMenu();
}
};
}
}
测试一下:
package IteratorPattern;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
/**
* @author Zeyu Wan
* @version 1.0.0
* @ClassName IteratorTest.java
* @Description 测试
* @createTime 2022年03月14日 15:19:00
*/
public class IteratorTest {
public static void main(String[] args) {
ArrayList<MenuItem> menuList = new ArrayList<>();
Collection<MenuItem> menu = new FoodMenu(menuList);
menu.add(new MenuItem("炸鸡", BigDecimal.valueOf(20)));
menu.add(new MenuItem("炸鸡翅", BigDecimal.valueOf(15)));
menu.add(new MenuItem("炸香菇", BigDecimal.valueOf(9)));
menu.add(new MenuItem("炸触手怪", BigDecimal.valueOf(12)));
menu.add(new MenuItem("炸奶油", BigDecimal.valueOf(4)));
Map<Integer,MenuLink> menuLinkMap = new HashMap<>();
Collection<MenuItem> menu1 = new DrinkMenu(menuLinkMap);
menu1.add(new MenuItem("矿泉水",BigDecimal.valueOf(1)));
menu1.add(new MenuItem("可乐",BigDecimal.valueOf(2)));
menu1.add(new MenuItem("鲜橙多",BigDecimal.valueOf(3)));
menu1.add(new MenuItem("自来水",BigDecimal.valueOf(0)));
System.out.println("==========食物菜单===========");
printList(menu);
System.out.println("==========饮料菜单===========");
printList(menu1);
}
/**
* 打印菜单方法
* @param collection 传入的菜单
*/
public static void printList(Collection<MenuItem> collection){
Iterator<MenuItem> iterator = collection.iterator();
while (iterator.hasNext()){
MenuItem item2 = (MenuItem)iterator.next();
System.out.println(item2.getName()+"========"+item2.getPrice());
}
}
}
可以看到,我们虽然通过不同的存储方式,但是都是通过迭代器进行迭代访问。
优点:
- 它支持以不同的方式遍历一个聚合对象。
- 迭代器简化了聚合类。
- 在同一个聚合上可以有多个遍历。
- 在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
缺点:
- 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。