Monday, July 11, 2022

Behavioral Design Patterns # Iterator

Iterator is a behavioral design pattern that lets you traverse elements of a collection without exposing its underlying representation (list, stack, tree, etc.).


 
Problem

Collections are one of the most used data types in programming. Nonetheless, a collection is just a container for a group of objects.


Most collections store their elements in simple lists. However, some of them are based on stacks, trees, graphs and other complex data structures.

But no matter how a collection is structured, it must provide some way of accessing its elements so that other code can use these elements. There should be a way to go through each element of the collection without accessing the same elements over and over.

This may sound like an easy job if you have a collection based on a list. You just loop over all of the elements.

But how do you sequentially traverse elements of a complex data structure, such as a tree? For example, one day you might be just fine with depth-first traversal of a tree. Yet the next day you might require breadth-first traversal.And the next week, you might need something else, like random access to the tree elements.



Adding more and more traversal algorithms to the collection gradually blurs its primary responsibility, which is efficient data storage. Additionally, some algorithms might be tailored for a specific application, so including them into a generic collection class would be weird.

On the other hand, the client code that’s supposed to work with various collections may not even care how they store their elements.


However, since collections all provide different ways of accessing their elements, you have no option other than to couple your code to the specific collection classes.


Solution
The main idea of the Iterator pattern is to extract the traversal behavior of a collection into a separate object called an iterator.



In addition to implementing the algorithm itself, an iterator object encapsulates all of the traversal details, such as the current position and how many elements are left till the end. 

Because of this, several iterators can go through the same collection at the same time, independently of each other.

Usually, iterators provide one primary method for fetching elements of the collection. The client can keep running this method until it doesn’t return anything, which means that the iterator has traversed all of the elements.

All iterators must implement the same interface. This makes the client code compatible with any collection type or any traversal algorithm as long as there’s a proper iterator.


If you need a special way to traverse a collection, you just create a new iterator class, without having to change the collection or the client.


Structure

Pros and Cons


Iterator Pattern Example

Let’s understand iterator pattern with a simple example. Suppose we have a list of Radio channels and the client program want to traverse through them one by one or based on the type of channel. 

For example some client programs are only interested in English channels and want to process only them, they don’t want to process other types of channels.

So we can provide a collection of channels to the client and let them write the logic to traverse through the channels and decide whether to process them.

But this solution has lots of issues such as client has to come up with the logic for traversal. We can’t make sure that client logic is correct. Furthermore if the number of client grows then it will become very hard to maintain.

Here we can use Iterator pattern and provide iteration based on type of channel. We should make sure that client program can access the list of channels only through the iterator.

The first part of implementation is to define the contract for our collection and iterator interfaces.

public enum ChannelTypeEnum { ENGLISH, HINDI, FRENCH, ALL; }

ChannelTypeEnum is java enum that defines all the different types of channels.


public class Channel { private double frequency; private ChannelTypeEnum TYPE; public Channel(double freq, ChannelTypeEnum type){ this.frequency=freq; this.TYPE=type; } public double getFrequency() { return frequency; } public ChannelTypeEnum getTYPE() { return TYPE; } @Override public String toString(){ return "Frequency="+this.frequency+", Type="+this.TYPE; } }

Channel is a simple POJO class that has attributes frequency and channel type
public interface ChannelCollection { public void addChannel(Channel c); public void removeChannel(Channel c); public ChannelIterator iterator(ChannelTypeEnum type); }


ChannelCollection interface defines the contract for our collection class implementation. 

Notice that there are methods to add and remove a channel but there is no method that returns the list of channels. ChannelCollection has a method that returns the iterator for traversal. ChannelIterator interface defines following methods;

public interface ChannelIterator { public boolean hasNext(); public Channel next(); }

Now our base interface and core classes are ready, let’s proceed with the implementation of collection class and iterator.

import java.util.ArrayList; import java.util.List; public class ChannelCollectionImpl implements ChannelCollection { private List<Channel> channelsList; public ChannelCollectionImpl() { channelsList = new ArrayList<>(); } public void addChannel(Channel c) { this.channelsList.add(c); } public void removeChannel(Channel c) { this.channelsList.remove(c); } @Override public ChannelIterator iterator(ChannelTypeEnum type) { return new ChannelIteratorImpl(type, this.channelsList); } private class ChannelIteratorImpl implements ChannelIterator { private ChannelTypeEnum type; private List<Channel> channels; private int position; public ChannelIteratorImpl(ChannelTypeEnum ty, List<Channel> channelsList) { this.type = ty; this.channels = channelsList; } @Override public boolean hasNext() { while (position < channels.size()) { Channel c = channels.get(position); if (c.getTYPE().equals(type) || type.equals(ChannelTypeEnum.ALL)) { return true; } else position++; } return false; } @Override public Channel next() { Channel c = channels.get(position); position++; return c; } } }

Notice the inner class implementation of iterator interface so that the implementation can’t be used by any other collection. Same approach is followed by collection classes also and all of them have inner class implementation of Iterator interface.

Let’s write a simple iterator pattern test program to use our collection and iterator to traverse through the collection of channel.

public class IteratorPatternTest { public static void main(String[] args) { ChannelCollection channels = populateChannels(); ChannelIterator baseIterator = channels.iterator(ChannelTypeEnum.ALL); while (baseIterator.hasNext()) { Channel c = baseIterator.next(); System.out.println(c.toString()); } System.out.println("******"); // Channel Type Iterator ChannelIterator englishIterator = channels.iterator(ChannelTypeEnum.ENGLISH); while (englishIterator.hasNext()) { Channel c = englishIterator.next(); System.out.println(c.toString()); } } private static ChannelCollection populateChannels() { ChannelCollection channels = new ChannelCollectionImpl(); channels.addChannel(new Channel(98.5, ChannelTypeEnum.ENGLISH)); channels.addChannel(new Channel(99.5, ChannelTypeEnum.HINDI)); channels.addChannel(new Channel(100.5, ChannelTypeEnum.FRENCH)); channels.addChannel(new Channel(101.5, ChannelTypeEnum.ENGLISH)); channels.addChannel(new Channel(102.5, ChannelTypeEnum.HINDI)); channels.addChannel(new Channel(103.5, ChannelTypeEnum.FRENCH)); channels.addChannel(new Channel(104.5, ChannelTypeEnum.ENGLISH)); channels.addChannel(new Channel(105.5, ChannelTypeEnum.HINDI)); channels.addChannel(new Channel(106.5, ChannelTypeEnum.FRENCH)); return channels; } }

When we run above program, it produces following output

Frequency=98.5, Type=ENGLISH Frequency=99.5, Type=HINDI Frequency=100.5, Type=FRENCH Frequency=101.5, Type=ENGLISH Frequency=102.5, Type=HINDI Frequency=103.5, Type=FRENCH Frequency=104.5, Type=ENGLISH Frequency=105.5, Type=HINDI Frequency=106.5, Type=FRENCH ****** Frequency=98.5, Type=ENGLISH Frequency=101.5, Type=ENGLISH Frequency=104.5, Type=ENGLISH

You may also like

Kubernetes Microservices
Python AI/ML
Spring Framework Spring Boot
Core Java Java Coding Question
Maven AWS