Intent
Iterator Pattern provides a way to access the
elements of an aggregate object sequentially without exposing its underlying representation .
It also places the task of traversal on the iterator object, not on the aggregate, which simplifies the aggregate interface and implementation and places the responsibility where it should be.
It also places the task of traversal on the iterator object, not on the aggregate, which simplifies the aggregate interface and implementation and places the responsibility where it should be.
Typically an Iterator has
three tasks that might or might not be implemented in separate methods:
- Testing whether elements are available
- Advancing to the next n.th position
- Accessing the value at the current
position
Bidirectional iterators might have additional methods for checking and
advancing the reverse direction.
We use iterators quite frequently in everyday life. For example, remote
control of TV. We just pick up the TV remote control and start pressing Up and
Down or Forward and Back keys to iterate through the channels.
Motivation
Need to traverse different data structures so that algorithms can be
defined that are capable of interfacing with each transparently.
Discussion
The idea of the iterator pattern is to take the responsibility of
accessing and passing through the objects of the collection and put it in the
iterator object. The iterator object will maintain the state of the iteration,
keeping track of the current item and having a way of identifying what elements
are next to be iterated.
Moreover, you might want to traverse the list in different ways,
depending on what you need to accomplish. But you probably don’t want to bloat
the List interface with operations for different traversals, even if you could
anticipate the ones you’ll require. You might also need to have more than one
traversal pending on the same list. The Iterator pattern lets you do all this.
As an example, if you wanted to support four data structures (array,
binary tree, linked list, and hash table) and three algorithms (sort, find, and
merge), a traditional approach would require four times three permutations to
develop and maintain. Whereas, a generic programming approach would only
require four plus three configuration items.
Problem Statement:
We have a have big Car Dealer Showroom in United States which only deals
in selling OLD cars. They now decide to grow their business. They have started
building relationships with various Car Dealers involved in same business in
different cities.
To start off with, they tie up with Sports Car House who only deals in
Sports Cars and Heritage Car House who only deals in Heritage Cars. They are
very satisfied with the Agreement, but they will definitely face a lot of challenges
running such a business.
Iterator Pattern at First Glance:
Shown below is the United States Car House; they want to display the details
of all the cars available in all the branches of car with which they have a tie
ups.
UnitedStatesCarHouse |
Shown below is the Sports Car House and Heritage
Car House which are very different from each other.
SportsCarHouse |
HeritageCarHouse |
Let us try out Some Coding
Have a look at the below class which are very easy to understand.
public class Car {
String color;
String model;
double price;
public Car(String color, String model, double price) {
this.color = color;
this.model = model;
this.price = price;
}
// We can have the getter and Setters here
@Override
public String toString() {
return color+","+model+","+price;
}
}
|
public class SportCarHouse {
List
public SportCarHouse() {
sportsCars = new ArrayList
addItem("Black","Audi",3000000);
addItem("Red","Aston
Martin",299999);
addItem("Yellow","BMW",34966666);
addItem("Red","Bugatti",35555559);
}
public void addItem(String name, String description,double price)
{
Car menuItem = new Car(name, description,price);
sportsCars.add(menuItem);
}
public List
return sportsCars;
}
}
|
public class HeritageCarHouse{
static final int MAX_ITEMS = 6;
int numberOfCars = 0;
Car[] heritageCars;
public HeritageCarHouse() {
heritageCars = new Car[MAX_ITEMS];
addItem("Black","Land Rover",3000000);
addItem("Red","Aston
Martin",299999);
addItem("Yellow","Bentley Heritage",34966666);
addItem("Green","Austin Mini",35555559);
}
public void addItem(String name, String model, double price)
{
Car menuItem = new Car(name,model,price);
if (numberOfCars >= MAX_ITEMS) {
System.err.println("Sorry,
Car Hoouse is full! Can't add more
Cars");
}else {
heritageCars[numberOfCars] = menuItem;
numberOfCars = numberOfCars + 1;
}
}
public Car[] getCars() {
return heritageCars;
}
}
|
Looking at the above two classes we can easily make out that printing
the list all the cars from SportCarHouse and HeritageCarHouse we have to call
their getCars() methods which returns a List and an Array Respectively .
We would need two different
logics to print the cars from both the Car Houses dealers. We are also not sure
that tomorrow we are not going to add a new car dealer which returns a SortedTree or a Map
of cars. Oh oh oh!!!!! This is getting clumsy.
// To print the array of Heritage Cars
for (int i = 0; i < heritageCars.length; i++) {
Car car = heritageCars[i];
System.out.println(car);
}
// To print the list of Sports Cars
for (Iterator
Car car = iterator.next();
System.out.println(car);
}
|
NOW WHAT.
Both the
Car dealers are putting us in difficulty .They do not want to change their
implementations as this would mean them writing a lot of code in their classes.
But if one of them doesn’t give up, then we are going to implement the Car Dash
Board code which would be very difficult to maintain and extend.
It would
be great if we can find out a way to implement a common interface which will
perform the looping on the list of cars irrespective of the Data Structure.
This sounds good but how to do it.
public interface CarIterator {
public Iterator
}
public class SportCarHouse implements CarIterator {
/**
* We can remove this method as we don't require this.
* This also exposes our car List to outside world
*/
public List
return sportsCars;
}
public Iterator
return sportsCars.iterator();
}
}
public class HeritageCarHouse implements CarIterator {
/**
* We can remove this method as we don't require this.
* This also exposes our car List to outside world
*/
public Car[] getCars() {
return heritageCars;
}
public Iterator
return new HeritageCarArrayIterator(heritageCars);
}
}
|
Both the
SportsCarHouse and HeritageCarHouse will be implementing CarIteraror interface
and override the method createIterartor() which will return a concrete iterator
to loop the list/array of the cars.
Now we
have only one thing left to be done. Write Concrete Iterartors for both the Car
Houses. At last they agreed to make some changes to their code.
Here we
can make use of the fact that ArrayList themselves have return an iterator. So
we only have to worry for HeritageCarArrayIterator which look something like
this.
public class HeritageCarArrayIterator
implements
Iterator
Car[] carList;
int position = 0;
public HeritageCarArrayIterator(Car[] carList)
{
this.carList = carList;
}
public Car next() {
Car car = carList[position];
position = position + 1;
return car;
}
public boolean hasNext() {
if (position >= carList.length || carList[position] == null) {
return false;
} else {
return true;
}
}
public void remove() {
// We can implement this if required
}
}
|
Our dash
board code will now look very simple and it’s very maintainable and generic.
public class UnitedStateCarHouseDashBoard
{
private static List
public static void
addCarListToBePrinted(CarIterator iterator){
carIterator.add(iterator);
}
public static void
printMenu() {
Iterator
while (iterator.hasNext()) {
CarIterator carListIterator
= (CarIterator) iterator.next();
printMenu(carListIterator.createIterator());
}
}
public static void
printMenu(Iterator
while (iterator.hasNext()) {
Car car =
iterator.next();
System.out.println(car);
}
}
}
|
It’s time
to test out code have a first look at our Dash Board.
public class UnitedStatesCarHouse {
public static void
main(String[] args) {
SportCarHouse sch = new SportCarHouse();
HeritageCarHouse hch
= new
HeritageCarHouse();
UnitedStateCarHouseDashBoard.addCarListToBePrinted(sch);
UnitedStateCarHouseDashBoard.addCarListToBePrinted(hch);
UnitedStateCarHouseDashBoard.printMenu();
UnitedStateCarHouseDashBoard.printMenu(sch.createIterator());
}
}
Out Put is:
Black,Audi,3000000.0
Red,Aston Martin,299999.0
Yellow,BMW,3.4966666E7
Red,Bugatti,3.5555559E7
Black,Land Rover,3000000.0
Red,Aston Martin,299999.0
Yellow,Bentley Heritage,3.4966666E7
Green,Austin Mini,3.5555559E7
|
We are able to achieve what we wanted. Now we are only left with one
thing to grow our business, establish tie up with more and more car dealers.
Class Diagram |
Check list
- Add
a
createIterator()
method to the “collection” class, and grant the “iterator” class privileged access. - Design an “iterator” class that can encapsulate traversal of the “collection” class.
- Clients ask the collection object to create an iterator object.
- Clients
use the
first()
,remove()
,next()
, andhasNext()
protocol to access the elements of the collection class.
With iterators, the inability to get the count/size sometimes makes it awkward. Usually, one'd have a separate method to query the size but in doing so we would have split the two operations.
ReplyDeleteSo, it might not play well with UI components that do pagination.
How about using a lazy collection instead? At least the Hibernate folks took that route.
I do concede that a lazy collection would still not play with pagination though.
Choosing of design patterns to be used for a given problem depends on a lot of factors..Different approaches may be followed in different scernios
ReplyDelete