Design Pattern : Iterator
Design Patterns: Elements of Reusable Object-Oriented Software

Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.
Search for a command to run...
Design Patterns: Elements of Reusable Object-Oriented Software

Senior Android Engineer from Bangladesh. Love to contribute in Open-Source. Indie Music Producer.
No comments yet. Be the first to comment.
A practical guide to choosing the right coroutine primitive for your Android/Kotlin projects

This guide explains what Channels are, when to use them, how to use them correctly, and when NOT to use them — in simple, professional language. 1. What problem do Channels solve? In Kotlin coroutines, you often have multiple coroutines running at t...

Learn how to build preview-safe ViewModels in Jetpack Compose using Hilt.

HandlerInterceptor vs Filter in Spring Boot: A Practical Guide with JNDI Injection Prevention

Simple In-Memory Caching: A Tiny Trick with Massive Impact

If get to know something new by reading my articles, don't forget to endorse me on LinkedIn
** Provide a way to access the elements of an aggregate object sequentially without exposing its underlying representation.**
As a Java programmer, it’s likely you have worked with aggregate objects, which are referred as collections in Java. A collection is an object that acts as a container to store other objects, which are referred as elements. Based on how a collection stores elements, there are different types of collections. For example, some collections may allow duplicate elements while others may not.
ArrayList, LinkedList, HashMap, LinkedHashSet` are few of the collections that are part of the Java Collection Framework. Like any other objects, a collection can be passed around the application or stored in other collection objects.
Where we can add or remove a Ticket or List of Tickets from the system. Also, we can find a Ticket or update a Ticket or travers through the Ticket list one by one.
By creating a Data Holder for Ticket Management System is enough for our requirements, where we can create a class that provides public APIs to the client but one minute. By doing that, the Data Holder’s underlying structure and implementation will be exposed to clients. This is a bad object-oriented design principle that does not follow encapsulation.
In addition, if you go back and revisit the SOLID design principles, it will be apparent that implementing element access and traversal operations in the collection itself is a clear violation of the Single Responsibility Principle. A collection should be only responsible to act as a container for storing elements, and any other responsibility such as element access and traversal should not be part of it.
The Iterator pattern addresses such recurring problems when dealing with aggregate objects. What this pattern says is that aggregate objects should provide a way to access its elements sequentially without exposing its internal structure.
Let’s summarize the participants of the Iterator Pattern.
Aggregate(TicketHolder): An interface that define all APIs to store or remove Ticket or list of Ticket and provide access to TicketIterator to iterate though the list of Tickets.
ConcreteAggregate(TicketHolderImpl): Is a concrete class that implements the Aggregate interface to create and return an iterator.
Iterator(TicketIterator): An interface that expose some APIs to the client to access, find, update, traverse thorough the elements.
ConcreteIterator(TicketIteratorImpl): Is a concrete class that implements the Iterator interface. Objects of this class keeps track of the elements and implements access and traversal operations on the elements, find an element or update an element.
Client(VerifyTickets): Client access point to access TicketHolder
data class TicketModel(val id: Int, var name: String = "")
interface TicketHolder {
fun addTicket(ticketModel: TicketModel)
fun addTickets(ticketModel: MutableList<TicketModel>)
fun removeTicket(ticketModel: TicketModel)
fun removeTickets(ticketModel: MutableList<TicketModel>)
fun clearAll(): Boolean
val ticketIterator: TicketIterator
}
class TicketHolderImpl : TicketHolder {
private var ticketList: MutableList<TicketModel> = ArrayList()
private var iterator: TicketIterator = TicketIteratorImpl(ticketList)
override fun addTicket(ticketModel: TicketModel) {
ticketList.add(ticketModel)
}
override fun addTickets(ticketModel: MutableList<TicketModel>) {
ticketList.addAll(ticketModel)
}
override fun removeTicket(ticketModel: TicketModel) {
ticketList.remove(ticketModel)
}
override fun removeTickets(ticketModel: MutableList<TicketModel>) {
ticketList.removeAll(ticketModel)
}
override fun clearAll(): Boolean {
this.ticketList.clear()
return true
}
override val ticketIterator: TicketIterator
get() = iterator
}
We have a Data Class (TicketModel) that represent a Ticket object, Data Holder and it's concrete implementation (TicketHolder, TicketHolderImpl) where all of the APIs are defined and access point for the TicketIterator
interface TicketIterator {
fun nextTicket(): TicketModel
fun findTicket(id: Int): TicketModel?
fun updateTicket(id: Int, name: String): Boolean
val hasNextTicket: Boolean
fun resetPositionTo(newPosition: Int)
}
class TicketIteratorImpl(private var ticketList: List<TicketModel>) : TicketIterator {
private var position: Int = 0
override fun nextTicket(): TicketModel {
val ticket = ticketList[position]
println("Ticket Info at position : $position - $ticket")
position++
return ticket
}
override fun findTicket(id: Int): TicketModel? =
ticketList.find { it.id == id; }?.apply {
println("Ticket found: $this")
} ?: kotlin.run { println("No ticket found with id :${id}"); null }
override fun updateTicket(id: Int, name: String): Boolean {
return ticketList.find { it.id == id }?.let {
it.name = name
return true
} ?: kotlin.run { println("No ticket found with id :${id}"); return@run false }
}
override val hasNextTicket: Boolean
get() = position >= ticketList.size
override fun resetPositionTo(newPosition: Int) {
this.position = newPosition
println("Position reset to $newPosition")
}
}
Now, we have the Iterator and implementation of Iterator (TicketIterator, TicketIteratorImpl) which enable access to public APIs to Iterate, Get Next Ticket, Find a Ticket, Update a Ticket.
object VerifyTickets {
private val ticketHolder: TicketHolder = TicketHolderImpl()
fun getTicketHolder(freshStart: Boolean = true): TicketHolder {
if (freshStart) ticketHolder.clearAll(); ticketHolder.addTickets(provideValidBusTickets())
return ticketHolder
}
fun printAll(holder: TicketHolder) {
println()
println("Starting to print....")
while (!holder.ticketIterator.hasNextTicket) {
holder.ticketIterator.nextTicket()
}
println("Ending of print....")
println()
}
/*Return a list of valid Tickets*/
private fun provideValidBusTickets(): MutableList<TicketModel> = mutableListOf(
TicketModel(0).apply { name = "Something $id" },
TicketModel(1).apply { name = "Something $id" },
TicketModel(2).apply { name = "Something $id" },
TicketModel(3).apply { name = "Something $id" },
TicketModel(4).apply { name = "Something $id" },
TicketModel(5).apply { name = "Something $id" },
TicketModel(6).apply { name = "Something $id" },
TicketModel(7).apply { name = "Something $id" },
TicketModel(8).apply { name = "Something $id" },
TicketModel(9).apply { name = "Something $id" },
TicketModel(10).apply { name = "Something $id" },
TicketModel(11).apply { name = "Something $id" }
)
}
VerifyTickets is the client access point to the whole system, where we have two public APIs, getTicketHolder(freshStart: Boolean = true) to get TicketHolder instance (with some Dummy Data) and printAll(holder: TicketHolder) to print all available tickets in the system.
Let's test.
class IteratorPatternExample {
companion object {
@JvmStatic
fun main(args: Array<String>) {
val holder = VerifyTickets.getTicketHolder()
/*Printing several tickets*/
println("Printing next tickets...")
holder.ticketIterator.nextTicket()
holder.ticketIterator.nextTicket()
holder.ticketIterator.nextTicket()
/*Find a specific ticket*/
println()
println("Finding tickets...")
holder.ticketIterator.findTicket(10)
holder.ticketIterator.findTicket(55)
/*Update a specific ticket*/
println()
println("Update ticket...")
println("Ticket update successfully :${holder.ticketIterator.updateTicket(1, "Updated Name")}")
println("Ticket update successfully :${holder.ticketIterator.updateTicket(55, "Exception")}")
/*Find a specific ticket*/
println()
println("Find ticket...")
holder.ticketIterator.findTicket(1)
/*Reset the iterator position*/
println()
println("Reset iterator position...")
holder.ticketIterator.resetPositionTo(0)
/*Print all tickets*/
VerifyTickets.printAll(holder)
}
}
}
Result:
Printing next tickets...
Ticket Info at position : 0 - TicketModel(id=0, name=Something 0)
Ticket Info at position : 1 - TicketModel(id=1, name=Something 1)
Ticket Info at position : 2 - TicketModel(id=2, name=Something 2)
Finding tickets...
Ticket found: TicketModel(id=10, name=Something 10)
No ticket found with id :55
Update ticket...
Ticket update successfully :true
No ticket found with id :55
Ticket update successfully :false
Find ticket...
Ticket found: TicketModel(id=1, name=Updated Name)
Reset iterator position...
Position reset to 0
Starting to print....
Ticket Info at position : 0 - TicketModel(id=0, name=Something 0)
Ticket Info at position : 1 - TicketModel(id=1, name=Updated Name)
Ticket Info at position : 2 - TicketModel(id=2, name=Something 2)
Ticket Info at position : 3 - TicketModel(id=3, name=Something 3)
Ticket Info at position : 4 - TicketModel(id=4, name=Something 4)
Ticket Info at position : 5 - TicketModel(id=5, name=Something 5)
Ticket Info at position : 6 - TicketModel(id=6, name=Something 6)
Ticket Info at position : 7 - TicketModel(id=7, name=Something 7)
Ticket Info at position : 8 - TicketModel(id=8, name=Something 8)
Ticket Info at position : 9 - TicketModel(id=9, name=Something 9)
Ticket Info at position : 10 - TicketModel(id=10, name=Something 10)
Ticket Info at position : 11 - TicketModel(id=11, name=Something 11)
Ending of print....
Process finished with exit code 0
TicketHolder focus on data storing, removing and provide access to TicketIterator. (Concrete classes implements the logic)
TicketIterator focus on Iteration process, finding ticket, update ticket or check if next ticket available or not. (Concrete classes implements the logic)
VerifyTickets provides access to client to access TicketHolder.