C# ile Kurucu Desen (Builder Pattern)


08 Mart 2013     Etiketler: Yazılım Mühendisliği C# YazılımDesenleri BuilderPattern KurucuDesen

Kurucu Desen (Builder Pattern) nedir? Fabrika ve Soyut Fabrika desenlerine benzeyen ve yaratımı karmaşık veya birçok dış etkene bağlı olan nesnelerin yaratımında bize yardımcı olabilecek bir yazlım desenidir.


Bu makale ilk olarak F5 Dergi Ağustos-Eylül 2012 sayısında yayınlanmıştır.

C# ile Kurucu Desen (Builder Pattern)

Geçtiğimiz sayımızla başladığımız Nesne Yönelimli Yazılım Desenleri yazı dizimize yaratıcı desenler grubundan Kurucu Desen ile devam edelim.

Kurucu desen daha önce incelediğimiz Fabrika ve Soyut Fabrika desenlerine benzeyen ve yaratımı karmaşık veya birçok dış etkene bağlı olan nesnelerin yaratımında bize yardımcı olabilecek bir yazlım desenidir. Kurucu desen özellikle bir çok parçadan oluşan ve şartlara göre değişik biçimlerde yaratılması gereken nesnelerin kuruluşunu adımlara bölerek basitleştirmek ve daha anlaşılır bir biçimde düzenlemekten ibarettir. Kurucu desende yer alan yapıları şu şekilde tanımlayabiliriz: Yaratımını basitleştirmek istediğimiz yapı KarmaşıkYapı olsun. Bu yapının yaratımında takip edilmesi gereken kuralları tespit eden ve soyutlayan bir SoyutKurucu yapısını, ve bu soyut yapıdan kalıt alan SomutKurucu’lar düşünelim. Ve son olarak da bütün bu yapıları koordine edecek ve kullanıcı kodlara KarmaşıkYapı nesneleri sağlayacak bir Direktör yapısı içerir kurucu desen. Aşağıdaki sembolik sınıflar kurucu desenin bu teorik tanımının daha somut bir biçimde ifade edilmiş halidir.

class KarmaşıkYapı { //bir çok parçadan oluşan yapı
Parça1 parça1; Parça2 parça2;

ParçaN parçaN;
}
abstract class SoyutKurucu { KarmaşıkYapı karmaşıkYapı;
abstract void Parça1Oluştur();

abstract void ParçaNOluştur();
}
class SomutKurucu1 : SoyutKurucu { override void parça1Oluştur() {…}

override void parçaNOluştur() {…}
}
class SomutKurucu2 : SoyutKurucu { override void Parça1Oluştur() {…}

override void ParçaNOluştur() {…}
}
class Direktör { SoyutKurucu soyutKurucu;
void KurucuAta(SoyutKurucu kurucu) { SoyutKurucu = kurucu; }
KarmaşıkYapı KarmaşıkYapıKur() { kurucu.Parça1Oluştur();

kurucu.ParçaNOluştur(); }
}
class Program { //kullanıcı kod static void Main() { Direktör direktör = new Direktör();
SoyukKurucu kurucu = new SomutKurucu1();
direktör.KurucuAta(kurucu);
KarmaşıkYapı ky = direktör.KarmaşıkYapıKur();
} }

Şimdi kurucu desenini uygulamalarımızda nasıl kullanabiliriz ve bize ne gibi faydalar getirir bunu adım adım inceleyelim.

Bir sipariş otomasyon sistemine ait aşağıda basitleştirilmiş halleri verilen sınıfları düşünelim.

public class Ürün
{
  public string Ad { get; set; }
  public string Kategori { get; set; }
  public double BirimFiyat { get; set; }
}

public class Sipariş
{
  public bool Toptan { get; set; }
  public Ürün Ürün { get; set; }
  public int Adet { get; set; }
  public bool NakliyatGerekli { get; set; }
  public double NormalTutar { get; set; }
  public double EsasToplam { get; set; }
}

Bu otomasyona ait karmaşık bir “toplam tutar hesaplama” işlevi düşünelim. İndirim ve ekstra ücretleri de içeren EsasToplam hesabı aşağıdaki faktörlere bağlı olsun:

  • Sipariş toptan mı? Perakende mi?
  • Toptan ve perakende indirim bantları ve oranları nelerdir?
  • Sipariş edilen ürün hangi kategoride? (Promosyon, ihraç fazlası vs.)
  • Kategori indirim oranları nelerdir? (Promosyon: %10, İhraç Fazlası: %30 vs.)
  • Nakliyat gerekli mi?
  • Nakliyat ücret oranları nelerdir?

