这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
在日常工作中, 我们常用的设计模式不是单例模式, 不是策略模式, 不是工厂模式, 而是我们今天要研究的迭代器模式. 我们在编程的时候, 经常需要遍历一个集合里的各个对象, 通常我们是怎么做的呢? 将创建和遍历耦合在一块, 如下所示:
List<Book> books = new ArrayList<>();
for (Book book: books) {
System.out.println("书名: " + book.getName() );
}
来看看这段代码的问题.
- 如果有多处List, 那就需要多次使用for循环遍历
- 如果代码需要扩展或者更换迭代方式呢? 需要多次修改for循环里面的内容, 违背了"开闭原则"
解决这个问题的方法有很多种, 其中一种就是我们今天要研究的迭代器模式. 将对象的创建和遍历相分离. 对客户隐藏了内部细节, 功能单一, 所以符合“单一职责原则”, 提供接口, 可以自由扩展聚合类和迭代器类, 符合“开闭原则”. 下面就来详细研究 迭代器模式。
一、什么是迭代器模式
迭代器(Iterator)模式:提供一个对象来顺序访问聚合对象中的一系列数据,而不暴露聚合对象的内部表示。迭代器模式是一种对象行为型模式
我们可以在迭代器中自主定义需要的方法,如上一个, 下一个,第一个,最后一个,是否还有等方法。
实际上在Java里面, 已经为我们实现好了,那就是Java的Iterator类。很多编程语言也都有自己的迭代器类,所以在我们实现迭代器模式的时候创建一个实现迭代器Iterator接口的类即可。(也可以自定义接口类)
二、迭代器模式的结构
先来看迭代器的UML图:
从图中, 我们可以看出迭代器模式一共有5个重要的组成部分。
- 抽象迭代器接口(Iterator):在抽象迭代器中有两个非常总要的抽象方法,hasNext()和next(). 前者是用来判断是否还有下一个元素,后者是用来获取当前指针指向的元素, 同时让指针指向下一个元素。在java中, 已经有定义好的Iterator接口,我们可以直接使用
- 具体迭代器实现类(ConcreteIterator)实现了抽象迭代器接口
- 抽象聚合类接口(Aggregate):定义了抽象的聚合类,并且定义了一个创建迭代器对象的接口。还可以定义一些其他的方法。
- 具体聚合实现类(ConcreteAggregate):实现抽象聚合类,返回一个具体迭代器对象。实现了创建迭代器对象的接口。
- 客户端(client):主要是main方法模拟客户端调用。
下面来看看基础结构用法:
第一步: 定义抽象迭代器接口
package com.lxl.www.designPatterns.iteratePattern.book;
/**
* 迭代器接口
* @param <T>
*/
public interface Iterator<T> {
boolean hasNext();
T next();
}
这个接口可以自己定义, 也可以直接使用jdk提供的Iterator接口。
第二步: 具体迭代器实现类
package com.lxl.www.designPatterns.iteratePattern.book;
import java.util.List;
/**
* 具体迭代器
*/
public class ConcreteIterator implements Iterator{
/** 聚合抽象类 */
Aggregate aggregate;
/** 下标 */
int index;
public ConcreteIterator(Aggregate aggregate) {
this.aggregate = aggregate;
index = 0;
}
@Override
public boolean hasNext() {
if (index < aggregate.getLength()) {
return true;
}
return false;
}
@Override
public Object next() {
Object obj = aggregate.getByIndex(index);
index ++;
return obj;
}
}
在具体迭代器实现类中, 定义了一个聚合成员变量, 并实现了hasNext()和next()方法。
第三步: 抽象聚合类接口
package com.lxl.www.designPatterns.iteratePattern.book;
/**
* 抽象聚合类
* Aggregate 集合, 聚合的意思
*/
public interface Aggregate {
public int getLength();
public Object getByIndex(int index);
/**
* 循环遍历
* @return
*/
Iterator iterator();
}
抽象聚合接口中获取了Iterator迭代器对象。并定义了getLength()方法和getByIndex(int index)方法。
第四步: 具体聚合实现类
package com.lxl.www.designPatterns.iteratePattern.book;
import java.util.ArrayList;
import java.util.List;
/**
* 具体的聚合实现类
*/
public class ConcreteAggregate implements Aggregate {
/** 对象集合 */
List<Object> objs = new ArrayList<>();
public void add(Object obj){
this.objs.add(obj);
}
@Override
public int getLength() {
return objs.size();
}
@Override
public Object getByIndex(int index) {
return objs.get(index);
}
@Override
public Iterator iterator() {
return new ConcreteIterator(this);
}
}
具体的聚合实现类实现了Iterator接口。
第五步: client客户端调用
public class Client {
public static void main(String[] args) {
ConcreteAggregate bookShelf = new ConcreteAggregate();
bookShelf.add(new Book("java语言"));
bookShelf.add(new Book("c++语言"));
bookShelf.add(new Book("php语言"));
bookShelf.add(new Book("前端语言"));
Iterator iterator = bookShelf.iterator();
while (iterator.hasNext()) {
System.out.println(((Book)iterator.next()).getName());
}
}
}
客户端调用,主导代码是获取Iterator对象并判断是否还有元素, 有的话取出来。
三、迭代器模式的实现案例
1. 案例1
首都图书馆有很多书架, 架子上有很多书。 我们想要将书按照在书架上摆放的顺序录入书名。书架有小说类书架,科幻类书架。下面我们来实现这个需求。
第一步: 定义一个书籍实体
package com.lxl.www.designPatterns.iteratePattern.book;
/**
*
*/
public class Book {
/**
* 书名
*/
public String name;
public Book(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
第二步: 定义抽象迭代器类 这里使用的是jdk自带的Iterator
第三步: 定义具体的迭代器类
package com.lxl.www.designPatterns.iteratePattern.BookShelf;
import java.util.Iterator;
/**
* 图书迭代器
*/
public class BookIterator implements Iterator<Book> {
IBookShelf<Book> bookShelf;
int index;
public BookIterator(IBookShelf bookShelf) {
this.bookShelf = bookShelf;
index = 0;
}
@Override
public boolean hasNext() {
if (index < bookShelf.getLength()) {
return true;
}
return false;
}
@Override
public Book next() {
Book book = bookShelf.getByIndex(index);
index ++;
return book;
}
}
第四步: 定义抽象聚合接口
package com.lxl.www.designPatterns.iteratePattern.BookShelf;
import java.util.Iterator;
/**
* 书架抽象类
*/
public interface IBookShelf<T> {
T getByIndex(int index);
int getLength();
/**
* 返回书籍列表的集合
* @return
*/
public Iterator<T> iterator();
void add(T t);
}
第五步: 定义具体的聚合实现类
- 定义小说书架
package com.lxl.www.designPatterns.iteratePattern.BookShelf;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 小说类书架
*/
public class NovelBookShelf implements IBookShelf<Book> {
List<Book> novels = new ArrayList<>();
@Override
public void add(Book novle) {
novels.add(novle);
}
@Override
public Book getByIndex(int index) {
if (index < novels.size()) {
return novels.get(index);
}
return null;
}
@Override
public int getLength() {
return novels.size();
}
@Override
public Iterator iterator() {
return new BookIterator(this);
}
}
- 定义科幻类书架
package com.lxl.www.designPatterns.iteratePattern.BookShelf;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 科幻类书架
*/
public class ScienceFictionBookShelf implements IBookShelf<Book> {
List<Book> scienceFiction = new ArrayList<>();
@Override
public void add(Book novle) {
scienceFiction.add(novle);
}
@Override
public Book getByIndex(int index) {
if (index < scienceFiction.size()) {
return scienceFiction.get(index);
}
return null;
}
@Override
public int getLength() {
return scienceFiction.size();
}
@Override
public Iterator iterator() {
return new BookIterator(this);
}
}
第六步: 客户端
package com.lxl.www.designPatterns.iteratePattern.BookShelf;
import java.util.Iterator;
public class Client {
public static void main(String[] args) {
System.out.println("=========小说类书架=========");
IBookShelf<Book> novels = new NovelBookShelf();
novels.add(new Book("老人与海"));
novels.add(new Book("哈利波特"));
BookIterator iterator = (BookIterator)novels.iterator();
while (iterator.hasNext()){
Book book = iterator.next();
System.out.println(book.getName());
}
System.out.println("=========科幻类书架=========");
IBookShelf scienceFiction = new NovelBookShelf();
scienceFiction.add(new Book("三体I"));
scienceFiction.add(new Book("三体II"));
scienceFiction.add(new Book("三体III"));
BookIterator scienceFictionIterator = (BookIterator)novels.iterator();
while (scienceFictionIterator.hasNext()){
Book book = scienceFictionIterator.next();
System.out.println(book.getName());
}
}
}
第七步: 运行结果
=========小说类书架=========
老人与海
哈利波特
=========科幻类书架=========
老人与海
哈利波特
小说和科幻类书架使用的是同一类迭代器, 所以只需要定义一种迭代器即可.
2. 案例2
在java中, 很多集合都是使用的迭代器模式. 例如: List集合, Set集合. 接下来我们来看看如何作为客户端调用List和Set.
public class Client {
public static void main(String[] args) {
// set迭代器
Set<String> sets = new HashSet<>();
java.util.Iterator<String> iterator1 = sets.iterator();
while (iterator1.hasNext()) {
Object next = iterator.next();
}
// list迭代器
List<String> lists = new ArrayList<>();
java.util.Iterator<String> iterator2 = lists.iterator();
while (iterator2.hasNext()) {
System.out.println(iterator2.next());
}
// map迭代器
Map<String, Object> maps = new HashMap<>();
Set<String> strings = maps.keySet();
java.util.Iterator<String> iterator3 = strings.iterator();
while (iterator3.hasNext()) {
System.out.println(iterator3.next());
}
}
}
下面就以ArrayList为例来看看ArrayList是如何实现迭代器模式的. 注: ArrayList中还有很多其他的逻辑, 我们暂且忽略,主要看迭代器模式的部分.
- 在jdk中定义了Iterator接口.
package java.util;
import java.util.function.Consumer;
/**
* An iterator over a collection. {@code Iterator} takes the place of
* {@link Enumeration} in the Java Collections Framework. Iterators
* differ from enumerations in two ways:
*
* <ul>
* <li> Iterators allow the caller to remove elements from the
* underlying collection during the iteration with well-defined
* semantics.
* <li> Method names have been improved.
* </ul>
*
* <p>This interface is a member of the
* <a href="{@docRoot}/../technotes/guides/collections/index.html">
* Java Collections Framework</a>.
*
* @param <E> the type of elements returned by this iterator
*
* @author Josh Bloch
* @see Collection
* @see ListIterator
* @see Iterable
* @since 1.2
*/
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove() {
throw new UnsupportedOperationException("remove");
}
default void forEachRemaining(Consumer<? super E> action) {
Objects.requireNonNull(action);
while (hasNext())
action.accept(next());
}
}
这就是我们第一步的定义抽象迭代器接口 2. 在AbstractList中定义了抽象迭代器的实现类--具体的迭代器
在jdk中有这样一个Iterable接口, ArrayList最终实现了这个接口. 在接口中定义了一个抽象方法 Iterator iterator(); 然后在抽象类AbstractList中重写了Iterator iterator();这个方法, 如下图所示:
这个觉得迭代器返回的是Ite对象. 这个对象就是具体迭代器对象. Ite是一个子类, 来看看具体的代码实现
private class Itr implements Iterator<E> {
int cursor = 0;
int lastRet = -1;
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();
}
}
public void remove() {
....
}
final void checkForComodification() {
....
}
}
这是一个子类, 里面重写了hasNext()方法和next()方法, 并定义了一个指针, 因为是子类方法,可直接使用父类集合, 所以这里不需要在定义操作的集合类.
第三步: 定义抽象聚合类接口. AbstractList抽象类继承自List接口, 在List中定义了iterator()抽象方法
public interface List<E> extends Collection<E> {
int size();
boolean isEmpty();
boolean contains(Object o);
Iterator<E> iterator();
......
}
第四步: 定义聚合实现类 在AbstractList中, 实现了List接口, 并重写了iterator()方法. 这就是聚合的实现类了.
我们说研究ArrayList, 但好像一直围绕AbstractList研究, 其实ArrayList继承了AbstractList并重写了iterator()方法. 而且重新定义了迭代器实现类Ite. 我们来看看代码实现
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
......
public Iterator<E> iterator() {
return new Itr();
}
/**
* An optimized version of AbstractList.Itr
*/
private class Itr implements Iterator<E> {
int cursor; // index of next element to return
int lastRet = -1; // index of last element returned; -1 if no such
int expectedModCount = modCount;
Itr() {}
public boolean hasNext() {
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() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
......
}
以上就是ArrayList中迭代器设计模式的使用, Set也是如此.
四、迭代器模式的使用场景
1、访问一个聚合对象的内容而无须暴露它的内部表示。 2、需要为聚合对象提供多种遍历方式。 3、为遍历不同的聚合结构提供一个统一的接口。
五、迭代器模式的优缺点
优点
1、分离了对象的创建过程和遍历过程 2、支持以不同的方式遍历一个聚合对象。 3、迭代器简化了聚合类。 4、在迭代器模式中,增加新的聚合类和迭代器类都很方便,无须修改原有代码。
缺点
由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
六、迭代器模式对六大原则的应用
- 单一职责原则: 迭代器模式分离了对象的创建和迭代, 各司其职, 符合单一职责原则
- 里式替换原则: 父类出现的地方都可以使用子类替换. 这里接口方法定义的都是抽象的, 符合.
- 接口隔离原则: 接口尽量小,不要出现胖接口, 只提供访问者需要的方法. 符合
- 依赖倒置原则: 面向抽象编程, 而不是面向具体, 符合
- 迪米特法则: 最少知识原则,一个对象应当对其他对象尽可能少的了解。符合
- 开闭原则: 对扩展开放, 对修改关闭: 符合