Iterator Pattern – parcurgerea colecțiilor fără expunerea internelor
În dezvoltarea aplicațiilor apare frecvent nevoia de a parcurge colecții de obiecte: liste, arbori, agregate sau structuri personalizate. De multe ori, implementarea internă a acestor colecții nu ar trebui expusă către exterior. Iterator Pattern rezolvă exact această problemă, oferind o metodă standard de parcurgere, fără a dezvălui detalii interne.
Iterator Pattern este un design pattern comportamental, extrem de des întâlnit în .NET, chiar dacă nu este mereu recunoscut ca atare.
Ce este Iterator Pattern?
Iterator Pattern oferă o interfață care permite accesarea secvențială a elementelor unei colecții, fără a expune structura sa internă.
Ideea de bază este simplă:
„Separă logica de parcurgere de structura colecției.”
Astfel, clientul nu trebuie să știe dacă parcurge o listă, un array, un arbore sau o structură custom.
De ce este important?
Fără Iterator Pattern:
-
Codul client ajunge să depindă de implementarea colecției
-
Parcurgerea diferitelor structuri devine inconsistentă
-
Orice modificare internă rupe codul existent
Cu Iterator Pattern:
-
Colecția își păstrează încapsularea
-
Parcurgerea devine uniformă
-
Codul este mai curat și mai ușor de testat
Structura pattern-ului
Iterator Pattern include următoarele componente:
-
Iterator – definește metodele de parcurgere
-
ConcreteIterator – implementează efectiv parcurgerea
-
Aggregate – interfața colecției
-
ConcreteAggregate – implementarea colecției
În .NET, aceste concepte sunt deja bine reprezentate prin IEnumerable și IEnumerator.
Iterator Pattern în .NET – exemplul clasic
Cel mai cunoscut exemplu de Iterator Pattern în .NET este:
foreach (var item in collection)
{
// folosim item
}
În spate:
-
collection implementează IEnumerable
-
foreach folosește IEnumerator
-
structura internă rămâne ascunsă
Exemplu practic – Iterator custom în C#
1️⃣ Colecția
public class ProductCollection
{
private readonly List<string> _products = new();
public void Add(string product)
{
_products.Add(product);
}
public IEnumerator<string> GetEnumerator()
{
return _products.GetEnumerator();
}
}
Această colecție:
-
ascunde lista internă
-
expune doar mecanismul de parcurgere
2️⃣ Utilizarea iteratorului
var products = new ProductCollection();
products.Add("Laptop");
products.Add("Mouse");
products.Add("Keyboard");
foreach (var product in products)
{
Console.WriteLine(product);
}
✔ Codul client nu știe nimic despre List<string>
✔ Parcurgerea este sigură și controlată
Iterator cu yield return
C# simplifică enorm implementarea iteratorilor prin yield return:
public IEnumerable<int> GetEvenNumbers(int max)
{
for (int i = 0; i <= max; i++)
{
if (i % 2 == 0)
yield return i;
}
}
Acest cod:
-
creează automat un iterator
-
este lazy (execută la nevoie)
-
consumă mai puțină memorie
Când este util Iterator Pattern?
✔ când ai colecții complexe
✔ când vrei să eviți expunerea internelor
✔ când ai mai multe moduri de parcurgere
✔ când vrei iterație lazy
✔ când vrei cod ușor de testat
Avantaje
✅ Separare clară a responsabilităților
✅ Încapsulare mai bună
✅ Cod client simplu
✅ Suport nativ în .NET
✅ Lazy evaluation
Dezavantaje
⚠️ Overhead pentru colecții foarte simple
⚠️ Complexitate mai mare pentru structuri triviale
În practică, beneficiile depășesc aproape întotdeauna dezavantajele.
Iterator Pattern vs LINQ
LINQ folosește masiv Iterator Pattern:
-
Where
-
Select
-
OrderBy
-
Take
Toate sunt implementate ca iteratori lazy, ceea ce explică performanța și flexibilitatea lor.
Concluzie
Iterator Pattern este unul dintre cele mai importante și mai folosite design patterns în .NET. Chiar dacă nu îl implementezi manual zilnic, îl folosești constant prin foreach, IEnumerable și LINQ.
Înțelegerea acestui pattern te ajută să:
-
scrii cod mai curat
-
eviți dependențe inutile
-
creezi colecții extensibile și robuste