Makale Özeti

Kalıplar üzerindeki ilgi giderek artarken, hergün yeni kalıplarla karşılaşmaya devam ediyoruz. Bugün kalıplar arenasında büyük bir ilgi gören GoF kalıpları dışında da bir çok tasarım kalıbı mevcut. Bu yazımızda şartname kalıbını incelemeye çalışacağız.

Makale

Şartname Kalıbı (Specification Pattern)

 

 

Kalıplar üzerindeki ilgi giderek artarken, hergün yeni kalıplarla karşılaşmaya devam ediyoruz. Bugün kalıplar arenasında büyük bir ilgi gören GoF kalıpları dışında da bir çok tasarım kalıbı mevcut.  Bu yazımızda şartname kalıbını incelemeye çalışacağız.

 

İçinde yaşadığımız dünya şartların ve kuralların hakim olduğu ve bunların giderek karmaşık bir hal aldığı dünya. Ya yazılımlar, elbette yazılımlar da bu kural ve şartlara sıkı sıkıya bağlılar.  Değişimin önlenemez yükselişi sürekli yeni şartların sisteme katılmasını zorunlu kılmakta. Peki bahsi geçen şartlardan anladığımız ne, şartların ortaya çıkarttığı problem alanı nedir.

 

Bugün bir çok projede bazı koşulları kontrol eden mantıksal test metodları görmeniz mümkün. Eğer alan yönlendirmeli tasarım (Domain Driven Design) yöntemi üzerinde yürüyorsanız, yoğun olarak varlık (entity) sınıfları üzerinde çalışmaktasınız ve nesne kümeleri üzerindeki filtrelemeler için SQL’i tercih etmiyorsunuz yada SQL bunun için yeterli gelmiyordur. Bu durumda sınıfların bir çok şartı kontrol eden basit yada karmaşık metodlarla kirlendiği kötü bir durum söz konusudur. Böylesi bir durum sorumlulukların dağıtımında kafamızda büyük bir soru işareti bırakır. Mesela birazdan inceleyeceğimiz örnekte geçen görevlerin belli şartlara göre filtrelenmesi ihtiyacında; görevler arasından kritik, öncellikli, gecikmiş veya bu şartların bir karmasından oluşan belli kriterlere göre seçilmesi bir problem olarak karşımıza çıkmaktadır. Göze çarpan ilk çözümde bu şartların Görev sınıfı içersinde tek tek ayrı metodlar olarak kodlanması kolay gözükmekte, fakat projenin ileriki aşamalarında ortaya çıkan yeni şartların Görev sınıfına eklenmesi görev sınıfının sorumluluğunun artmasına ve sınıf kirliliği olarak adlandırdığımız sınıflara aşırı sorumluluk yüklenmesi problemini ortaya çıkartmaktadır ve Görev sınıfı yapması gerekenlerden daha çok bu şart kontrol metodları ile şişmektedir.

 

İlk olarak problemin çıkış noktasını ortaya koymak istiyorum. Buyrun size görev takip uygulamasında kullanacağımız basit bir görev sınıfı:

 

class Görev

{

           

      public string GorevAdi;

      public string Sorumlu;

      public bool Kritik;

      public DateTime SonTeslimTarihi;

      public byte Oncelik;

     

      public Gorev()

      {

           

      }

           

      public override string ToString()

      {

            return      "Görev Adı : " + GorevAdi

                        ": " + Sorumlu +

                        "Görev : " + Kritik.ToString() +

                        "Teslim Tarihi : " + SonTeslimTarihi.ToShortDateString() +

                        "Öncelik : " + Oncelik.ToString();

      }

           

      public bool KritikGorevmi()

      {

            ..........

      }

           

      public bool OncellikliGorevmi()

      {

            .............

      }

           

      public bool GecikmisGorevmi()

      {

            .............

      }

}

 

 

 

