22 Nisan 2010 Perşembe

Strategy Pattern

Strateji Deseninin amacı nedir?
Genel olarak yazacak olursam yazılımın değişikliklere uyum sağlayabilir bir şekilde tasarlanmasını sağlamak.(Aslında daha fazlası var)
Örnek ile başlayacak olursak:
Diyelim ki, öğretmenim veya çalıştığım şirket benden bir kuş similasyonu yapmamı istedi.Similasyonda iki tane kuş türü var,papağan ve güvercin.İlk olarak bu deseni bilmiyorsam ne yaparım.Muhtemelen aşağıdaki şekildeki gibi bir tasarım aklıma gelir.

Şekil.1
Dedim ki benim sistemimde zaten iki tane kuş türüm var papağan ve güvercin onların ortak özelliklerini kuş isimli sınıfta toplarım sonra göster methodunu da soyut tanımlarım ve de alt sınıflarda bunların tanımlanmasını sağlarım.Ne kadar iyi bir tasarım yaptım ya mükemmel bir programcıyım ben!!!Öylemiyim?
İleriki gelişim sürecinde patronum sisteme yeni tür bir kuş ördek eklememi istedi.Kolay hemen kuş sınıfından ördek sınıfı türetirim.Ama bir sorun var ördekler yüzebilir fakat kuş ana sınıfında öyle bir method yok ne yapsam ki?
Şekil.2
Çözüm 1.Yukarıdaki gibi türettiğim ördek sınıfına yüz methodunu eklerim olur.
Çözüm 2. Kuş sınıfının içine yüz methodu eklerim yeni bir yüzebilen bir sınıf eklendiğinde örneğin flamingo hiç tekrardan yüz methodu yazmam gerekmez çünkü temel sınıfta var,Eğer yüzemeyen sınıf varsa da yüz methodunu ezerek (overriding)tanımlarım boş bırakırım yada yüzemem yazdırırım.
İki çözümde güzel istediklerimi veriyor fakat
Çözüm 1'de bir sorun var her yeni yüzebilen kuş ekleğimde yüz fonksiyonunu yazmak yerine biraz daha iyi birşey yapmam gerekir.
Çözüm 2'de de başka bir sorun var ana sınıfa yüz methodunu eklersem bütün yüzemeyen sınıfların yüz methodunu ezmek(override) yazmak tasarımımda sorun olduğunu gösteriyor.


Sonra düşündüm ki similasyonuma hiç umulmadık kuş türleri katılabilir.Kartal,Tavuk,Penguen,Devekuşu vs .Mesela papağan ötmüyor,konuşuyor.Mesela öyle bir kuş düşünün ki uçamıyor ama uçak içinde olduğu için uçuyor göstermem lazım yada roketle uçan bir similasyon kuşu istenirse ne yapacağım...

Sonra aklıma bir fikir geliyor alttaki resim gibi bir tasarım yapıyorum.

Tasarımımda ilk olarak arayüz(interface) kullandım.3 tane arayüz(interface) 1 tane soyut ana sınıfım ve 4 tane somut alt sınıfım var.Böyle bir tasarım yaparak bir anlamda istediğim alt sınıflara istediğimiz fonksiyonları verebiliyorum.Diğer bir deyişle ayrılan özellikleri arayüzlere(interface) attım istediğim özelliği arayüzlerden çekebilirim.Fakat hala bir sorunum var interface özelliği gereği uç ve öt methodlarının tanımları tekrar alt sınıflarda yapma zorundayım.Buda beni başladığım noktaya geri döndürdü kod tekrarı...
Neden sınıftan değilde arayüzden(interface) değişen öt, yüz,uç fonksiyonlarını alıyoruz?
Bunu yapmamızın nedeni Java, C# gibi dillerde sadece alt sınıfları sadece bir sınıftan türetebilmemizdir.
Java C# gibi neden tek sınıftan türetiyorlar C++ gibi izin verseler birçok sınıftan türetilsin?
Aslında Java ve C# tasarımcıları böyle bir özelliğe gerek olmadığını görmüşler ve de onun yerine arayüz(interface) yapısını kullanmışlardır.İnterfacede zaten bir sınıftır bütün mehtodları soyut olan bir sınıf.
En son olarak söyle bir tasarım yaptım