Uygulamamızda bu faktörlere bağlı olarak toplam tutar hesabını yapmamız gerekecektir. Mesela, toptan ürünler için indirim bantları 1000 adet ve üzeri %30 indirim, 500-999 adet için %20 indirim şeklinde iken perakende siparişler için indirim bantları tamamen farklı olabilecektir. Aynı şekilde toptan ve perakende siparişlere uygulanması gerekli kategori indirim oranları tamamen farklı olacaktır. Peki bütün bu faktörleri dikkate alacak hesabı yapacak mantığı nerede ve nasıl oluşturmalıyız? Bu mantığı programın ana akışı içerisine yerleştirmek pek tabii ki bir çılgınlık olacaktır. Bu mantığın Sipariş sınıfı içine yerleştirilmesi de mantığın karmaşıklığından dolayı çok iyi bir fikir olmayacaktır. Bu hem Sipariş sınıfını kirletecek hem de gereksiz bir bağımlılık yaratacaktır. Toplam tutar hesabını yapacak yapıyı ayrı bir sınıf şeklinde aşağıdaki gibi tanımlayalım.


public class ToplamHesabı
{
  public Dictionary VolümİndirimOranları { get; set; }
  public double NakliyatOranı { get; set; } public Dictionary KategoriOranları { get; set; }

  public void ToplamHesapla(Sipariş sipariş)
  {
   sipariş.NormalTutar = sipariş.Adet * sipariş.Ürün.BirimFiyat;
   sipariş.EsasToplam = sipariş.Adet * sipariş.Ürün.BirimFiyat;

   foreach (var oran in VolümİndirimOranları)
   {
    if (sipariş.Adet >= oran.Key)
    {//volüm indirim bandını tespit et ve indirimi uygula
     sipariş.EsasToplam = sipariş.EsasToplam - (sipariş.EsasToplam * oran.Value);
     break;
    }
   }
  //nakliyat gerekliyse nakliye ücreti ekle
  if (sipariş.NakliyatGerekli)
  sipariş.EsasToplam = sipariş.EsasToplam + (sipariş.EsasToplam * NakliyatOranı);
  //kategori indirimi uygula
  sipariş.EsasToplam = sipariş.EsasToplam - (sipariş.EsasToplam * KategoriOranları[sipariş.Ürün.Kategori]);
  }
}

Bu yapı üzerinde tanımladığımız VolümİndirimOranları, UlaştırmaOranı ve KategoriOranları alanları ile gerekli indirim ve ekstra ücret oranlarını siparişlere uygulayabilir ve ToplamHesapla metodunda da görülebileceği gibi sipariş nesnelerinin NormalTutar ve EsasToplam alanlarını doldurabiliriz. Ancak ToplamHesabı nesnelerinin yaratılması basitleştirilmiş örneğimizde bile oldukça karmaşık olacaktır. Ve siparişin toptan veya perakende olmasına göre de değişecektir. Böyle bir durumda yapılması gereken bu mantığı merkezi bir noktaya toplamaktır. Bir önceki sayımızda incelediğimiz fabrika desenini takip ederek ToplamHesabı nesnelerini bir fabrika sınıfı aracılığıyla yaratabiliriz. Aşağıdaki sembolik sınıf böyle bir fabrika yapısına bir örnektir.

class ToplamHesabıFabrika {
 static ToplamHesabı Yarat(string tip) {
  if (tip == “toptan”) { 
    ToplamHesabı toplamHesabı = new ToplamHesabı();
    toplamHesabı.VolümİndirimOranları = …
    toplamHesabı.NakliyatOranı = …
    toplamHesabı.KategoriOranları = …
    return toplamHesabı;
  }
  else if (tip == “perakende”) {
    ToplamHesabı toplamHesabı = new ToplamHesabı();
    toplamHesabı.VolümİndirimOranları = …
    toplamHesabı.NakliyatOranı = …
    toplamHesabı.KategoriOranları = …
    return toplamHesabı;
   }
  }
}

Bu bir dereceye kadar etkili bir çözüm olacaktır. Ancak toptan/perakende gibi koşul sayısının artması ve nesne yaratımının karmaşıklaşmasıyla fabrika sınıfımız da karmaşık bir hale gelecek anlaşılması zorlaşacak ve sonuç olarak sürdürülebilirliği azalacaktır. Kurucu desen bize bu noktada bir seviye daha soyutlama yaparak kodumuzu daha düzenli ve modüler hale getirebilmemizi sağlayacaktır.

