Iterator design pattern for beginners.

Background - 

Assume yourself as an expert in data structure. Someone asked you to create a container class. Which can hold any user-defined data type, and it has to be very efficient. As you are the expert of data structure. You will decide which data structure to be picked for implementing a given container efficiently. As you want to hold private information about implementation with yourself. You provide a container in the form of a library. Without mentioning how you have implemented it (fully encapsulated).

You did it because of many reasons. Like – you can change internal logic in the future without affecting much/no client code. Or you don’t want to put implementation in the public domain as you want to charge the client for it. Or you want to own code single-handed for future code manipulation.


Understand problem statement - 

  1. If you have chosen an array to implement your container, You can traverse data through the index. i.e array[index0], array[index1]... etc.
  2. If you have chosen a Linked list to implement your container. You can traverse data through pointer next. i.e data = data->next;
  3. If you have chosen a tree to implement your container. You can traverse data through DFS or BFS.
For all different types of DS traversing techniques are different (I have mentioned only 3, but there are many more). It means you have to tell the client what data structure you have chosen. For the client's sequential data traversal, because without knowing the DS no one can traverse stored data sequentially.

Okay, all set, the client has purchased your library. Without knowing the internal data structure. The core behavior of a container class is to hold data. The client has put his data into a container. But after putting data, he has to get those data back sequentially from container class as and when he needs.

Now without knowing the data structure. How you or any client can traverse the container and get the data back? Is it a linked list or array or hash table or tree? Traversing data is different in all different types of data structure, and you are not willing to tell what data structure you have used to implement your library. That makes sense as it is your library, and you want to keep internal implementation private.

Let me think about a straightforward solution. You would agree. Only one who knows the internal data structure is the container class itself. So why not, I introduce a function called next() as a public behavior/function in my container class? Then client just has to keep calling next() to traverse data.

Why the solution, as mentioned above, is not right, first we are violating the very first design principle, that says a class is not supposed to perform any operation which is not in its core behavior. Think about any container in real-life scenarios. It can contain the data, add or remove data. So – add(), remove() and insert(), but traversing container is not the core behavior of any container class.  Second, it will be encapsulation violation if we tell the internal data structure through next()'s function signature because without showing the internal data structure, it cannot traverse sequentially.


How to implement an algorithm (more specifically a container class) where we have to traverse my data sequentially, but without knowing the internal data structure? Answer is iterator design pattern.

Why we need iterator design pattern –

We can retrieve data sequentially without showing internal data structure, with following all OOAD design principles.


Iterator design pattern –

It uses implicit trusting. The container class will be the owner of the iterator object, and it will destroy the iterator object as soon as work gets completed. Below mentioned UML will give you high-level understanding -

UML Structure -


iterator DP
UML structure of iterator design pattern.

C++ Example -





#include<iostream>
using namespace std;
 
const int MAXSIZE = 10;

class Stack 
{
  public:
    Stack()
    {
      m_tos = -1;
    }
    void Push(int input)
    {
      if(m_tos > MAXSIZE) 
      {
        cout<<"List is full, first delete some item then insert "<<endl;
        return;
      }
      m_array[++m_tos] = input;
    }
    int Pop() 
    {
      if(m_tos == -1) 
      {
        cout<<"List is empty, first insert some item then delete "<<endl;
        return 0;
      }
      return m_array[m_tos++];
    }
    bool IsEmpty()
    {
      if(m_tos == -1)
        return true;
      return false;
    }
    Stack* Begin()
    {
      return this;
    }
    int End()
    {
      return MAXSIZE;
    }

    friend class StackIterator;
    class StackIterator* CreateIterator() const;

  private:
    int m_array[MAXSIZE];
    int m_tos;
};
 
class StackIterator 
{
  public:
    StackIterator(): m_stackIterator(NULL),m_index(0) {}
    StackIterator(const Stack* stack):m_stackIterator(stack),m_index(0){}
    void Next()
    {
      m_index++;
    }
    int CurrentItem()
    {
      return m_stackIterator->m_array[m_index];
    }
    void operator = (const Stack* stack)
    {
      m_stackIterator = stack;
    }
    bool operator !=(int input)
    {
      if (m_index < input)
          return true;
      return false;
    }
    int CurrentIndex()
    {
      return m_index;
    }

  private:
    int m_index;
    const Stack* m_stackIterator;
};
 
StackIterator* Stack::CreateIterator() const 
{
  return new StackIterator(this);
}
/*
#######################################################################################
#########  __  __       _        ######################################################
######### |  \/  | __ _(_)_ __   ######################################################
######### | |\/| |/ _` | | '_ \  ######################################################
######### | |  | | (_| | | | | | ######################################################
######### |_|  |_|\__,_|_|_| |_| ######################################################
#######################################################################################
*/
int main(int argc, char** argv) 
{
  Stack objStack;
     
  for(int index = 0; index < MAXSIZE; ++index) 
  {
      objStack.Push(index);
  }
  StackIterator *objStackIterator = objStack.CreateIterator();
  for(; (objStackIterator->CurrentIndex()) != (objStack.End()); objStackIterator->Next())
  {
    cout<<" "<< (objStackIterator->CurrentItem());
  }
  cout << endl;
  return 0;
}
 
///////////////////////////////////////////////////////////////////-------------OutPut
/*
$ ./a.exe
 0 1 2 3 4 5 6 7 8 9

*/

Thanks for reading it. To learn more about design patterns and basic design principles, please see my web page.

Comments

Popular posts from this blog

Non-virtual interface idiom (NVI)

Architectural patterns => Mud to structure => layers.

Architectural style -> Adoptable system -> Reflection.