Behavioral — Davranışsal Desenler
Iterator
Yineleyici (Gezici) Deseni
Amaç: Bir koleksiyonun iç yapısını (dizi mi, ağaç mı, liste mi) açığa çıkarmadan elemanlarını sırayla dolaşmayı sağlamak.
Basit Anlatım
Teknik terim yok — günlük hayattan bir benzetme
Bir müze rehberi düşünün. Müzenin içi karmaşık: katlar, salonlar, koridorlar, depolar. Ziyaretçi olarak siz müzenin planını bilmek zorunda değilsiniz. Rehber size "Sıradaki eser şu" diyerek sizi gezdirir.
Üstelik farklı rehberler farklı turlar sunabilir: "Kronolojik tur", "En popüler 10 eser turu", "Sadece heykeller turu". Müze aynı; dolaşma şekli farklı.
Televizyon kumandası
Kumandada "sonraki kanal" tuşuna basarsınız. Kanalların hangi frekansta olduğunu, uydudan mı kablodan mı geldiğini bilmezsiniz. Sadece "sıradakini göster" dersiniz.
Teknik Anlamı
Bir yazılımcının bakış açısıyla
Iterator, dolaşma (traversal) mantığını koleksiyondan ayırarak ayrı bir iterator nesnesine taşır. İterator; mevcut konumu, sonraki elemana geçmeyi ve dolaşmanın bitip bitmediğini yönetir.
Katılımcılar
- Iterator:
MoveNext(),Currentgibi dolaşma operasyonları. - ConcreteIterator: Belirli bir koleksiyon için dolaşma algoritması.
- Aggregate (Collection): Iterator üreten metodu tanımlar.
.NET'te Iterator dilin bir parçasıdır
IEnumerable<T>= Aggregate,IEnumerator<T>= Iterator.foreachdöngüsü arka plandaGetEnumerator(),MoveNext()veCurrentçağırır.yield returnile derleyici iterator sınıfını sizin yerinize üretir.- LINQ tamamen bu desen üzerine kuruludur ve lazy (ertelenmiş) değerlendirme sayesinde sonsuz dizilerle bile çalışabilir.
Avantajlar
- Koleksiyonun iç yapısı gizli kalır.
- Aynı koleksiyon için birden fazla dolaşma yöntemi tanımlanabilir.
- Birden fazla iterator aynı koleksiyonu aynı anda bağımsız olarak dolaşabilir.
Dezavantajlar
- Basit diziler için gereksiz soyutlama olabilir.
- Dolaşma sırasında koleksiyonun değiştirilmesi sorun çıkarır (
InvalidOperationException).
C# ile Uygulama
Adım adım, çalışan kod örneği
Senaryo: Bir organizasyon şeması (ağaç yapısı). Önce iterator'ı klasik yöntemle elle yazacağız, sonra aynı işi C#'ın yield return özelliğiyle yapacağız.
Adım 1 Koleksiyon: ağaç yapısındaki çalışanlar
public class Employee
{
public string Name { get; }
public string Title { get; }
public List<Employee> Reports { get; } = new();
public Employee(string name, string title) { Name = name; Title = title; }
public Employee Add(Employee e) { Reports.Add(e); return this; }
}Adım 2 Klasik yöntem: elle yazılmış iterator
IEnumerator<T> arayüzünü uygulayarak genişlik öncelikli (seviye seviye) dolaşma yapan bir iterator. İçeride bir kuyruk kullanır.
public class BreadthFirstIterator : IEnumerator<Employee>
{
private readonly Employee _root;
private readonly Queue<Employee> _queue = new();
public BreadthFirstIterator(Employee root) { _root = root; Reset(); }
public Employee Current { get; private set; } = null!;
object System.Collections.IEnumerator.Current => Current;
public bool MoveNext()
{
if (_queue.Count == 0) return false;
Current = _queue.Dequeue();
foreach (var r in Current.Reports) _queue.Enqueue(r);
return true;
}
public void Reset() { _queue.Clear(); _queue.Enqueue(_root); }
public void Dispose() { }
}Adım 3 Aggregate: farklı dolaşma yöntemleri sunan koleksiyon
Derinlik öncelikli dolaşma için yield return kullanıyoruz: derleyici bizim için bir iterator sınıfı üretir.
public class OrgChart : IEnumerable<Employee>
{
private readonly Employee _root;
public OrgChart(Employee root) => _root = root;
// Varsayılan dolaşma: genişlik öncelikli (elle yazılmış iterator)
public IEnumerator<Employee> GetEnumerator() => new BreadthFirstIterator(_root);
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() =>
GetEnumerator();
// Alternatif dolaşma: derinlik öncelikli (yield ile)
public IEnumerable<(Employee Emp, int Level)> DepthFirst() => Walk(_root, 0);
private static IEnumerable<(Employee, int)> Walk(Employee e, int level)
{
yield return (e, level);
foreach (var r in e.Reports)
foreach (var child in Walk(r, level + 1))
yield return child;
}
}Adım 4 Kullanım
Tüm parçaları bir araya getiren istemci (client) kodu:
var ceo = new Employee("Selin", "Genel Müdür")
.Add(new Employee("Kerem", "CTO")
.Add(new Employee("Ece", "Backend Lead"))
.Add(new Employee("Onur", "Frontend Lead")))
.Add(new Employee("Melis", "CFO")
.Add(new Employee("Tolga", "Muhasebe Uzmanı")));
var org = new OrgChart(ceo);
Console.WriteLine("Seviye seviye (genişlik öncelikli):");
foreach (var e in org)
Console.Write($"{e.Name} ");
Console.WriteLine("\n\nHiyerarşik (derinlik öncelikli):");
foreach (var (emp, level) in org.DepthFirst())
Console.WriteLine($"{new string(' ', level * 3)}- {emp.Name} ({emp.Title})");
// IEnumerable olduğu için LINQ ile de kullanılabilir:
var leads = org.Where(e => e.Title.Contains("Lead")).Select(e => e.Name);
Console.WriteLine($"\nLead'ler: {string.Join(", ", leads)}");Konsol çıktısı
Seviye seviye (genişlik öncelikli):
Selin Kerem Melis Ece Onur Tolga
Hiyerarşik (derinlik öncelikli):
- Selin (Genel Müdür)
- Kerem (CTO)
- Ece (Backend Lead)
- Onur (Frontend Lead)
- Melis (CFO)
- Tolga (Muhasebe Uzmanı)
Lead'ler: Ece, OnurKodda dikkat edilmesi gerekenler
- İstemci kodu ağacın nasıl tutulduğunu bilmiyor; sadece
foreachkullanıyor. - Aynı koleksiyon iki farklı sırayla dolaşıldı.
yield returnversiyonu elle yazılana göre çok daha kısa. Modern C#'ta iterator'lar neredeyse her zaman bu şekilde yazılır.
IEnumerable, foreach ve yield ile dilin temel parçasıdır.