Şimdi elimizdeki senaryoya kurucu desen prensiplerini uygulayarak kurucu deseni yakından inceleyelim. İlk yapmamız gereken karmaşık yapımızın yaratımında takip edilmesi gereken kuralları tespit eden ve soyutlayan bir yapı tanımlamak:

public abstract class ToplamHesabıKurucusu
{
   protected ToplamHesabı toplamHesabı = new ToplamHesabı();
   //somut yapılar tarafından oluşturacak soyut metotlar:
   public abstract void   VolümOranlarıOluştur();
   public abstract void NakliyatOranıOluştur();
   public abstract void KategoriOranlarıOluştur();

   public ToplamHesabı ToplamHesabıNesnesi
   {
    get { return toplamHesabı; }
   }
}

Şimdi bu soyut yapıdan kalıt alan toptan ve perakende siparişlerin tutar hesabında kullanılacak somut yapıları tanımlayalım.

public class Toptan_ToplamHesabıKurucusu : ToplamHesabıKurucusu
{
   public override void VolümOranlarıOluştur() {
     Dictionary oranlar = new Dictionary();
     oranlar.Add(100, 0.15); //100-499 adet için %15 indirim
     oranlar.Add(500, 0.25); //500-999 adet için %25 indirim
     oranlar.Add(1000, 0.35); //1000 adet ve üzeri için alana %35 indirim
     base.toplamHesabı.VolümİndirimOranları = oranlar;
   }

   public override void NakliyatOranıOluştur() {
     double oran = 0.015; //%1,5 nakliyat ücreti
     ToplamHesabı.NakliyatOranı = oran;
   }

   public override void KategoriOranlarıOluştur() {
     Dictionary oranlar = new Dictionary();
     oranlar.Add("PROMOSYON", 0.2); //%20 indirim
     oranlar.Add("IHRAC_FAZLASI", 0.35); //%35 indirim
     base.toplamHesabı.KategoriOranları = oranlar;
   }
}


public class Perakende_ToplamHesabıKurucusu : ToplamHesabıKurucusu
{
   public override void VolümOranlarıOluştur() {
     Dictionary oranlar = new Dictionary();
     oranlar.Add(5, 0.10); //5 adet ve üzeri için %10 indirim
     base.toplamHesabı.VolümİndirimOranları = oranlar;
   }

   public override void NakliyatOranıOluştur() {
     double oran = 0.1; //%10 nakliyat ücreti
     base.toplamHesabı.NakliyatOranı = oran;
   }

   public override void KategoriOranlarıOluştur() {
     Dictionary oranlar = new Dictionary();
     oranlar.Add("PROMOSYON", 0.15); //%15 promosyon indirimi
     base.toplamHesabı.KategoriOranları = oranlar;
   }
}

Kurucu yapılarımızı tanımladıktan sonra sembolik olarak ifade ettiğimiz fabrika sınıfımızın yerine geçecek aşağıdaki direktör sınıfını tanımlayalım.

public class Direktör
{
   private ToplamHesabıKurucusu kurucu;

   public Direktör(ToplamHesabıKurucusu siparişKurucu)
   {
     kurucu = siparişKurucu;
   }
   public ToplamHesabı ToplamHesabiKur()
   {
     kurucu.VolümOranlarıOluştur();
     kurucu.NakliyatOranıOluştur();
     kurucu.KategoriOranlarıOluştur();
    return kurucu.ToplamHesabıNesnesi;
   }
}

Şimdi bir sipariş nesnesi için tutar hesabını aşağıdaki gibi gerçekleştirebiliriz.

Direktör direktör;
if (sipariş.Toptan)
   direktör = new Direktör(new Toptan_ToplamHesabıKurucusu());
else
   direktör = new Direktör(new Perakende_ToplamHesabıKurucusu());

ToplamHesabı th = direktör.ToplamHesabiKur();
th.ToplamHesapla(sipariş);

Fabrika ve Soyut Fabrika desenlerine benzeyen ve fabrika yapılarının aşırı karmaşıklaşmasını önlemek için ortaya çıkmış bir tasarım deseni olan Kurucu Deseni ve bu deseni oluşturan unsurları inceledik. Kurucu desen küçük ölçekli uygulamalardan ziyade büyük ölçekli projelerde yaratımı karmaşık olan nesnelerin yaratımına bir düzen ve modülerlik kazandırması açısından oldukça faydalı olabilecek bir desendir.

Yorum, istek ve düşüncelerinizi lütfen bizimle paylaşın.




Yorumlar


Bu makaleye ait yorum bulunamadı.

Sizin yorumunuz

Email


Adınız







F5 Dergi © 2017