Design Pattern (20) Iterator Pattern: Unified Data Traversal Interface, Elegant Access to Multiple Collection Structures

You can download the Design Pattern series code from this design_pattern repo.

Requirements

Our task is to design a file system search tool with the following core requirements:

  • Multiple Search Strategies: Users can choose different file search methods. This mainly includes Breadth-First Search (BFS) and Depth-First Search (DFS) traversal strategies.
  • Unified Access Interface: The client doesn’t need to understand implementation details of various search algorithms. All search results should be accessed through a unified iterator interface.
  • Good Extensibility: The system architecture should support adding other search algorithms in the future. For example, sorted searches based on file size, modification time, or file type.

This requirement scenario demonstrates typical application characteristics of behavioral design patterns. We need to manage interaction relationships between different algorithm objects while providing a consistent access method.

Object-Oriented Analysis (OOA)

After understanding the requirements, we perform object-oriented analysis. In this scenario, we need to identify key elements in the system.

From requirement analysis, we have the following important elements:

  • File system structure (tree data structure)
  • Multiple traversal algorithms (BFS, DFS, etc.)
  • Unified access interface
  • Extensible search strategies

The core challenge lies in how to make different traversal algorithms provide consistent access methods while maintaining their respective independence and avoiding mutual interference.

Recognizing Forces

In direct implementation without design patterns, we encounter the following core challenges:

1. High Coupling Problem (High Coupling)

The client needs to directly operate specific implementation details of each search method. This means the client must understand internal logic like BFS using queues and DFS using stacks.

This tight coupling leads to bloated and hard-to-maintain code. When search algorithms need adjustment, client code must also be modified accordingly.

More importantly, this design violates the core principle of behavioral patterns: behavior users should not understand behavior implementation details.

2. Inconsistent Access Methods (Inconsistent Access)

Different search algorithms may adopt different result return methods. For example, BFS returns a List while DFS returns an Array. This inconsistency increases client usage complexity.

This problem directly affects behavior consistency. The client must write different processing logic for different return types. The result is code that’s difficult to maintain and error-prone.

3. Violates Open-Closed Principle (Violates OCP)

Whenever new search algorithms need to be added or existing search logic modified, client code must also be modified. This violates the Open-Closed Principle in software design, significantly increasing maintenance costs.

In behavioral design, this means adding new behaviors affects existing code. This design clearly violates basic principles of behavioral extensibility.

4. Tight Coupling Between Algorithms and Data Structures

Search algorithms are tightly coupled with internal data structures of the file system, making algorithms difficult to test independently and reuse. This tight binding limits code flexibility.

This coupling hinders behavior modularization. Ideally, each traversal behavior should operate independently without being restricted by specific data structures.

Root Problem Analysis

The root of these problems lies in the lack of an appropriate abstraction layer between data traversal logic and data access interface. Without abstraction layer protection, various implementation details are directly exposed to the client.

From a behavioral design perspective, we lack a unified behavior interface to manage different traversal strategies. This absence makes object interactions complex and difficult to control.

Applying Iterator Pattern to Solve Problems

After object-oriented analysis and recognizing system challenges, we can apply the Iterator Pattern to solve these problems.

Behavioral Characteristics of Iterator Pattern

Iterator Pattern is a behavioral design pattern. It provides a method to sequentially access elements of aggregate objects without exposing the object’s internal representation.

This pattern embodies the core spirit of behavioral design: simplifying complex object interaction behaviors into unified interfaces. Through this simplification, it successfully separates traversal algorithms from data structures, allowing us to independently change traversal behaviors without affecting other parts.

More importantly, it achieves unified behavior management. Whether BFS, DFS, or any other traversal strategy, clients can use them in exactly the same way, significantly reducing learning and usage costs.

Core Concepts of Iterator Pattern

The essence of the iterator pattern lies in separating the logic of ā€œhow to traverseā€ from the data structure of ā€œwhat to traverseā€. This separation brings tremendous flexibility.

For file search systems, this means we can access search results from BFS and DFS in the same way. Clients don’t need to learn two different usage methods.

This separation is the core of behavior management. It allows us to independently manage ā€œhow to traverseā€ behavior without being affected by ā€œwhat to traverseā€ data structures.

Roles and Responsibilities

Iterator Pattern solves system problems through the following four core roles. Each role is responsible for specific behavior management tasks:

