Iterator Design Pattern

Merhaba sevgili dostlar!

Iterator Design Pattern ile tasarım desenleri dizisine devam ediyoruz! .NET framework içinde birçok yerde kullanılan bu deseni gelin tanımaya başlayalım. Elbette yine daha önceki yazılarda da olduğu gibi önce problemi anlayarak başlayalım.

Problem

Uygulama geliştirirken en çok kullandığımız veri yapısı herhalde koleksiyonlardır. Hatta özellikle veritabanı uygulamaları geliştirirken, genellikle veriyi bir koleksiyon biçiminde bellekte saklarız. Bu koleksiyonların çoğu basit düzeyde olduğu doğru. Fakat bazen, ağaç yapısı (composite) gibi kompleks veri yapılarının kullanıldığı koleksiyonlar da karşımıza çıkabiliyor.

Peki şimdi bir düşünün. Elinizde birkaç tane farklı yapıda koleksiyon olsun. Bu koleksiyonların içindeki her bir elemanda dolaşmanız gereken durumlar olacaktır öyle değil mi? Basit bir koleksiyonda böyle bir iş problem değil. Ancak kompleks yapıdaki bir koleksiyonda, elemanlar arasında nasıl hareket edersiniz? Hele ki bu hareketinizin stratejisi değişirse, bu değişim ile nasıl bir mimariyle baş edersiniz?

Gerçek Dünyadan bir örnek

Bir zamanların en çok izlenen güldürüsü, Olacak O Kadar skeçlerindeki gibi bir sahne düşünün. Masasının başında oturan memurun önünde bir yığın dosya bulunmaktadır. Memur sizce bu dosyaları nasıl eritecek? Yöntemi ne olacak? Baştan sona mı yoksa tersten mi gidecek? Ya da tarih veya konu gibi bir özelliğe göre gruplayıp tek tek mi ele alacak?

İşte memurun tercih edebileceği bu yöntemlerin hepsi bir iterasyon (yineleme) biçimidir. Dosyalar değişebilir. Ama seçilecek yineleme şekline göre yapabilecekleri aşağı yukarı aynıdır.

Çözüm

Gerçek dünya örneğinden anladığımız üzere, tüm yineleme mekanizmalarının şu sorulara cevap verebilmesi ve işlemleri yapabilmesi gerekir:

  • Şu anki öge nedir?
  • Başka öge kaldı mı?
  • Bir sonraki ögeye geç (ya da bir önceki)

Her ne olursa olsun, bir yineleyici her türlü öge ile çalışabilir olmalı. Başka bir deyişle, iterasyon (bu kelimeyi kullandığım için bağışlayın ancak böyle bir kelime konunun kavranmasına yardımcı olabiliyor) koleksiyondan bağımsız bir biçimde tasarlanmalı. Yani yineleyicinin neyi yinelediğini bilmesine gerek yok. Şimdi çözümümüzün parçalarını üç aşağı beş yukarı belirleyebiliriz artık.

Kod Örneği

İlk olarak, her yineleyicinin ortak arayüzünün nasıl olacağını tasarlayalım. Bunun için yukarıda belirttiğim üç adım bize yardımcı olabilir. Bu durumda:

  1. Yineleyici, her türlü tiple çalışabilmeli. Bunu, arayüzü jenerik yaparak sağlayabiliriz.
  2. Bir yineleyici, o an çalıştığı ögeyi döndürebilmeli. Bu, yineleyicinin yalnızca-okunur bir özelliği olabilir.
  3. Yineleyici, ileri ya da geri hareket edebilmeli. Fakat bu hareketi, eğer yinelediği koleksiyon uygunsa yapmalı. Yani denetlemeli ve uygunsa, ikinci adımda belirttiğimiz özelliği güncellemeli.

Elbette ihtiyaç duyulan başka operasyonlar da arayüze eklenebilir. Örneğin ben genellikle, yinelenen koleksiyonun ilk ve son ögelerini döndüren metotlar da ekliyorum. Ancak tekil sorumluluk prensibi açısından bu operasyonları spesifik sınıflara da eklemek mümkün. Her neyse. Şimdiye dek ele aldığımız bu adımları koda dönüştürelim bakalım.

  //1. yineleyici her türlü tip ile çalışabilmeli
    public interface Iterator<T>
    {
        //2. Yineleyici, çalıştığı ögeyi döndürebilmeli
        T SuAnkiOge { get; }
        //3. Varsa bir sonraki ögeye geç. 
        bool BirSonraki();
    }

Her yineleyicinin, yinelediği koleksiyondan farklı tanımlanması gerektiğini söyledik. Ancak bir de yineleyici mekanizmanın değişebileceğini de söyledik. Madem öyle, bir koleksiyon ve bu koleksiyonun nasıl yenileneceği bir bileşen tarafından bir arada tutulabilir. İşte o bileşenin arayüzü de şöyle olacak:

//Her taşıyıcı, hem koleksiyon hem de yineleyici nesneyi bir arada barındırır.

 //Her taşıyıcı, hem koleksiyon hem de yineleyici nesneyi bir arada barındırır.
    public interface ITasiyici<T>
    {
        Iterator<T> YineleyiciOlustur();
    }

