Patterns. The Iterator pattern. Features of implementation in C++

The Iterator pattern. Features of implementation in C++ for a polymorphic container and a polymorphic iterator

This topic is a continuation of the topic:


Contents


Search other websites:




1. The structure of the Iterator pattern in C++ codes. Picture

Figure 1 shows the structure of the Iterator pattern for a certain container class (list, dynamic array, queue, etc.). The figure reflects the structure for any number of containers (polymorphic container) and any number of iterators (polymorphic iterator). Of all the possible structures, this structure is the most versatile.

For educational purposes, a fragment of its implementation in C++ is added to each block of the structure.

Diagram of the Iterator pattern with a C++ implementation

Figure 1. Diagram of the Iterator pattern with a C++ implementation

 

2. Components of the Iterator pattern. A minimal set of classes to implement a C++ pattern that supports multiple containers and multiple iterators

The block diagram of the Iterator pattern, which provides support for multiple containers and multiple iterators, is shown in Figure 4. For this implementation of the pattern, the following classes need to be developed.

2.1. Abstract class Aggregate

 The abstract class Aggregate defines the interface of the container. Typically, this class contains declarations of a pure virtual CreateIterator() method to return an iterator, which is the abstract class Iterator.

// Abstract class of aggregate (interface)
template <class T>
class Aggregate
{
public:
  // Method that will return an iterator for some list
  virtual Iterator<T>* CreateIterator() const = 0;
};

 

2.2. The abstract class Iterator

The abstract class Iterator defines an iterator interface. Here you can specify the basic bypass methods according to the following example

// Abstract class of iterator
template <class T>
class Iterator
{
public:
  // Move to the beginning of the list
  virtual void First() = 0;

  // Move to the next item in the list
  virtual void Next() = 0;

  // Checking if the end of the list has been reached,
  // the current cursor position is behind the last item in the list
  virtual bool IsDone() const = 0;

  // Get a list item at the current cursor position
  virtual T CurrentItem() const = 0;
};

When using an iterator, the concept of a cursor or the current position of the element in question arises.

If desired, the programmer can declare additional workarounds. For example, the End() method – moves the cursor to the last element of the list.

 

3. One or more specific containers

Any container is a class that implements one of the aggregated data structures (list, dynamic array, queue, etc.). In our case (Figure 2), the container name is generic and defined as ConcreteAggregate. In our case (Figure 2), the container name is generic and defined as ConcreteAggregate. However, in real programs, the name can be changed to something more readable (List, Array, Vector, Map, Queue, etc.).

The ConcreteAggregate class implements the Aggregate interface class. This means that the ConcreteAggregate class implements the CreateIterator() method.

The container class code could be something like this

// Concrete Aggregate - Implements the Aggregate interface
template <class T>
class ConcreteAggregate : public Aggregate<T>
{
private:
  // aggregate data (list, array, ...)
  // ...

public:
  Iterator<T>* CreateIterator() const;

  // Other methods of ConcreteAggregate class
  // ...
};

For example, for a dynamic array, the abbreviated version of the C++ code might be

// Concrete dynamic array
template <class T>
class Array : public Aggregate<T>
{
private:
  // Internal variable
  T* array; // data in the form of dynamic array
  long count; // number of items

public:
  // Constructor
  Array(long size);

  // Copy constructor
  Array(const Array& obj);

  // Returns the number of items in the collection
  long Count() const;

  // Get an element of an array at a given index,
  // if the index is incorrectly specified, the function returns nullptr
  T& Get(long index) const
  {
    ...
  }

  // Implementation of the method that creates an iterator
  Iterator<T>* CreateIterator() const
  {
    ...
  }

  // Implementing a method that adds an item to an array
  void Append(T value)
  {
    ...
  }

  // Deleting item from array, index = 0, 1, ..., count-1
  void Remove(long index)
  {
    ...
  }

  // Destructor
  ~Array()
  {
    ...
  }

  // Other components of the class
  // ...
};

 

4. One or more specific iterators

For any aggregated object (container), various strategies for traversing its elements can be implemented. A concrete iterator implements one of these strategies

// Concrete iterator
template <class T>
class ConcreteIterator : public Iterator<T>
{
private:
  const ConcreteAggregate<T>* aggregate;
  long current;

public:
  // Constructor that gets the aggregated object
  ConcreteIterator(const ConcreteAggregate<T>* _aggregate) :
    aggregate(_aggregate), current(0)
  { }

  // To the first item
  void First()
  {
    current = 0;
  }

  // To the next item
  void Next()
  {
    current++;
  }

  // Check whether the end of the list
  bool IsDone() const
  {
    return current >= aggregate->Count();
  }

  // Outputs the current item
  T CurrentItem() const
  {
    if (!IsDone())
      return aggregate->GetItem(current);
    else
    {
      // Here is an error, you can throw an exception or perform other actions
      cout << "Error." << endl;
      return 0;
    }
  }
};

Following the above example, you can create your own iterator that will traverse the elements of the container in some specific way.

 


Related topics