1. Iterator (Iterator Interface)

Defines standard protocol for traversing elements. Mainly includes hasNext() to check if more elements exist, and next() to get the next element.

This interface ensures all traversal methods have consistent access methods. Regardless of underlying algorithms used, clients can operate in the same way. It’s the key to behavior uniformity, allowing different traversal strategies to provide services through the same interface.

2. ConcreteIterator (Concrete Iterator)

Implements specific traversal algorithms. For example, BFS iterator uses queue structure while DFS iterator uses stack structure. Each iterator has its own implementation details.

Each concrete iterator encapsulates specific search logic and state management. These classes implement behavior encapsulation, hiding complex algorithm logic behind simple interfaces. Clients only need to know how to use them without understanding internal operations.

3. Aggregate (Aggregate Interface)

Defines factory methods for creating iterators. It specifies how aggregate objects produce corresponding iterator instances. This interface is like an iterator manufacturing factory.

This interface is particularly important because it establishes connections between data and behavior. Through this connection, data structures can provide different traversal behaviors based on different needs.

4. ConcreteAggregate (Concrete Aggregate Class)

Implements the aggregate interface, representing actual file system data structure. It creates corresponding concrete iterators based on specified traversal strategies. This is the actual iterator factory implementation.

This class implements dynamic behavior selection. It can provide different traversal behaviors based on different requirements without exposing internal data structure details.

Application to File System Search Tool

Object-Oriented Programming Implementation

Now we’ll transform Iterator Pattern theory into actual code. Each component has clear responsibility division, forming a complete iterator system together.

This implementation fully demonstrates the design philosophy of behavioral patterns: managing various different behaviors through unified interfaces. This allows clients to focus on using behaviors themselves without caring about specific implementation details.

Iterator - Iterator Interface

The iterator interface defines standard protocols that all traversal operations must implement. This interface is the foundation of behavior uniformity. It ensures all traversal behaviors follow the same usage conventions:

interface Iterator<T> {
    fun hasNext(): Boolean
    fun next(): T
}

Aggregate - File System Aggregate Interface

The aggregate interface defines factory methods for creating iterators. It establishes connections between data and behavior. Through this interface, data structures can generate corresponding traversal behaviors:

interface FileSystem {
    fun createIterator(): Iterator<File>
}

ConcreteIterator - Concrete Iterator Implementation

Each concrete iterator encapsulates specific traversal algorithms and state management logic. These classes demonstrate concrete implementation of behavior. They transform abstract traversal interfaces into executable algorithms:

class BFSIterator(private val root: File) : Iterator<File> {
    private val queue = ArrayDeque<File>()

    init {
        queue.add(root)
    }

    override fun hasNext(): Boolean {
        return queue.isNotEmpty()
    }

    override fun next(): File {
        if (!hasNext()) throw NoSuchElementException()
        val current = queue.removeFirst()
        if (current.isDirectory) {
            queue.addAll(current.listFiles().orEmpty())
        }
        return current
    }
}

class DFSIterator(private val root: File) : Iterator<File> {
    private val stack = ArrayDeque<File>()

    init {
        stack.add(root)
    }

    override fun hasNext(): Boolean {
        return stack.isNotEmpty()
    }

    override fun next(): File {
        if (!hasNext()) throw NoSuchElementException()
        val current = stack.removeLast()
        if (current.isDirectory) {
            stack.addAll(current.listFiles().orEmpty())
        }
        return current
    }
}

ConcreteAggregate - Default File System Implementation

The concrete aggregate class creates corresponding iterators based on specified search strategies. This class implements dynamic behavior management. It can provide corresponding traversal behaviors based on different strategy requirements:

class DefaultFileSystem(private val root: File, private val searchMethod: SearchMethod) : FileSystem {
    override fun createIterator(): Iterator<File> {
        return when (searchMethod) {
            SearchMethod.BFS -> BFSIterator(root)
            SearchMethod.DFS -> DFSIterator(root)
        }
    }
}

enum class SearchMethod {
    BFS, DFS
}

File - File Data Structure

Simple file data structure supporting directories and regular files. This data structure remains simple and focused, containing no traversal logic. It perfectly embodies the separation of data and behavior design principle:

data class File(val name: String, val isDirectory: Boolean, val children: List<File> = emptyList()) {
    fun listFiles(): List<File> = if (isDirectory) children else emptyList()
}

