Iterator Pattern
WHAT IS ITERATOR PATTERN?
Iterator pattern allows us to traverse elements of a collection without exposing its underlying implementation or representation. The logic of traversal is encapsulated within a separate iterator object. This comes in handy when we have complex data structures like trees or graphs, where iterating can be difficult.
It essentially addresses the issue of code duplication and low maintainability by encapsulating the traversal logic.
So the doctor used his staff as an iterator to see the patients. Everytime one appointment got over, he asked his staff to send the next patient. If there were no patients left, the staff would send no-one and everyone would go home. (There is actually another way to handle this. Can you think about it and leave a comment? Hint: JavaScript does it in this way.)
In other words, the logic of reviewing and sending the patients was encapsulated by the staff.
But How did it help the doctor? (with parrallels to software)
Where did they mess up?
Since the staff had the responsibility to send the patients, they did a terrible job when they asked everyone to turn up at the same time(11 AM). They could have let the patients know an hour before their turn and asked them to come over. They (as a business) failed to utilize the full potential of being an iterator. As good as the doctor and his name was in the market, we left him for another because of this sole reason.
Anyway, enough of story!
If you have used python, you must be familiar with the range function
for number in range(1, 10, 2):
print(number)
# output:
# 1
# 3
# 5
# 7
# 9
Lets try to implement a similar range function in Java using the Iterator Pattern.
My goal is to create something close to the below code so that it is closer to Python;
领英推荐
public class Main {
public static void main(String[] args) {
for (int num : range(1, 10, 2)) {
System.out.println(num);
}
}
}
Iterator and Iterable Interfaces in Java
There will be a separate detailed article on these two. Till then lets define them for the sake of completion:
Iterable Interface: Part of java.lang package. Provides a way to create an Iterator for a collection, allowing it to be used with the enhanced for-loop. We need to implement iterator() method as part of this contract.
Iterator Interface: Part of java.utilpackage Provides methods to traverse a collection, retrieve elements, and optionally remove elements during iteration. We need to implement hasNext() and next() methods as a part of this contract.
The Iterator Interface gives us the essence on how the Iterator Pattern is actually implemented. The next() should return the next element after following the rules of iteration. All the logic for iteration goes in this function.
It is defined something like this
package java.util;
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());
}
}
Lets implement the Iterable and Iterator interfaces in the Range Class. We’ll define the start, end and the step variables.
public class Range implements Iterable<Integer>, Iterator<Integer> {
private final int start;
private final int end;
private final int step;
private int current;
public Range(int start, int end, int step) {
if (step == 0) {
throw new IllegalArgumentException("Step cannot be zero");
}
if (end > 0 && step < 0){
throw new IllegalArgumentException("Step and End are not aligned");
}
this.start = start;
this.end = end;
this.step = step;
this.current = start;
}
public Range(int end){
this(0, end, end > 0 ? 1 : -1);
}
public Range(int end, int step){
this(0, end, step);
}
@Override
public boolean hasNext() {
if (step > 0) {
return current < end;
} else {
return current > end;
}
}
@Override
public Integer next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
int value = current;
current += step;
return value;
}
@Override
public Iterator<Integer> iterator() {
// Reset the current position for new iterations
current = start;
return this;
}
}
Since we want to use some sort of a function, we can either encapsulate the creation of the object inside a utility function or define static functions somewhere in any other class.
public class RangeCreator{
public static Iterable<Integer> range(int end) {
return new Range(0, end, 1);
}
public static Iterable<Integer> range(int start, int end) {
return new Range(start, end, 1);
}
public static Iterable<Integer> range(int start, int end, int step) {
return new Range(start, end, step);
}
}
USAGE:
public class Main {
public static void main(String[] args) {
// Test the range method with different parameters
System.out.println("range(10):");
for (int num : RangeCreator.range(10)) {
System.out.println(num);
}
System.out.println("range(5, 15):");
for (int num : RangeCreator.range(5, 15)) {
System.out.println(num);
}
System.out.println("range(0, 10, 2):");
for (int num : RangeCreator.range(0, 10, 2)) {
System.out.println(num);
}
System.out.println("range(10, 0, -2):");
for (int num : RangeCreator.range(10, 0, -2)) {
System.out.println(num);
}
}
}
NOTE:
Thank you