其他分享
首页 > 其他分享> > 迭代器模式

迭代器模式

作者:互联网

目录

迭代器模式

案例

最近张三在书店上班,老板有着两家书店。一天老板让他把书店 A 和书店 B 中存放的书籍罗列出来。他来到两个书店发现两个书店存放书籍的格式是不一样的,其中书店 A 内部采用数组的形式,书店 B 采用链表的形式。现在他需要根据不同的格式进行遍历罗列,程序类似下面这样:

1.首先定义书籍实体类

/**
 * 书籍类
 */
public class Book {
    private String bookName;

    public Book(String bookName) {
        this.bookName = bookName;
    }

    @Override
    public String toString() {
        return "Book{" +
                "bookName='" + bookName + '\'' +
                '}';
    }
}

2.书店 A:

/**
 * A 书店内部使用数组存储书籍
 */
public class BookStoreA {
    private Book[] books;

    private int index;

    public BookStoreA() {
        books = new Book[10];
    }

    // 添加书籍
    public void addBook(Book book) {
        if (index == books.length) {
            throw new IndexOutOfBoundsException("容量不足");
        }
        books[index] = book;
        index++;
    }

    public Book[] getBooks() {
        return books;
    }

    public int getIndex() {
        return index;
    }
}

3.书店 B:

/**
 * B 书店内部使用链表存储书籍
 */
public class BookStoreB {
    // 记录第一个节点
    private Node<Book> first;
    // 记录最后一个节点
    private Node<Book> last;

    public void addBook(Book book) {
        Node<Book> node = new Node<>(book, null);
        if (first == null) {
            first = last = node;
        } else {
            last.next = node;
            last = node;
        }
    }

    /**
     * 实际存放数据及结构的内部类
     * @param <Book>
     */
    static class Node<Book> {
        // 存放数据
        Book book;
        // 存放/指向下一个节点
        Node<Book> next;

        Node(Book book, Node<Book> next) {
            this.book = book;
            this.next = next;
        }
    }

    public Node<Book> getFirst() {
        return first;
    }
}

4.罗列书籍:

public class Main {
    public static void main(String[] args) {
        BookStoreA bookStoreA = new BookStoreA();
        bookStoreA.addBook(new Book("A Book"));
        bookStoreA.addBook(new Book("B Book"));
        bookStoreA.addBook(new Book("C Book"));
        bookStoreA.addBook(new Book("D Book"));
        // 遍历书籍
        Book[] books = bookStoreA.getBooks();
        for (int i = 0; i < bookStoreA.getIndex(); i++) {
            System.out.println(books[i]);
        }

        System.out.println("--------------------------");

        BookStoreB bookStoreB = new BookStoreB();
        bookStoreB.addBook(new Book("A Book"));
        bookStoreB.addBook(new Book("B Book"));
        bookStoreB.addBook(new Book("C Book"));
        bookStoreB.addBook(new Book("D Book"));
        // 遍历书籍
        BookStoreB.Node<Book> first = bookStoreB.getFirst();
        while (first != null) {
            System.out.println(first.book);
            first = first.next;
        }
    }
}

5.罗列效果:

Book{bookName='A Book'}
Book{bookName='B Book'}
Book{bookName='C Book'}
Book{bookName='D Book'}
--------------------------
Book{bookName='A Book'}
Book{bookName='B Book'}
Book{bookName='C Book'}
Book{bookName='D Book'}

可以看到虽然两个书店存放书籍的数据格式不同,张三还是能够对不同的数据格式进行遍历。但是,如果还有一家店存放书籍的格式变化了,或新增了一家书店同时存放书籍的方式也同样变化了,那么张三就需要针对变化后的数据格式重新变换其他遍历方式。这一问题在设计模式中对应的就是迭代器模式,它解决了对不同数据结构遍历方式的问题。

模式介绍

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

角色构成

UML类图

iterator

在迭代器模式中,引入了迭代器类,它定义了对聚合类进行访问的接口,使得对每一种聚合类都可以通过的具体的迭代器进行遍历。下面就对上面的案例进行改造。

代码改造

1.首先是书籍类,与上面代码相同。

2.抽象聚合类:

/**
 * 抽象聚合类角色
 */
public interface Aggregate {
    Iterator createIterator();
}

3.抽象迭代器:

/**
 * 抽象迭代器角色
 */
public interface Iterator {
    // 判断是否存在下一个元素
    boolean hasNext();
    // 获取下一个元素
    Object next();
}

4.具体聚合类,书店 A:

/**
 * A 书店内部使用数组存储书籍
 */
public class BookStoreA implements Aggregate {
    // ...
	// 其余代码与案例相同
    @Override
    public Iterator createIterator() {
        return new BookStoreAIterator(this);
    }
}

具体聚合类,书店 B:

/**
 * B 书店内部使用链表存储书籍
 */
public class BookStoreB implements Aggregate {
    // ...
	// 其余代码与案例相同
    @Override
    public Iterator createIterator() {
        return new BookStoreBIterator(this);
    }

}

5.具体迭代器:书店 A 迭代器:

/**
 * 具体迭代器,针对数组形式重写 hasNext()、next() 等方法
 */
public class BookStoreAIterator implements Iterator {
    private BookStoreA bookStoreA;
    private int index;

    public BookStoreAIterator(BookStoreA bookStoreA) {
        this.bookStoreA = bookStoreA;
    }

    @Override
    public boolean hasNext() {
        return this.index < bookStoreA.getIndex();
    }

    @Override
    public Book next() {
        return bookStoreA.getBooks()[this.index++];
    }
}

书店 B 迭代器:

/**
 * 具体迭代器,针对链表形式重写 hasNext()、next() 等方法
 */
public class BookStoreBIterator implements Iterator {
    private BookStoreB bookStoreB;
    private BookStoreB.Node<Book> node;

    public BookStoreBIterator(BookStoreB bookStoreB) {
        this.bookStoreB = bookStoreB;
        this.node = new BookStoreB.Node<>(null, bookStoreB.getFirst());
    }

    @Override
    public boolean hasNext() {
        boolean hasNext = this.node.next != null;
        this.node = this.node.next;
        return hasNext;
    }

    @Override
    public Book next() {
        return node.book;
    }
}

6.测试类:

public class Main {
    public static void main(String[] args) {
        BookStoreA bookStoreA = new BookStoreA();
        bookStoreA.addBook(new Book("A Book"));
        bookStoreA.addBook(new Book("B Book"));
        bookStoreA.addBook(new Book("C Book"));
        bookStoreA.addBook(new Book("D Book"));

        // 使用迭代器进行统一遍历
        Iterator bookStoreAIterator = bookStoreA.createIterator();
        while (bookStoreAIterator.hasNext()) {
            System.out.println(bookStoreAIterator.next());
        }
        System.out.println("---------------------------");
        BookStoreB bookStoreB = new BookStoreB();
        bookStoreB.addBook(new Book("A Book"));
        bookStoreB.addBook(new Book("B Book"));
        bookStoreB.addBook(new Book("C Book"));
        bookStoreB.addBook(new Book("D Book"));

        // 使用迭代器进行统一遍历
        Iterator bookStoreBIterator = bookStoreB.createIterator();
        while (bookStoreBIterator.hasNext()) {
            System.out.println(bookStoreBIterator.next());
        }
    }
}

7.测试结果:

Book{bookName='A Book'}
Book{bookName='B Book'}
Book{bookName='C Book'}
Book{bookName='D Book'}
---------------------------
Book{bookName='A Book'}
Book{bookName='B Book'}
Book{bookName='C Book'}
Book{bookName='D Book'}

通过引入迭代器接口,然后针对不同的数据结构存储方式的书店提供不同的具体迭代器类,就可以通过统一的访问方式对其进行遍历。如果新增一种其他形式的存储方式,只用新增一个具体的迭代器类,而使用方不需要更改访问方式,同样可以对其进行访问,符合“开闭原则”。

模式应用

迭代器模式 Java 中最常见的应用就是集合框架中,为了让开发人员更加方便地遍历集合对象,实现了Collection(接口中定义了Iterator<E> iterator()方法,待子类具体实现)接口的类,都提供了迭代器来统一的访问集合内部的元素,比如ArrayList实现类在遍历元素时可以这样遍历:

public class Main {
    public static void main(String[] args) {
        List<Book> bookList = new ArrayList<>();
        bookList.add(new Book("A Book"));
        bookList.add(new Book("B Book"));
        bookList.add(new Book("C Book"));
        bookList.add(new Book("D Book"));

        // 迭代器遍历
        Iterator<Book> bookIterator = bookList.iterator();
        while (bookIterator.hasNext()) {
            System.out.println(bookIterator.next());
        }
    }
}

可以看到这里遍历集合使用的是迭代器Iterator对集合元素进行访问的,而迭代器是从它自身提供的方法iterator获取的。itertor():

public Iterator<E> iterator() {
    return new Itr();
}

这个方法返回迭代器接口的具体实现类实例,首先是Iterator接口:

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());
    }
}

可以看到它定义了访问元素的接口方法,然后是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() {
        // ...
    }

    public void remove() {
        // ...
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
        // ...
    }
    // ...
}

Itr它实现了Iterator接口,实现了访问ArrayList内部数据的方法。与案例改造代码中不同的地方就是,它是ArrayList中私有的内部类,而改造代码时定义的是一个公共的类。JDK 这样设计的想法我觉得是Itr本来就是用于访问ArrayList的内部元素的,把它设计为内部类能更好的访问外部内的数据,同时设置为私有的使得外部访问不到Itr类。下面是这几个类之间的类图关系:

iterator-jdk-collection

总结

1.主要优点

2.主要缺点

3.适用场景

参考资料

本篇文章github代码地址:https://github.com/Phoegel/design-pattern/tree/main/iterator
转载请说明出处,本篇博客地址:https://www.cnblogs.com/phoegel/p/14151798.html

标签:遍历,迭代,bookName,模式,Book,new,public
来源: https://www.cnblogs.com/phoegel/p/14151798.html