Client - Client Usage Example

The client demonstrates how to use unified iterator interfaces to traverse file systems. Notice how the client uses different traversal strategies through unified behavior interfaces. Throughout the process, the client completely doesn’t need to understand internal implementation details:

fun main() {
    val fileSystem = DefaultFileSystem(
        root = File(
            name = "root",
            isDirectory = true,
            children = listOf(
                File("file1.txt", false),
                File("folder1", true, listOf(
                    File("file2.txt", false),
                    File("file3.txt", false)
                )),
                File("folder2", true, listOf(
                    File("file4.txt", false)
                ))
            )
        ),
        searchMethod = SearchMethod.BFS
    )

    val iterator = fileSystem.createIterator()
    println("Files:")
    while (iterator.hasNext()) {
        println("- ${iterator.next().name}")
    }
}

Execution Results

Execution results using BFS search strategy are as follows:

Files:
- root
- file1.txt
- folder1
- folder2
- file2.txt
- file3.txt
- file4.txt

Conclusion and Benefits

By applying the Iterator Pattern, we successfully solved the core problems originally faced by the system. This implementation fully demonstrates the powerful capability of behavioral design patterns in managing complex object interaction behaviors.

Main Improvement Effects

1. Unified Access Interface

All search algorithms are accessed through the same Iterator interface. This makes client code concise and consistent, no longer needing to learn multiple different operation methods.

This uniformity embodies the core value of behavior abstraction: unifying complex, diverse behaviors under simple interfaces. This design significantly reduces usage complexity.

2. Algorithm Independence

BFS and DFS implementations are completely independent, each encapsulating traversal logic and state management without mutual interference. Modifications to one algorithm don’t affect other algorithms.

This independence allows us to independently manage each behavior. Different traversal strategies can be independently developed, tested, and maintained, significantly improving code manageability.

3. Good Extensibility

Adding new search algorithms only requires implementing new ConcreteIterator without modifying existing code. This design fully complies with the Open-Closed Principle.

This extensibility demonstrates behavior composition capability: new behaviors can be easily integrated into existing systems without affecting existing behavior implementations. System evolution becomes easier.

4. Responsibility Separation

Traversal logic is completely separated from data structure, significantly improving code maintainability and testability. Each component has clear responsibility boundaries.

This separation is the essence of behavioral patterns: letting each object focus on its core responsibilities. Objects don’t need to understand or manage internal implementations of other objects, reducing overall complexity.

Value Embodiment of Behavioral Patterns

Iterator Pattern perfectly interprets the core values of behavioral design patterns:

  • Behavior Abstraction: Unifying complex diverse traversal behaviors under simple interfaces
  • Responsibility Separation: Letting each object focus on specific behavior management tasks
  • Interaction Simplification: Reducing complex dependencies between objects through unified interfaces
  • Dynamic Management: Selecting and switching different behavior strategies at runtime

Applicable Scenarios

Iterator Pattern is particularly suitable for the following application scenarios:

  • Complex Data Structure Traversal: Different traversal methods for tree, graph, or network structures
  • Collection Class Operations: Data collections requiring multiple traversal orders
  • Algorithm Strategy Switching: Scenarios where the same data needs to support multiple processing algorithms
  • Large Dataset Processing: Scenarios requiring lazy loading or batch processing

Core Value Summary

The core value of this pattern lies in abstracting traversal behavior. It allows clients to handle different traversal strategies in a unified way, greatly enhancing system flexibility and maintainability.

Through Iterator Pattern, we not only solved current technical problems but also established a system architecture with excellent behavior management capabilities. This architecture can flexibly respond to various traversal requirements and future feature expansions, laying a solid foundation for long-term system development.




    Enjoy Reading This Article?

    Here are some more articles you might like to read next:

  • Claude Code ä½æē”ØęŠ€å·§čˆ‡ęœ€ä½³åÆ¦čø - Tips and Best Practices
  • šŸ¤– AI Agent Series (Part 1): Understanding the Core Interaction Logic of LLM, RAG, and MCP
  • šŸ’” Managing Multiple GitHub Accounts on One Computer: The Simplest SSH Configuration Method
  • šŸš€ How to Use Excalidraw AI to Quickly Generate Professional Diagrams and Boost Work Efficiency!
  • Complete macOS Development Environment Setup Guide: Mobile Development Toolchain Configuration Tutorial