Karmaşık görünen bu tasarımda ne yaptım:
  1. İlk olarak kutu içinde görünen yapıları bir arayüzden türemiş somut sınıflardır.Bu yapı arayüzler ile somut sınıflar arası bir kapsülleme imkanını da sağladı.Somut sınıflar her kuş için değişen özellikleri barındırır.Mesela uçmadavranışı arayüzünü yaratmamızın sebebi bütün kuşların uçamamasıdır ya da aynı şekilde uçamamasıdır.beslen fonksiyonunu bir beslenmedavranışı arayüzüne almadım çünkü bütün kuşların aynı şekilde beslendiğini varsaydım.
  2. Bütün arayüzlerin referanslarını kuş sınıfında tanımladım.Daha önceki tasarımda olduğu gibi direk somut sınıflarda implement etmedim.
  3. En son olarak arayüzlere değer ataması için kuş sınıfında yüzme,ötme,uçma davranışarını ataması birde bu işlemleri yapması için methodlar ekledim.
Bunun kodlarını ekliyeceğim...

Soru 1. Neden uçmadavranışının içine uç methodunu içini uçamam yazdıracak bir somut method yaptık?
Çünkü kuş sınıfına uçmadavranışı arayüzünün referansını verdiğimiz için uçamayan bir kuş türü eklendiğinde o somut sınıfı kullanmasını istiyoruz.
Soru 2.O zaman uçamayan kuş türleri var neden kuş ana sınıfı içerisinde uçmadavranışı arayüzüne referans veriyoruz.Onu çıkartalım sonra sadece uçabilen kuş türleri içerisinde onlara referans verelim?
İşte burda bir programlama seçeneği karşımıza çıkıyor
1.Uygulamaya programlama
Ördek ö= new Ördek();
ö.Uç();
2.Arayüze programlama
Kuş k= new Ördek();
k.Uç();
Eğer soruda denilen gibi yapsaydık ördek sınıfı uçabildiği fakat uçma özelliği kuş sınıfında olmadığı için 2 deki gibi yapamayacaktık çünkü k değişkeninde uçma özelliği olmayacaktı (Hata verecekti).2. seçenekte bizim tanımladığımız k değişkenine istediğimiz özellikleri verebiliriz ördek çalışma zamanında (runtime) olsa bile uçmasını engelleyebiliriz mesela...

Strateji Deseninde uygulanan tasarım prensipleri.
  1. Değişen methodları aynı kalan methodlardan ayırın ve de onları ayrı bir sınıfta tanımlayın.
  2. Arayüze yani ana sınıfa programlama yapın.(Yukarıda kısa anlatımı var)
  3. Sınıfları türetmek yerine birleştirmeyi seçin.
Bu seçenekleri bazılarını açıkladım fakat ayrı bir sayfada bunlarada değineceğim çünkü çok temel dolayısıyla çok önemli şeyler.

Strateji Desenini bize şunu sağladı:
Gereksiz arayüz veya sınıf türettiğimiz tasarımlar kodu sürekli olarak tekrarlanmasını sağlar.Biz bu deseni uygulayarak herhangi bir algoritmanın sadece bir kere yazılarak çok rahat bir şekilde heryerde kullanılmasını sağladık.
Ayrıca yazdığımız sınıfların özelliklerinin çalışma zamanın da bile değistirebilmesini sağladık.
Bunlarda yaptığımız tasarımın değişikliklere uyum gösterebilme yetenegini kazandırdı.







1 yorum:

  1. Çok iyi bir anlatım olmuş, en ufak ayrıntılara bil değinmişsiniz, kafamızda oluşabilecek soru işaretlerinin bile cevabı var.
    Çok teşekkürler..

    YanıtlaSil