Kod örneğinden anlaşıldığı üzere görevimiz üzerinde 3 adet şart kontrol metodu bulunmakta.  Görevimizin kritik, öncelikli ve gecikmiş olması gibi şartlarını kontrol eden bu metodlar Görev sınıfı içinde tanımlanmış. Peki yarın proje yöneticiniz size “Oğuz bu kriterlere yenileri eklemek istiyorum” diyerek gelirse ve buna birde , “hem kritik hemde gecikmiş görevleri görmek istiyorum, aynı zamanda eğer görev hem kritik hem de son teslim tarihini 2 gün geçmiş ise bunları da riskli görevler olarak almak istiyorum” derse, elbette proje yöneticinize kaşlarınızı çatıp buda nerden çıktı gibi bir tepki vermek istemezsiniz, ne güzel gül gibi çalışan bir kodunuz vardı, şimdi görev sınıfınıza bu yeni istekleri ekleyerek Görev sınıfınızda değişime ve dahası aşırı sorumluluklarla yüklenmesine sebebiyet vereceksiniz. Hiç hoş bir durum değil.

 

Bu çeşit şartları temel olarak alan nesnemiz(Domain Object) olan Görevin  sorumluluk alanına pek uymamakta dahası görev nesnesi bu türden metodlarla kirlenmekte ve aşırı yüklenmekte. Şartname kalıbı ile bu türden koşul ve kriter kontrol metodlarını ayrı şartname nesneleri olarak kodlamak bize;

 

§         Etkin sorumluluk dağıtımı

§         Açık/Kapalı prensibine uygun tasarım

§         Kod okunabilirliğinin artması

§         Karmaşık şartları ve kuralları modelleyebilme (SQL’in yetersiz olduğu koşullar)

 

gibi avantajlar kazandırmaktadır.

 

Şartname nesne üzerindeki belli koşul ve kriter onaylarının kendi sorumluluk nesneleri ile modellenmesi ve ilgili alan nesnesinden ayrılması üzerine kurulmuş, Eric Evans’ın Domain Driven Design kitabında ortaya koyduğu bir kalıptır (Specification Pattern).

 

 

İsterseniz şartname kalıbının uygulandığı örneğimize geçmeden çözümün UML diyagramı ile Görev takip uygulama mimarisini ortaya koyalım. Görev takip uygulaması görevlerin belli şart ve kriterlere göre filtrelendiği basit bir uygulamadan oluşmakta. Görevler kritik görevler, öncel görevler, gecikmiş görevler ve bunların karmasından oluşan bir dizi karmaşık kriterlere (bileşik kalıp - composite pattern) göre sınıflandırılmışdır.

 

 

GorevSartname: somut şartnameler için temel oluşturan soyut sınıf. Tüm somut şartname sınıflarının uygulaması gereken Şart sağlandımı metodu burada tanımlanmıştır. Bu sayede şartnameyi kullanan istemciler somut sınıflara bağımlı olmadan soyut şartname sınıfı üzerinden iş görürler. Bu sınıf aynı zamanda şartnamelerin esnek yaratılma sürecinden de sorumludur. Yaratım süreci fabrika metodu (Factory Method) ile gerçekleştirilmektedir.

 

(Kritik|Oncelikli|Gecikmis)Gorev: basit şartname sınıfları. Bileşik kalıbı (composite pattern) içinde yaprak görevi görürler. Karma şartname bu basit şartnamelerin birleşiminden oluşur. Her şartname ŞartSağlandımı metodunu uygulamak zorundadır.

 

KarmaSartname: basit şartnamelerin birleşiminden oluşan karmaşık şartları ve kriterleri barındıran bileşik sınıf. (Composite Pattern)

 

Yukarıdaki sınıf diyagramında şartname kalıbının dışında 2 farklı tasarım kalıbının daha kullanılması ile tasarım daha da esnek bir yapıya dönüştürülmüştür. Gözüken o ki, kalıp kalıbı çekiyor.

 

Gelelim örnek kodlarımıza. Kodlar üzerine yazdığım kısa yorumlar ile kodları anlatmaya çalıştım. Ne de olsa en iyi kod kendini anlatan koddur.

 

using System;

using System.Collections;

using System.Reflection;

 

namespace GorevTakip

{

 

public class Gorev

{

      public string GorevAdi;

      public string Sorumlu;

      public bool Kritik;

      public DateTime SonTeslimTarihi;

      public byte Oncelik;

           

      public Gorev()

      {

                 

      }

           

      public override string ToString()

