The beauty of design patterns by Wang Zheng Study notes
Principle and implementation of iterator pattern
- Iterator Design Pattern, also known as Cursor Design Pattern.
- Iterators are used to traverse containers, so a complete iterator pattern generally involves containers
And container iterators.
Implement an iterator
- Suppose that linear data structures include arrays and linked lists, corresponding to ArrayList and LinkedList.
- We abstract the public interface from the two classes and define it as the List interface to facilitate developers to program based on the interface rather than the implementation. The code can be flexibly switched between the two data storage structures.
- We design and implement corresponding iterators for ArrayList and LinkedList linear containers. We define an Iterator interface Iterator and specific Iterator implementation classes arrayitrator and ListIterator for the two containers.
// Interface definition mode I public interface Iterator<E> { boolean hasNext(); void next(); E currentItem(); } // Interface definition mode II public interface Iterator<E> { boolean hasNext(); E next(); }
- The Iterator interface can be defined in two ways:
- In the first definition, the next() function is used to move the cursor back one bit, and the currentItem() function is used to return the element pointed to by the current cursor.
- In the second definition, the two operations of returning the current element and moving back one bit should be completed in the same function next().
- The first definition method is more flexible. For example, we can call currentItem() multiple times to query the current element without moving the cursor. Therefore, in the next implementation, we choose the first interface definition method.
public class ArrayIterator<E> implements Iterator<E> { private int cursor; private ArrayList<E> arrayList; public ArrayIterator(ArrayList<E> arrayList) { this.cursor = 0; this.arrayList = arrayList; } @Override public boolean hasNext() { return cursor != arrayList.size();//Note that when cursor points to the last element, hasNext() still returns true. } @Override public void next() { cursor++; } @Override public E currentItem() { if (cursor >= arrayList.size()) { throw new NoSuchElementException(); } return arrayList.get(cursor); } } public class Demo { public static void main(String[] args) { ArrayList<String> names = new ArrayList<>(); names.add("xzg"); names.add("wang"); names.add("zheng"); Iterator<String> iterator = new ArrayIterator(names); while (iterator.hasNext()) { System.out.println(iterator.currentItem()); iterator.next(); } } }
- In the above code implementation, we need to pass the container object to be traversed to the iterator class through the constructor.
- In fact, in order to encapsulate the creation details of the iterator, we can define an iterator() method in the container to create the corresponding iterator. In order to implement interface based rather than programming, we also need to define this method in the List interface.
public interface List<E> { Iterator iterator(); //... Omit other interface functions } public class ArrayList<E> implements List<E> { //... public Iterator iterator() { return new ArrayIterator(this); } //... Omit other codes } public class Demo { public static void main(String[] args) { List<String> names = new ArrayList<>(); names.add("xzg"); names.add("wang"); names.add("zheng"); Iterator<String> iterator = names.iterator(); while (iterator.hasNext()) { System.out.println(iterator.currentItem()); iterator.next(); } } }
- Summary:
- The iterator needs to define three basic methods: hasNext(), currentItem(), and next().
- The container object to be traversed is passed to the iterator class through dependency injection.
- The container uses the iterator() method to create an iterator.
Advantages of iterator pattern
- Generally speaking, there are three ways to traverse set data: for loop, foreach loop, iterator iterator.
List<String> names = new ArrayList<>(); names.add("xzg"); names.add("wang"); names.add("zheng"); // The first traversal method: for loop for (int i = 0; i < names.size(); i++) { System.out.print(names.get(i) + ","); } // The second traversal method: foreach loop for (String name : names) { System.out.print(name + ",") } // The third traversal method: iterator traversal Iterator<String> iterator = names.iterator(); while (iterator.hasNext()) { System.out.print(iterator.next() + ",");//The iterator interface in Java is the second definition method. next() moves the cursor and returns data }
- foreach loop is just a syntax sugar, and the underlying implementation is based on iterators.
- The for loop traversal looks more concise than the iterator traversal.
- So why do we use iterators to traverse containers? There are three reasons:
- First, for data structures such as arrays and linked lists, the traversal method is relatively simple. It is sufficient to directly use the for loop to traverse. However, for complex data structures (such as trees and graphs), there are various complex traversal methods. The way to deal with complexity is to split. We can split the traversal operation into iterator classes. For example, we can define DFSIterator and BFSIterator iterator classes to realize depth first traversal and breadth first traversal respectively.
- Secondly, the information such as the current position pointed to by the cursor is stored in the iterator class, and each iterator has its own cursor information. In this way, we can create multiple different iterators and traverse the same container at the same time without affecting each other.
- Finally, both container and iterator provide abstract interfaces to facilitate our programming based on interfaces rather than concrete implementations. When a new traversal algorithm needs to be switched, for example, from traversing the linked list from front to back to back to front, the client code only needs to switch the iterator class from LinkedIterator to reversedlinkedeiterator, and other codes do not need to be modified. In addition, to add a new traversal algorithm, we only need to extend the new iterator class, which is more in line with the opening and closing principle.