Iterator (Iteratör)
Introduktion
Iterator är ett beteendemönster som låter dig traversera elementen i en samling sekventiellt utan att exponera dess underliggande representation — om det är en lista, ett träd, en graf eller något annat.
Problem
Du har en samling av objekt — kanske en anpassad trädstruktur för ett filsystem. Klientkoden behöver kunna loopa igenom alla noder, men den ska inte behöva känna till att det är ett träd. Och du vill kunna erbjuda flera olika traverseringsordningar: djupet-först, bredden-först, bara löv.
Att stoppa traverseringslogiken i samlingen bryter mot Single Responsibility Principle. Att exponera den interna strukturen bryter mot inkapsling.
Lösning
Iterator extraherar traverseringslogiken till ett eget objekt. Samlingen skapar en iterator via en fabriksmetod. Klientkoden arbetar mot iteratorn via ett standardgränssnitt — i .NET är det IEnumerable<T> och IEnumerator<T>, vilket låter dig använda foreach direkt.
När ska Iterator användas?
- När du vill dölja den interna strukturen för en samling.
- När du vill erbjuda flera traverseringsstrategier för samma samling.
- När du vill ha ett enhetligt sätt att traversera olika typer av samlingar.
Struktur
| Roll | Ansvar |
|---|---|
| Iterator Interface | Deklarerar traverseringsoperationerna (MoveNext, Current, Reset). |
| Concrete Iterator | Implementerar traverseringslogiken för en specifik samling. |
| Iterable Interface | Deklarerar metod för att skapa en iterator. |
| Concrete Collection | Implementerar IEnumerable<T> och returnerar sin iterator. |
Exempel — Filsystemsträd (C# / .NET)
Scenariot: En trädstruktur för ett filsystem som kan traverseras djupet-först eller bredden-först — klientkoden ser bara foreach.
using System.Collections;
using System.Collections.Generic;
// ── Nodmodell ─────────────────────────────────────────────
public class FileNode
{
public string Name { get; }
public bool IsFolder { get; }
public List<FileNode> Children { get; } = [];
public FileNode(string name, bool isFolder = false)
{
Name = name;
IsFolder = isFolder;
}
public FileNode Add(FileNode child)
{
Children.Add(child);
return this;
}
public override string ToString() =>
IsFolder ? $"📁 {Name}" : $"📄 {Name}";
}
// ── Djupet-först Iterator (DFS) ───────────────────────────
public class DepthFirstIterator : IEnumerator<FileNode>
{
private readonly Stack<FileNode> _stack = new();
private FileNode? _current;
public DepthFirstIterator(FileNode root)
{
_stack.Push(root);
}
public FileNode Current => _current
?? throw new InvalidOperationException("Iteratorn har inte startats.");
object IEnumerator.Current => Current;
public bool MoveNext()
{
if (_stack.Count == 0) return false;
_current = _stack.Pop();
// Lägg till barn i omvänd ordning så att första barnet processas först
for (int i = _current.Children.Count - 1; i >= 0; i--)
_stack.Push(_current.Children[i]);
return true;
}
public void Reset()
{
throw new NotSupportedException("Återställning stöds inte.");
}
public void Dispose() { }
}
// ── Bredden-först Iterator (BFS) ──────────────────────────
public class BreadthFirstIterator : IEnumerator<FileNode>
{
private readonly Queue<FileNode> _queue = new();
private FileNode? _current;
public BreadthFirstIterator(FileNode root)
{
_queue.Enqueue(root);
}
public FileNode Current => _current
?? throw new InvalidOperationException("Iteratorn har inte startats.");
object IEnumerator.Current => Current;
public bool MoveNext()
{
if (_queue.Count == 0) return false;
_current = _queue.Dequeue();
foreach (FileNode child in _current.Children)
_queue.Enqueue(child);
return true;
}
public void Reset()
{
throw new NotSupportedException("Återställning stöds inte.");
}
public void Dispose() { }
}
// ── Samling med fabriksmetoder ────────────────────────────
public class FileTree : IEnumerable<FileNode>
{
private readonly FileNode _root;
public FileTree(FileNode root)
{
_root = root;
}
// Standard: djupet-först (används av foreach)
public IEnumerator<FileNode> GetEnumerator() =>
new DepthFirstIterator(_root);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
// Alternativ traversering
public IEnumerable<FileNode> BreadthFirst()
{
BreadthFirstIterator iterator = new(_root);
while (iterator.MoveNext())
yield return iterator.Current;
}
}
// ── Klientkod ─────────────────────────────────────────────
FileNode root = new("projekt", isFolder: true);
root.Add(new FileNode("README.md"))
.Add(new FileNode("src", isFolder: true)
.Add(new FileNode("Program.cs"))
.Add(new FileNode("Models", isFolder: true)
.Add(new FileNode("User.cs"))
.Add(new FileNode("Order.cs"))))
.Add(new FileNode("tests", isFolder: true)
.Add(new FileNode("UnitTests.cs")));
FileTree tree = new(root);
Console.WriteLine("=== Djupet-först (DFS) ===");
foreach (FileNode node in tree)
Console.WriteLine($" {node}");
Console.WriteLine("\n=== Bredden-först (BFS) ===");
foreach (FileNode node in tree.BreadthFirst())
Console.WriteLine($" {node}");
Output:
=== Djupet-först (DFS) ===
📁 projekt
📄 README.md
📁 src
📄 Program.cs
📁 Models
📄 User.cs
📄 Order.cs
📁 tests
📄 UnitTests.cs
=== Bredden-först (BFS) ===
📁 projekt
📄 README.md
📁 src
📁 tests
📄 Program.cs
📁 Models
📄 UnitTests.cs
📄 User.cs
📄 Order.cs
Tips: I .NET är
yield returnett kraftfullt alternativ till att implementeraIEnumerator<T>manuellt för enklare fall.
Fördelar
- Single Responsibility Principle — Traverseringslogiken är separerad från samlingen.
- Klientkoden kan använda
foreachdirekt tack vareIEnumerable<T>. - Erbjud flera traverseringsstrategier utan att ändra samlingens klass.
Nackdelar
- Överkurs för enkla samlingar —
List<T>ochIEnumerable<T>täcker de flesta behov. - Kan vara ineffektivt om traverseringstillståndet är tungt att hålla i minnet.
Av Victor Hernandez från Bytebase.se