      {

            return      "Görev Adı : " + GorevAdi

                        ": " + Sorumlu +

                        "Görev : " + Kritik.ToString() +

                        "Teslim Tarihi : " + SonTeslimTarihi.ToShortDateString() +

                        "Öncelik : " + Oncelik.ToString();

      }

           

}

     

public abstract class GorevSartname

{

      const string NAMESPACE = "GorevTakip";

           

      public static GorevSartname SartnameYarat(string sartnameSinifAdi)

      {

            // Factory Method tasarım kalıbı ile istenen şartname sınıfı yaratılır

                 

            // Reflection yardımı ile yaratmak istediğimiz tip adını

            // kullanarak tip tanımlıyor ve yeni bir şartname örneği yaratıyoruz

            Type sartnameTipi=Type.GetType(NAMESPACE + sartnameSinifAdi);

            return (GorevSartname)Activator.CreateInstance(sartnameTipi);

      }

           

      // Şartların onaylanması bu metod yardımı ile olmakta, tüm somut

      // şartname sınıfları bu metodu uygulamak zorundadır.

      public abstract bool SartSaglandimi(Gorev gorev);

}

     

public class KritikGorev : GorevSartname

{

      // Tüm şartname sınıflarında geçen bu metod,

      // aldığı görev nesnesinin kendi şartına

      // olan uygunluğunu kontrol eder ve sonucu döndürür

      public override bool SartSaglandimi(Gorev gorev)

      {

            if(gorev.Kritik)

                  return true;

            else

                  return false;

      }

}

     

public class OncelikliGorev : GorevSartname

{

      // öncelik eşik değeri

      byte oncelikEsigi;

           

           

      public OncelikliGorev(byte esik)

      {

            oncelikEsigi = esik;

      }

           

      // diğer şartname nesnelerinden farklı olarak

      // OncelikliGorev yapılandırıcısından aldığı

      // eşik parametresine dayanarak şartı kontrol etmekte

      public override bool SartSaglandimi(Gorev gorev)

      {

            if(gorev.Oncelik > oncelikEsigi)

                  return true;

            else

                  return false;

      }

}

     

public class GecikmisGorev : GorevSartname

{

      public override bool SartSaglandimi(Gorev gorev)

      {

            if(DateTime.Compare(DateTime.Today,gorev.SonTeslimTarihi0)

                  return true;

            else

                  return false;

      }          

}

     

/// <summary>

/// Karma Şartname, composite pattern'ın uygulandığı karmaşık

/// yapıda bulunan ve diğer görev şartnamelerinden oluşan composite

/// sınıf görevindedir. Karma sartname aynı anda birden fazla şartnamenin

/// kontrol edilmesinden sorumludur.

/// </summary>

public class KarmaSartname : GorevSartname

{

      // Karma şartnameye eklenmiş olan basit(leaf) şartname listesi

      ArrayList sartnameler;

     

      public KarmaSartname()

      {

            sartnameler=new ArrayList();

      }

     

      public override bool SartSaglandimi(Gorev gorev)

      {

           

            foreach(GorevSartname sartname in sartnameler)

            {

                  // Herhangi bir şartın sağlanmaması

                  // durumunda karma şartname tümüyle sağlanamamıştır

                  if(!sartname.SartSaglandimi(gorev))

                        return false;

            }

           

            return true;

      }

           

      public void SartnameEkle(GorevSartname sartname)

      {

            sartnameler.Add(sartname);

      }

     

      public void SartnameCikar(GorevSartname sartname)

      {

            sartnameler.Remove(sartname);

      }

           

}

 

 

İlk kod parçacığında görev ve görev şartlarını oluşturduk. Şimdi sıra bunları nasıl kullanacağımıza geldi.  Görev takip işlerini gerçekleştiren basit bir raporlama istemcisini göstermeye çalıştığımız bu örnekte, 3 basit şartname ve bunların ikisinden oluşan karma bir şartname kullanılmıştır.

 

using System;

using System.Collections;

 

namespace GorevTakip

{

class MainClass

{

      public static void Main(string[] args)

