title: Iterator模式
tag: 笔记 设计模式
![image-20231017143616583](https://i-blog.csdnimg.cn/blog_migrate/307f223b30185a72d4f492ee5f6e66fc.png)
Iterator模式
在Java中我们可以用下面的for循环的方式遍历一个数组:
for (int i = 0; i < nums.length; i++) {
System.out.println(nums[i]);
}
这是我们非常熟悉的一种遍历元素的一种方式,其中i++的作用是时i的值循环之后自增,这样就能够将数组从头到尾遍历一遍。
我们可以将这里的i的作用抽象化,通用化后形成的模式在设计模式中称为**Iterator
模式**。Iterator
模式用于在数据集合中按照顺序遍历集合,也叫做迭代器模式。
示例程序
这段示例我们将书(Book)放置到书架(BookShelf)中,并可以将书的名字顺序输出。
示意图:
![image-20231017145524586](https://i-blog.csdnimg.cn/blog_migrate/62ce33054325b3ba986fcad4e6e59c94.png)
设计类图:
![image-20231017145649163](https://i-blog.csdnimg.cn/blog_migrate/4b8956dabc0493977c0b72fc5fd1d6c1.png)
该示例程序由以下部分组成:
Aggregate
:表示集合的接口Iterator
:遍历集合的接口Book
:书的类BookShelf
:书架的类BookShelfIterator
:遍历暑假的类
Aggregate接口
该接口在Java中用Iterable
接口表示,意为可迭代的,是在集合的接口Collection
中继承的接口。
实现该接口的类是集合类
该接口的方法很简单:
public interface Aggregate<T> {
Iterator<T> iterator();
}
作用是返回一个Iterator
迭代器,用于遍历集合。
Iterator接口
Iterator
接口用于遍历集合中的元素,其作用相当于循环遍历中的循环变量(i),该接口的定义方式很多,我们仅仅使用最简单的接口:
package Iterator;
public interface Iterator<T> {
boolean hasNext();
T next();
}
该接口包含了实现遍历的最基本的两个方法:
- hasNext:是否存在下一个元素,返回布尔值。相当于for循环中的循环执行条件(
i < nums.lenth
) - next:返回当前元素,并将迭代器移动到下个位置。相当于for循环中的
nums[i]
和i++
。
Book类
表示书的类,也就是集合中的元素。该类的作用仅仅是输出自己的名字。
public class Book {
private String name;
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
BookShelf类
表示书架的类,也就是需要将该类作为集合处理,需要实现Aggregate
接口,目前该类我们使用Book数组来实现。
public class BookShelf implements Aggregate<Book>{
Book[] books;
int last = 0;
public BookShelf(int shelfSize){
books = new Book[shelfSize];
}
public void appendBook(Book book){
books[last++] = book;
}
public int getLength(){
return last;
}
public Book getBookAt(int index) {
return books[index];
}
/**
* 获得该类的迭代器
* @return
*/
@Override
public Iterator<Book> iterator() {
return new BookShelfIterator(this);
}
}
该类实现了一些集合类的基础操作,并且实现了iterator
方法获取该类的迭代器,目前我们返回的BookShelfIterator
类还未实现,因此我们下一步需要实现该类的迭代器,也就是BookShelfIterator
。
BookShelfIterator
该类用于实现遍历BookShelf
,我们需要实现Iterator
接口的hasNext
和next
方法:
public class BookShelfIterator implements Iterator<Book>{
private int index;
private BookShelf bookShelf;
public BookShelfIterator(BookShelf bookShelf){
this.bookShelf = bookShelf;
index = 0;
}
@Override
public boolean hasNext() {
return index < bookShelf.getLength();
}
@Override
public Book next() {
Book book = bookShelf.getBookAt(index);
index++;
return book;
}
}
测试
至此,我们这个简单的迭代器示例程序就基本实现了,我们写一个main方法对该方法进行测试:
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf(5);
bookShelf.appendBook(new Book("罪与罚"));
bookShelf.appendBook(new Book("月亮与六便士"));
bookShelf.appendBook(new Book("罗生门"));
bookShelf.appendBook(new Book("局外人"));
Iterator<Book> iterator = bookShelf.iterator();
while(iterator.hasNext()){
System.out.println(iterator.next().getName());
}
}
这里我们实例化一个容量为5书架,并向其中添加了四本书。我们获取了该类的迭代器对象,并将书架中的书名输出:
罪与罚
月亮与六便士
罗生门
局外人
可以看到我们没有使用for循环而实现了书架的遍历。
Iterator模式中的角色
Iterator
(迭代器)
该角色负责定义按顺序逐个遍历元素的接口(API)。在示例程序中,由Iterator
接口扮演这个角色,它定义了hasNext
和next
两个方法。其中,hasNext
方法用于判断是否存在下一个元素,next
方法则用于获取该元素。
ConcreteIterator
(具体的迭代器)
该角色负责实现Iterator
角色所定义的接口(API)。在示例程序中,由BookShelfIterator
类扮演这个角色。该角色中包含了遍历集合所必需的信息。在示例程序中,BookShelf
类的实例保存在bookShelf
字段中,被指向的书的下标保存在index
字段中。
Aggregate
(集合)
该角色负责定义创建Iterator角色的接口(API)。这个接口(API)是一个方法,会创建出“按顺序访问保存在我内部元素的人”。在示例程序中,由Aggregate接口扮演这个角色,它里面定义了iterator方法。
ConcreteAggregate
(具体的集合)
该角色负责实现Aggregate角色所定义的接口(API)。它会创建出具体的Iterator
角色,即Concretelterator
角色。在示例程序中,由BookShelf
类扮演这个角色,它实现了iterator
方法。
扩展思路
无论实现如何变化,都可以使用Iterator
我们为何一定要引入复杂的Iterator
模式而不是简单的for循环遍历呢?
一个非常重要的理由就是:Iterator
可以将遍历和实现分离开:
while(iterator.hasNext()){
System.out.println(iterator.next().getName());
}
可以看到,这段遍历的代码仅仅使用了iterator
的两个方法而没有调用BookShelf
的方法。也就是这里的while循环并不依赖于BookShelf
的实现。
当我们将BookShelf
中存放book
的容器从数组更换为集合或者其它容器时,只要**Iterator
方法获取到的迭代器能够正常工作**,即使不对现有的循环进行改变,循环也同样能够正常工作。
这样就可以极大的方便重构。当我们对BookShelf
重构时,我们仅仅只需要保证它的迭代器能够正常工作,我们就无需对遍历BookShelf
的代码进行修改。
比如,我们可以将我们示例程序中保存book
的容器更换为ArrayList
:
public class BookShelf implements Aggregate<Book>{
ArrayList<Book> books;
int last = 0;
public BookShelf(int shelfSize){
books = new ArrayList(shelfSize);
}
public void appendBook(Book book){
books.add(book);
last++;
}
public int getLength(){
return last;
}
public Book getBookAt(int index) {
return books.get(index);
}
@Override
public Iterator<Book> iterator() {
return new BookShelfIterator(this);
}
}
我们还是运行原来的测试代码,输出结果相同。
从这个例子我们可以使用迭代器模式后重构会变得非常简单。
Aggregate和Iterator的对应
BookShelfIterator类知道BookShelf是如何实现的。也正是因为如此,我们才能调用用来获取下一本书的getBookAt方法。
也就是说,迭代器的实现依赖于集合类,当集合类的API发生改变时比如(getBookAt
),我们也需要修改迭代器的类,保证迭代器能够返回的正确值。
多个Iterator
将遍历功能置于Aggregate
角色之外是Iterator
模式的一个特征。根据这个特征,可以针对个ConcreteAggregate
角色编写多个Concretelterator
角色。
例如java的集合类库中ArrayList就实现了两个迭代器:
Itr
private class Itr implements Iterator<E> {
/**
* Index of element to be returned by subsequent call to next.
*/
int cursor = 0;
/**
* Index of element returned by most recent call to next or
* previous. Reset to -1 if this element is deleted by a call
* to remove.
*/
int lastRet = -1;
/**
* The modCount value that the iterator believes that the backing
* List should have. If this expectation is violated, the iterator
* has detected concurrent modification.
*/
int expectedModCount = modCount;
public boolean hasNext() {
return cursor != size();
}
public E next() {
checkForComodification();
try {
int i = cursor;
E next = get(i);
lastRet = i;
cursor = i + 1;
return next;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException(e);
}
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.remove(lastRet);
if (lastRet < cursor)
cursor--;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException e) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ListItr
private class ListItr extends Itr implements ListIterator<E> {
ListItr(int index) {
cursor = index;
}
public boolean hasPrevious() {
return cursor != 0;
}
public E previous() {
checkForComodification();
try {
int i = cursor - 1;
E previous = get(i);
lastRet = cursor = i;
return previous;
} catch (IndexOutOfBoundsException e) {
checkForComodification();
throw new NoSuchElementException(e);
}
}
public int nextIndex() {
return cursor;
}
public int previousIndex() {
return cursor-1;
}
public void set(E e) {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
AbstractList.this.set(lastRet, e);
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
public void add(E e) {
checkForComodification();
try {
int i = cursor;
AbstractList.this.add(i, e);
lastRet = -1;
cursor = i + 1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
}
ListItr
是Itr
的升级版,多了一些操作。
相关的设计模式
- Visitor模式:
Iterator
是从集合中一个一个取出元素进行遍历,但是并没有在Iterator
接口中声明对取出的元素进行何种处理。
Visitor模式则是在遍历元素集合的过程中,对元素进行相同的处理。在遍历集合的过程中对元素进行固定的处理是常有的需求。Visitor模式正是为了应对这种需求而出现的。在访问元素集合的过程中对元素进行相同的处理,这种模式就是Visitor
模式。
Factory Method
模式:
我们可能会使用Factory Method
来生成Iterator示例。
on ex) {
throw new ConcurrentModificationException();
}
}
}
`ListItr`是`Itr`的升级版,多了一些操作。
## 相关的设计模式
* Visitor模式:
`Iterator`是从集合中一个一个取出元素进行遍历,但是**并没有在`Iterator`接口中声明对取出的元素进行何种处理**。
Visitor模式则是在遍历元素集合的过程中,**对元素进行相同的处理**。在遍历集合的过程中对元素进行固定的处理是常有的需求。Visitor模式正是为了应对这种需求而出现的。在访问元素集合的过程中对元素进行相同的处理,这种模式就是`Visitor`模式。
* `Factory Method`模式:
我们可能会使用`Factory Method`来生成Iterator示例。