Design Pattern: Iterator Pattern in Go

Iterator pattern traverses through aggregate items. Using this pattern we can establish a standard for traversal and the traversal of the items becomes easy.

In this article, we are demonstrating Iterator pattern implementation in Golang. Check the implementation details and the examples.

Implementation

Follow the steps below to implement Iterator pattern in Golang-

  • Create an interface for the Iterator. Declare one method for checking if there is any item available for the next iteration, and another method for getting the next item. There can be other method declaration as per the requirement.
  • Create iterator struct, that implements the interface. Define field for storing the list of items, and the index of current items.
  • Create another struct that uses the iterator. This struct has fields for storing the list of items and a method that creates a new iterator and returns that.
  • While using the implementation we can the data struct and get a new iterator. Then we can use the methods of the iterator.

Here is a simple example of the implementation-

// Iterator pattern in Golang

package main

import "fmt"

// Iterator interface
type Iterator[T any] interface {
	HasNext() bool
	Next() T
}

// Custom iteratory
type MyIterator[T any] struct {
	index int
	items []T
}

func NewMyIterator[T any](items []T) (myIterator *MyIterator[T]) {
	myIterator = &MyIterator[T]{}
	myIterator.items = items
	return
}

func (myIterator *MyIterator[T]) HasNext() bool {
	return myIterator.index < len(myIterator.items)
}

func (myIterator *MyIterator[T]) Next() T {
	if myIterator.index >= len(myIterator.items) {
		panic("no more item")
	}

	currentIndex := myIterator.index
	myIterator.index += 1

	return myIterator.items[currentIndex]
}

// Data struct that uses the custom iterator
type Data[T any] struct {
	items []T
}

func NewData[T any](items []T) (data *Data[T]) {
	data = &Data[T]{}
	data.items = items
	return
}

func (data *Data[T]) GetIterator() Iterator[T] {
	return NewMyIterator(data.items)
}

// Demo
func main() {
	items := []string {
		"test1",
		"test2",
		"test3",
	}

	data := NewData[string](items)
	iterator := data.GetIterator()

	for iterator.HasNext() {
		fmt.Println(iterator.Next())
	}
}

Output will be as below-

test1
test2
test3

Examples

Here are a few examples. Take a look-

Example #1: Pagination Iterator

Let’s take the example of iterating and showing pagination items.

Page Item Struct [General]

This struct is for general use. It is not directly related to the iterator implementation. The “Page” struct represents a single item for representing an element of pagination.

  • Create file “page.go”.
  • Create struct “Page”.
  • Create method “NewPage”. This method initiates a new “Page” and returns the pointer.
  • We have some utility methods- “GetNumber” and “GetPath”, implemented for the struct.
// page.go

package main

import "fmt"

type Page struct {
	number int
	path   string
}

func NewPage() (page *Page) {
	page = &Page{}
	return
}
func (page *Page) GetNumber() int {
	return page.number
}
func (page *Page) GetPath() string {
	if page.path == "" {
		return fmt.Sprintf("/page/%v", page.number)
	}
	return page.path
}

func (page *Page) SetNumber(number int) {
	page.number = number
}

func (page *Page) SetPath(path string) {
	page.path = path
}

Page List Interface

  • Create file “abstract_page_list.go”.
  • Create interface “AbstractPageListInterface”.
  • Declare methods “Add”, for adding a new page element to the list.
  • Declare method “Remove”, for removing a page element from list.
  • Declare method “Iterator” that returns an iterator.
// abstract_page_list.go

package main

type AbstractPageList interface {
	Add(page *Page)
	Iterator() *AbstractIterator
	Remove(page *Page)
}

Page List Struct

  • Create file “page_list.go”.
  • Create struct “PageList”.
  • Declare a field named “pages” of type of slice of “Page”. This field will be used to store the list of pages.
  • Define method “NewPageList”. This method initiates a new “PageList” and returns the pointer.
  • Implement interface “AbstractPageList” for the struct. Define method “Add”, “Remove”, “Iterator” for the struct, as part of the interface implementation.
// page_list.go

package main

type PageList struct {
	pages []Page
}

func NewPageList() (pageList *PageList) {
	pageList = &PageList{}
	return
}

func (pageList *PageList) Add(page Page) {
	pageList.pages = append(pageList.pages, page)
}

func (pageList *PageList) Remove(page Page) {
	for i, pageElem := range pageList.pages {
		if pageElem == page {
			pageList.pages = append(pageList.pages[:i], pageList.pages[i+1:]...)
			break;
		}
	}
}

func (pageList *PageList) Iterator() AbstractIterator {
	return NewIterator(pageList.pages)
}

Iterator Interface

  • Create file “abstract_iterator.go”.
  • Define interface “AbstractIterator”.
  • Declare method “HasNext”. Purpose of this method is to check if any item remains while traversing the list of items. This method returns a boolean.
  • Declare method “Next”. This method will be used to get the next item(when implemented).
// abstract_iterator.go

package main

type AbstractIterator interface {
	HasNext() bool
	Next() Page
}

Iterator Struct

  • Create file “iterator.go”.
  • Create struct “Iterator”.
  • Declare field “currentPosition”, which is used to track the last element which is accessed from the list.
  • Define field “page”, which is a slice of “Page”. This is used to store the full list of pages.
  • Define method “NewIterator”. This accepts a list (slice) of “Page” items, initates a new “Iterator” and set the initial values.
  • Implement the “AbstractIterator” for the “Iterator” struct. Define methods “HasNext” and “Next” for the struct.
// iterator.go

package main

type Iterator struct {
	currentPosition int
	pages           []Page
}

func NewIterator(pages []Page) (iterator *Iterator) {
	iterator = &Iterator{}
	iterator.currentPosition = 0
	iterator.pages = pages
	return
}

func (iterator *Iterator) HasNext() bool {
	return iterator.currentPosition < len(iterator.pages)
}

func (iterator *Iterator) Next() Page {
	page := iterator.pages[iterator.currentPosition]
	iterator.currentPosition++
	return page
}

Demo

For using the implementation we need a list of pages. We are using the method “populatePageList” for generating a list of pages (this is a dummy method). This method creates a new “PageList” struct, add items to the list, and then returns that.

Then we are getting the “Iterator” method of the “pageList” to obtain the iterator.

Then we can use a loop that runs until there are items available in the list(use “HasNext” method to check that). And then get the next item using “Next” method.

// main.go

package main

import "fmt"

func main() {
	pageList := populatePageList()
	paginator := pageList.Iterator()
	for paginator.HasNext() {
		page := paginator.Next()

		fmt.Printf("Page Number: %v\n", page.GetNumber())
		fmt.Printf("Page Path: %v\n\n", page.GetPath())
	}
}

// dummy method for generating a list of pages
func populatePageList() *PageList {
	pageList := NewPageList()

	for i := 0; i < 10; i++ {
		page := NewPage()
		page.SetNumber(i)
		pageList.Add(*page)
	}

	return pageList
}

Output

Page Number: 0
Page Path: /page/0

Page Number: 1
Page Path: /page/1

Page Number: 2
Page Path: /page/2

Page Number: 3
Page Path: /page/3

Page Number: 4
Page Path: /page/4

Page Number: 5
Page Path: /page/5

Page Number: 6
Page Path: /page/6

Page Number: 7
Page Path: /page/7

Page Number: 8
Page Path: /page/8

Page Number: 9
Page Path: /page/9

Source Code

Use the following link to get the source code:

Other Code Implementations

Use the following links to check the Iterator pattern implementation in other programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.