      {

           

            IList tumGorevler=GorevleriGetir();

                 

            IList kritikGorevler=UygunGorevleriGetir(tumGorevler,new KritikGorev());

            GorevleriGoster(kritikGorevler);

                 

            IList oncelikliGorevler=UygunGorevleriGetir(tumGorevler,new OncelikliGorev(7));

            GorevleriGoster(oncelikliGorevler);

                 

            IList gecikmisGorevler=UygunGorevleriGetir(tumGorevler,new GecikmisGorev());

            GorevleriGoster(gecikmisGorevler);

                 

            KarmaSartname riskSarti=new KarmaSartname();

            riskSarti.SartnameEkle(new KritikGorev());

            riskSarti.SartnameEkle(new GecikmisGorev());

            IList riskliGorevler=UygunGorevleriGetir(tumGorevler,riskSarti);

            GorevleriGoster(riskliGorevler);

      }

           

      /// <summary>

      /// sağlanan şartnameye uygun görevleri getirir. Bu metod

      /// ile birlikte tüm şartlara uygun genel bir şablon oluşmuştur

      /// </summary>

      /// <param name="gorevler">şartnamenin çalışacağı görev listesi</param>

      /// <param name="sartname"></param>

      /// <returns></returns>

      public static IList UygunGorevleriGetir(IList gorevler,GorevSartname sartname)

      {

            ArrayList gorevListesi=new ArrayList();

           

            foreach(Gorev gorev in gorevler)

            {

                  // ilgili şart onayı

                  if(sartname.SartSaglandimi(gorev))

                        gorevListesi.Add(gorev);

            }

                 

            return gorevListesi;

      }

           

      public static IList GorevleriGetir()

      {

            // İsteğinize uygun olarak değiştirin

            return null;

      }

           

      public static void GorevleriGoster(IList gorevler)

      {

            foreach(Gorev gorev in gorevler)

            {

                  Console.WriteLine(gorev.ToString());

            }

      }

                       

}

     

     

}

 

Görev takip uygulamamız şartname kalıbı ile birlikte daha fazla sayıda sınıf barındırsada bir çok açıdan eski görev takip uygulamasına göre daha avantajlı. Sisteme yeni ve daha karmaşık şartların ve kriterlerin eklenmesi çok daha kolay. Üstelik yeni ihtiyaçlar mevcut kod tabanında (Görev, İstemci) bir değişime sebep olmamakta dolayısıyla açık/kapalı prensibine uygun olmaktadır. Şartname sınıfları sayesinde Görev nesnesi sadece kendi işine odaklanmış, sorumluluklar etkin olarak paylaştırılmıştır. Elbette bu örnekteki şartlar gerçek hayattaki örneklere nazaran basit kalmakla birlikte temel oluşturması açısından önemlidir.

 

Şartname kalıbı yukarıdaki örneklerden farklı olarak şartname delegate’leri (specification delegate) ile de rahatlıkla uygulanabilir. Hatta ve hatta C# 2.0 ile birlikte gelen anonim delegateler (anonymous delegates) somut şartname sınıflarını gereksiz kılar. Örneğin çalışanlarımız arasından maaş eşiğini aşanlarını anonim delegatelerin yardımı ile şu şekilde alabiliriz:

 

public List<Calisan> YuksekMaaslilar(List<Calisan> calisanlar) {
  int maasEsikDegeri = 2000; // YTL
  return calisanlar.FindAll(delegate(Calisan e) { 
    return e.Maas > maasEsikDegeri;
  });
}

 

 

Şimdi bazılarınızın “Bu nasıl bir kalıp, bu söylenenleri ben basit SQL WHERE şartları ile yapardım” dediğini duyar gibiyim ve onlara hak veriyorum. Şartname kalıbı alan modellemesine uygun düşen ve şartların SQL ile gerçekleştiremediği durumlarda, hatta bileşik kalıp (composite pattern) yardımıyla çok karmaşık şartlar için uygun gözükmekle birlikte uzun vadede size yol, su ve baraj olarak borcunu geri ödeyebilir. Şunu unutmayın en iyi tasarım en az satır kod yada çok az sayıda sınıfla işinizi görmek değildir, iyi tasarım hem kolay anlaşılır hemde gelecekteki ihtiyaçlara sorunsuz bir şekilde cevap verebilen tasarımdır.

 

 

Kaynak

 

Domain-Driven Design: Tackling Complexity in the Heart of Software , Eric Evans