Intent
Provides a way to access the elements of an aggregate object sequentially without expositing its underlying representation.
An iterator design pattern is also known as Cursor.
This is the type of behavioral design pattern where it helps traverse a sequence of elements without exposing its underlying representation.
Applicability/problem it solves?
- To access an aggregate object's contents without exposing its internal representation.
Means just pass the object to pull next instance instead passing entire collection. - To reduce duplication of the traversal code across your app use this pattern.
- use this when you have complex data structure and you do not wish to pass complexity to client instead you want them only ask what is next.
Design Problem
Simple Problem
Consider you have two apparel stores MensApparelStore and WomensApparelStore both decided to do merge their business and start a new store name as MensAndWomensApparelStore.
Both owners finds this as a good idea but problem was with their catalog structure. Both of them had a method which was returning a catalog but
MensApparelStore has ArrayList getCatalog() and WomensApparelStore has
Array getCatalog() .
Both of them returns a different return type, but they wish to merge a their catalog.
Solution
Encapsulate what varies it's one of the OOP principle, here iteration is what varies we can encapsulate the iteration itself using Iterator pattern and doing so 'll help our MensAndWomensApparelStore's getCatalog() method to deal with the catalog merger in much simpler and scalable way.
Code
To start with check out without iterator pattern example refer printCatalogWithoutIterator method in MensAndWomensApparelStore class
public class MensAndWomensApparelStore {
public static void main(String[] args) {
MensApparelStore mensApparelStore = new MensApparelStore();
WomensApparelStore womensApparelStore = new WomensApparelStore();
printCatalogWithoutIterator(mensApparelStore, womensApparelStore);
//printCatalogWithIterator(mensApparelStore, womensApparelStore);
}
private static void printCatalogWithoutIterator(MensApparelStore mensApparelStore, WomensApparelStore womensApparelStore) {
System.out.println("Printing mens Catalog: ");
for(Product apparelProduct : mensApparelStore.getCatalog()) {
System.out.print(apparelProduct.getName() + ", ");
System.out.print(apparelProduct.getDescription() + " ");
System.out.println(apparelProduct.getPrice());
}
System.out.println("\n Printing womens Catalog: ");
for(Product apparelProduct : womensApparelStore.getCatalog()) {
System.out.print(apparelProduct.getName() + ", ");
System.out.print(apparelProduct.getDescription() + " ");
System.out.println(apparelProduct.getPrice());
}
}
private static void printCatalogWithIterator(MensApparelStore mensApparelStore, WomensApparelStore womensApparelStore) {
Iterator mensApparelStoreIterator = mensApparelStore.createIterator();
System.out.println("Printing mens Catalog: ");
printCatalog(mensApparelStoreIterator);
System.out.println("\nPrinting womens Catalog: ");
Iterator womensApparelStoreIterator = womensApparelStore.createIterator();
printCatalog(womensApparelStoreIterator);
}
private static void printCatalog(Iterator iterator) {
while (iterator.hasNext()) {
Product product = (Product)iterator.next();
System.out.print(product.getName() + ", ");
System.out.print(product.getDescription() + " ");
System.out.println(product.getPrice());
}
}
}
Result :
Printing mens Catalog:
Polo Mens T-Shirt Medium, Polo Mens Shirt Medium size 16.99
Polo Mens T-Shirt Short, Polo Mens Shirt Short size 15.99
Polo Mens T-Shirt XL, Polo Mens Shirt XL size 19.99
Polo Mens T-Shirt XXL, Polo Mens Shirt XXL size 20.99
Louis Philippe slim-fit Shirt Medium, Louis Philippe slim-fit Shirt Medium size 15.99
Louis Philippe slim-fit Shirt Short, Louis Philippe slim-fit Shirt Short size 14.99
Louis Philippe slim-fit Shirt XL, Louis Philippe slim-fit Shirt XL size 18.99
Louis Philippe slim-fitShirt XXL, Louis Philippe slim-fit Shirt XXL size 19.99
Printing womens Catalog:
Beige & Black Animal Print Shirt Style Top MEDIUM, Beige & Black Animal Print Shirt Style Top best buy medium 12.99
Beige & Black Animal Print Shirt Style Top Long, Polo Womens Shirt Short size best buy long 15.99
Women Black Geometric Print Puff Sleeves Top, Women Black Geometric Print Puff Sleeves Top 19.99
Women White & Yellow Floral Shirt Style Top, Women White & Yellow Floral Shirt Style Top 20.99
Note, we can definitely achieve the goal without using iterator pattern but the problem is we would have to write code to make iterate over different return types. Also every time new merger happens we need to revisit and modify class again.
To avoid this and to provide minimal information about business classes(MensApparelStore and WomensApparelStore catalog ) to client class (MensAndWomensApparelStore) Iterator is good option.
Iterator pattern's goal is to make object iterable with their own terms instead passing entire collection to the client class , client is expected to call one by one element and don't worry about entire collection as well as what to filter.
Below is the code to create Iterator for our MensApparelStore and WomensApparelStore and then use their iterable objects in MensAndWomensApparelStore class.
public class MensApparelStoreIterator implements Iterator {
ArrayList<Product> catalog;
int position = 0;
public MensApparelStoreIterator(ArrayList<Product> catalog) {
this.catalog = catalog;
}
@Override
public boolean hasNext() {
if (position >= catalog.size() || catalog.get(position) == null) {
return false;
} else {
return true;
}
}
@Override
public Object next() {
Product product = catalog.get(position);
position = position + 1;
return product;
}
}
public class WomensApparelStoreIterator implements Iterator {
Product[] catalog;
int position = 0;
public WomensApparelStoreIterator(Product[] catalog) {
this.catalog = catalog;
}
@Override
public boolean hasNext() {
if (position >= catalog.length || catalog[position] == null) {
return false;
} else {
return true;
}
}
@Override
public Object next() {
Product product = catalog[position];
position = position + 1;
return product;
}
@Override
public void remove() {
if (position <= 0) {
throw new IllegalStateException("Can't remove item until you've done at least one next()");
}
if (catalog[position - 1] == null) {
for (int i = position - 1; i < (catalog.length - 1); i++) {
catalog[i] = catalog[i + 1];
}
catalog[catalog.length - 1] = null;
}
}
}
public class MensApparelStore {
private ArrayList<Product> catalog;
public MensApparelStore() {
catalog = new ArrayList<>();
//Add a few products to catalog
addItem("Polo Mens T-Shirt Medium", "Polo Mens Shirt Medium size ", 16.99);
addItem("Polo Mens T-Shirt Short", "Polo Mens Shirt Short size", 15.99);
addItem("Polo Mens T-Shirt XL", "Polo Mens Shirt XL size", 19.99);
addItem("Polo Mens T-Shirt XXL", "Polo Mens Shirt XXL size", 20.99);
addItem("Louis Philippe slim-fit Shirt Medium", "Louis Philippe slim-fit Shirt Medium size ", 15.99);
addItem("Louis Philippe slim-fit Shirt Short", "Louis Philippe slim-fit Shirt Short size", 14.99);
addItem("Louis Philippe slim-fit Shirt XL", "Louis Philippe slim-fit Shirt XL size", 18.99);
addItem("Louis Philippe slim-fitShirt XXL", "Louis Philippe slim-fit Shirt XXL size", 19.99);
}
public void addItem(String name, String description, double price) {
Product product = new Product(name, description, price);
catalog.add(product);
}
public ArrayList<Product> getCatalog() {
return catalog;
}
public MensApparelStoreIterator createIterator() {
return new MensApparelStoreIterator(catalog);
}
}
public class WomensApparelStore {
private static final int MAX_ITEMS = 4;
private int numberOfProducts = 0;
private Product[] catalog;
public WomensApparelStore() {
catalog = new Product[MAX_ITEMS];
//Add a few products to catalog
addItem("Beige & Black Animal Print Shirt Style Top MEDIUM", "Beige & Black Animal Print Shirt Style Top best buy medium ", 12.99);
addItem("Beige & Black Animal Print Shirt Style Top Long", "Polo Womens Shirt Short size best buy long", 15.99);
addItem("Women Black Geometric Print Puff Sleeves Top", "Women Black Geometric Print Puff Sleeves Top", 19.99);
addItem("Women White & Yellow Floral Shirt Style Top", "Women White & Yellow Floral Shirt Style Top", 20.99);
}
public void addItem(String name, String description, double price) {
Product product = new Product(name, description, price);
if (numberOfProducts >= MAX_ITEMS) {
System.out.println("Increase capacity Catalog size is Full");
} else {
catalog[numberOfProducts] = product;
numberOfProducts = numberOfProducts + 1;
}
}
public Product[] getCatalog() {
return catalog;
}
public WomensApparelStoreIterator createIterator() {
return new WomensApparelStoreIterator(catalog);
}
}
Main class :
public class MensAndWomensApparelStore {
public static void main(String[] args) {
MensApparelStore mensApparelStore = new MensApparelStore();
WomensApparelStore womensApparelStore = new WomensApparelStore();
//printCatalogWithoutIterator(mensApparelStore, womensApparelStore);
printCatalogWithIterator(mensApparelStore, womensApparelStore);
}
private static void printCatalogWithoutIterator(MensApparelStore mensApparelStore, WomensApparelStore womensApparelStore) {
System.out.println("Printing mens Catalog: ");
for(Product apparelProduct : mensApparelStore.getCatalog()) {
System.out.print(apparelProduct.getName() + ", ");
System.out.print(apparelProduct.getDescription() + " ");
System.out.println(apparelProduct.getPrice());
}
System.out.println("\n Printing womens Catalog: ");
for(Product apparelProduct : womensApparelStore.getCatalog()) {
System.out.print(apparelProduct.getName() + ", ");
System.out.print(apparelProduct.getDescription() + " ");
System.out.println(apparelProduct.getPrice());
}
}
private static void printCatalogWithIterator(MensApparelStore mensApparelStore, WomensApparelStore womensApparelStore) {
Iterator mensApparelStoreIterator = mensApparelStore.createIterator();
System.out.println("Printing mens Catalog: ");
printCatalog(mensApparelStoreIterator);
System.out.println("\nPrinting womens Catalog: ");
Iterator womensApparelStoreIterator = womensApparelStore.createIterator();
printCatalog(womensApparelStoreIterator);
}
private static void printCatalog(Iterator iterator) {
while (iterator.hasNext()) {
Product product = (Product)iterator.next();
System.out.print(product.getName() + ", ");
System.out.print(product.getDescription() + " ");
System.out.println(product.getPrice());
}
}
}
please note in printCatalogWithIterator method we are not passing entire list instead we pass Iterator which can be used to pull next item .
Pros and Cons
Pros
- Make complex traversal logic in your class itself which makes your class be responsible for it what to return next. By doing this Single Responsibility principle is achieved.
- We can delay an iteration like a pause or conditional agreement where if you wish to send next element you pass it or don't pass next element as you have not exposed your entire collection to client class its possible.
Cons
- It's not efficient as like passing entire collection and iterate in most non-complex cases.
- For simple types it 'll be complex process to make them Iterable enabled.
References : head-first design patterns, Learn Java Design Patterns: The Complete Guide [PACKT pub] , GOF design pattern book, refactoring . guru .