Interface’ler hazır olduğuna göre, somut nesnelerimi inşa etmeye başlayabilirim. İlk olarak yinelenecek koleksiyonun ögelerini tanımlayarak başlayalım.

Belki biraz klişe bir senaryo olacak ama ben yine de bir kategori koleksiyonunu yinelemek istiyorum.  

 //Yinelenecek koleksiyon ögesi:
    public class Kategori
    {
        public string KategoriAdi { get; set; }
        public string Aciklama { get; set; }
    }

İlk olarak taşıyıcı sınıfımızı somutlaştıralım. Unutmayın! Taşıyıcı, hem kategori koleksiyonunu hem de bu koleksiyonun nasıl yineleneceğini barındırmalı.  

 public class KategoriTasiyici : ITasiyici<Kategori>
    {
        //Kategori Koleksiyonu.
        public List<Kategori> TumKategoriler { get; } = new List<Kategori>();

        //koleksiyona kategori ekle
        public void KategoriEkle(Kategori kategori) => TumKategoriler.Add(kategori);        

        public int KategoriSayisi { get => TumKategoriler.Count; }
        //Koleksiyonun yineleyicisi
        public Iterator<Kategori> YineleyiciOlustur()
        {
            //TODO 1: Yineleyiciyi döndür.
           
        }
    }

Yineleyici (Iteratör) sınıfımız da doğrudan taşıyıcı ile çalışmalı. Çünkü bu sınıfın koleksiyonumuza erişip belli bir algoritma ile içindeki ögelerde dolaşması gerekecek. O zaman gelsin bakalım kodumuz:

//Kategori yineleyici sınıfı
    public class KategoriIterator : Iterator<Kategori>
    {
        //Çalışılacak taşıyıcı nesne:
        private KategoriTasiyici kategoriTasiyici;
        //taşıyıcı nesne, constructor'da belirtiliyor:
        public KategoriIterator(KategoriTasiyici kategoriTasiyici)
        {
            this.kategoriTasiyici = kategoriTasiyici;
        }

        //bir ögeden diğerine geçişi yöneten algoritmanın ana noktası, koleksiyonun aktif index değerini bellekte tutmak:
        private int aktifIndex = 0;

        public Kategori SuAnkiOge { get; private set; }

        public bool BirSonraki()
        {
            if (aktifIndex < kategoriTasiyici.KategoriSayisi)
            {
                SuAnkiOge = kategoriTasiyici.TumKategoriler[aktifIndex++];
                return true;
            }
            else
            {
                return false;
            }

        }
    }

Artık yineleyici nesnemiz hazır olduğuna göre, taşıyıcı nesnesinin YineleyiciOlustur() metodunu tamamlayabiliriz.

public Iterator<Kategori> YineleyiciOlustur()
{
    //TODO 1: Yineleyiciyi döndür.
     return new KategoriIterator(this);
}

Burada karışık gelebilecek durum şu: Yineleyici, taşıyıcıda bulunan koleksiyonu kullanıyor. Fakat aynı yineleyici, taşıyıcı üzerinden oluşturuluyor. Bunun yapmamızın sebebini belirtmiştik. Bir yineleyici, yinelediği koleksiyondan bağımsız olarak tasarlanabilmeli.

Tüm amacımızın buradaki “bağımsızlık” olduğunu vurgulayarak, istemci kodumuzu yazabiliriz artık.

 private static void Main(string[] args)
 {
    KategoriTasiyici kategoriTasiyici = new KategoriTasiyici();
    kategoriTasiyici.KategoriEkle(new Kategori { KategoriAdi = "Tekstil", Aciklama = "Tekstil ürünleri" });
    kategoriTasiyici.KategoriEkle(new Kategori { KategoriAdi = "Elektronik", Aciklama = "Elektronik ürünler" });
    kategoriTasiyici.KategoriEkle(new Kategori { KategoriAdi = "Mobilya", Aciklama = "Mobilya ürünleri" });

    var iterator = kategoriTasiyici.YineleyiciOlustur();

     while (iterator.BirSonraki())
     {
         Console.WriteLine(iterator.SuAnkiOge.KategoriAdi);
     }
    Console.ReadLine();

 }

İstemci kodunda gördüğünüz while döngüsü, tüm çabamızın sebebi işte 😊. Şaka bir yana, bakın bu desenin sağladığı faydalar neler:

  1. Yineleyicinin algoritması değişebilir. Bu söz konusu olduğunda, tek yapmanız gereken aynı arayüzü kullanan başka bir yineleyici geliştirip, taşıyıcı üzerinden bu nesneyi döndürmek (oldu mu size kapalı/açık prensibi uyumluluğu).
  2. İstemci kodunuz (yukarıdaki main) hiç değiştirmeye gerek duymadan çalışabilir (oldu mu size tekil sorumluluk prensibi uyumluluğu)
  3. Yineleyici altyapısı, sadece Kategori ögesi ile değil, ihtiyaç duyulan bir başkası ile de çalışabilir.

Evet sevgili dostlar. .NET bileşenleri içinde bolca karşımıza çıkan Iterator tasarım deseninin hakkından böylece gelmiş olduk.

Bir sonraki yazımıza dek arrivederci!

 

  

Leave a Reply