Iterator Pattern
Iterator is a behavioral design pattern that allows sequential traversal through a complex data structure without exposing its internal details.
Thanks to the Iterator, clients can go over elements of different collections in a similar fashion using a single iterator interface.
Golang Implementation
Example:
The main idea behind the Iterator pattern is to extract the iteration logic of a collection into a different object called iterator. This iterator provides a generic method of iterating over a collection independent of its type.
collection.go
package main
type Collection interface {
createIterator() Iterator
}
userCollection.go
package main
type UserCollection struct {
users []*User
}
func (u *UserCollection) createIterator() Iterator {
return &UserIterator{
users: u.users,
}
}
iterator.go
package main
type Iterator interface {
hasNext() bool
getNext() *User
}
userIterator.go
package main
type UserIterator struct {
index int
users []*User
}
func (u *UserIterator) hasNext() bool {
if u.index < len(u.users) {
return true
}
return false
}
func (u *UserIterator) getNext() *User {
if u.hasNext() {
user := u.users[u.index]
u.index++
return user
}
return nil
}
user.go
package main
type User struct {
name string
age int
}
main.go
package main
import "fmt"
func main() {
user1 := &User{
name: "a",
age: 30,
}
user2 := &User{
name: "b",
age: 20,
}
userCollection := &UserCollection{
users: []*User{user1, user2},
}
iterator := userCollection.createIterator()
for iterator.hasNext() {
user := iterator.getNext()
fmt.Printf("User is %+v\n", user)
}
}
// User is &{name:a age:30}
// User is &{name:b age:20}
Rust Implementation
Example:
In Rust, the recommended way to define your custom iterator is to use a standard Iterator trait. The example doesn’t contain a synthetic iterator interface, because it is really recommended to use the idiomatic Rust way.
users.rs
pub struct UserCollection {
users: [&'static str; 3], // static lifetime
}
/// A custom collection contains an arbitrary user array under the hood.
impl UserCollection {
/// Returns a custom user collection.
pub fn new() -> Self {
Self {
users: ["Alice", "Bob", "Carl"],
}
}
/// Returns an iterator over a user collection.
///
/// The method name may be different, however, `iter` is used as a de facto
/// standard in a Rust naming convention.
pub fn iter(&self) -> UserIterator {
UserIterator {
index: 0,
user_collection: self,
}
}
}
/// UserIterator allows sequential traversal through a complex user collection
/// without exposing its internal details.
pub struct UserIterator<'a> { // 'a here is a lifetime of the UserCollection
index: usize,
user_collection: &'a UserCollection,
}
/// `Iterator` is a standard interface for dealing with iterators
/// from the Rust standard library.
impl Iterator for UserIterator<'_> {
// above line is the same as:
// impl<'a> Iterator for UserIterator<'a> {
type Item = &'static str;
/// A `next` method is the only `Iterator` trait method which is mandatory to be
/// implemented. It makes accessible a huge range of standard methods,
/// e.g. `fold`, `map`, `for_each`.
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.user_collection.users.len() {
let user = Some(self.user_collection.users[self.index]);
self.index += 1;
return user;
}
None
}
}
main.rs
use crate::users::UserCollection;
mod users;
fn main() {
print!("Iterators are widely used in the standard library: ");
let array = &[1, 2, 3];
let iterator = array.iter();
// Traversal over each element of the array.
iterator.for_each(|e| print!("{} ", e));
println!("\n\nLet's test our own iterator.\n");
let users = UserCollection::new();
let mut iterator = users.iter();
println!("1nd element: {:?}", iterator.next());
println!("2nd element: {:?}", iterator.next());
println!("3rd element: {:?}", iterator.next());
println!("4th element: {:?}", iterator.next());
print!("\nAll elements in user collection: ");
users.iter().for_each(|e| print!("{} ", e));
println!();
}
// Iterators are widely used in the standard library: 1 2 3
//
// Let's test our own iterator.
//
// 1nd element: Some("Alice")
// 2nd element: Some("Bob")
// 3rd element: Some("Carl")
// 4th element: None
// All elements in user collection: Alice Bob Carl