Veysel Uğur KIZMAZ

Chain of Responsibility Design Pattern

10.03.2013Okunma Sayısı: 7459Kategori: Yazılım Mühendisliği

Tasarım desenleri arasında ilk inceleyeceğimiz desen, Behavioral Patterns kategorisinde yer alan Chain of Responsibility desenidir.

Chain of Responsibility, Türkçe’de Sorumluluk Zinciri olarak geçmektedir. Bir işlemi yapabilecek birden fazla sınıftan hangisinin yapacağına karar veren tasarım desenidir. Bu konuda en güzel örnek, günlük hayatımızda kullandığımız bankamatik (otomata) sistemleridir.
 
Senaryo: Bankamatiğe para çekmeye gittiğinizde tutarı 1385 TL olarak yazdığınızı ve bankamatik içerisinde 200, 100, 50, 20, 10 ve 5 TL’lik banknotlardan yeterli miktarda olduğunu varsayalım. Makine banknotları minimum sayıda verebilmek için şu yöntemi izlemelidir:
 
  • 6 adet 200 TL
  • 1 adet 100 TL
  • 1 adet 50 TL
  • 3 adet 10 TL
  • 1 adet 5 TL
Bunun kodlamasını nasıl yapacağınızı düşündüğünüzde ilk aklınıza gelen şu olmuştur: Bir while ve birkaç if ile yapılabilir. Bu yöntemle deneyelim.
 
public class Banknot
{
    public int Adet { get; set; }
    public int Tutar { get; set; }
}
 
static void Main(string[] args)
{
    int tutar = 1385;
    List<Banknot> banknotlar = newList<Banknot>();
    if (tutar / 200 > 0)
    {
        banknotlar.Add(new Banknot
        {
            Adet = tutar / 200,
            Tutar = 200
        });
        tutar = tutar % 200;
    }
    if (tutar / 100 > 0)
    {
        banknotlar.Add(new Banknot
        {
            Adet = tutar / 100,
            Tutar = 100
        });
        tutar = tutar % 100;
    }
    if (tutar / 50 > 0)
    {
        banknotlar.Add(new Banknot
        {
            Adet = tutar / 50,
            Tutar = 50
        });
        tutar = tutar % 50;
    }
    if (tutar / 20 > 0)
    {
        banknotlar.Add(new Banknot
        {
            Adet = tutar / 20,
            Tutar = 20
        });
        tutar = tutar % 20;
    }
    if (tutar / 10 > 0)
    {
        banknotlar.Add(new Banknot
        {
            Adet = tutar / 10,
            Tutar = 10
        });
        tutar = tutar % 10;
    }
    if (tutar / 5 > 0)
    {
        banknotlar.Add(new Banknot
        {
            Adet = tutar / 5,
            Tutar = 5
        });
        tutar = tutar % 5;
    }
 
    foreach (Banknot b in banknotlar)
    {
        Console.WriteLine("Tutar: " + b.Tutar + "\tAdet: " + b.Adet);
    }
 
    Console.ReadKey();
}
 
Projeyi çalıştırdığımızda istediğimiz sonucu elde edebiliyoruz.
 
Chain of Repsonsibility Örnek Uygulama Ekran Çıktısı
 
İsterseniz if blokları içerisindeki kodlamayı bir metoda alıp parametre göndererek de işlemleri yapabilirsiniz.
 
Bu kodlama doğru çalışsa da yazılım geliştirme sırasında araya farklı kontrollerin ve işlemlerin geleceği düşünüldüğünde bir süre sonra kodlar karmaşık bir hale gelebilir. Örneğin şu senaryolar eklenebilir:
 
  • 200 TL sayısı 50’nin altına inmişse 1000 TL’den büyük tutarlarda 2 tane 200 TL’den sonra 100 TL’leri ver.
  • 200 TL sayısı 20’nin ve 100 TL sayısı 30’un altına inmişse ve 50 TL sayısı 150’nin üstünde ise 500 TL ile 1500 TL arasındaki tutarlarda 1 tane 200 TL 4 tane 100 TL’den sonra 50 TL’leri ver.
  • 200 TL sayısı 5’in, 100 TL sayısı 10’un altına inmişse ve 50 TL sayısı 100’ün üstünde, 20 TL sayısı 250’den fazla ise 200 TL ile 1000 TL arasındaki tutarlarda 200 TL verme, 1 tane 100 TL’den sonra  50 TL sayısının en fazla 10’da 1’i kadar 50 TL ver, sonra kalan bölümde 20 TL’leri ver.
Bu ve bunun gibi yüzlerce senaryo yazılması istenebilir. Eğer bunlar if blokları içerisine yazılırsa tek metod içerisinde çok karmaşık kodlar ortaya çıkacaktır ve yönetimi oldukça zor olacaktır.
 
Kodlamada karmaşıklığı önlemek için Chain of Responsibility (sorumluluk zinciri) tasarım deseni oluşturulmuştur. Örnek üzerinden ilerlersek; her banknot için bir sınıf oluşturulacak, bu sınıflar içerisinde sadece banknota özgü işlemler yapılacak, çağırıldığı yerde (bankamatikte) ek bir kod yazılmadan banknotların hiyerarşisi (hangi sırayla işlem yapacağı) birbirine bağlanacak ve tutar girilerek hangi banknottan kaç adet verilmesi gerektiği sonucu alınacaktır.
 
Öncelikle projenin sınıf diagramını inceleyelim.
 
Chain of Repsonsibility Class Diagram
 
200 TL’lik banknotlar için Para200 sınıfı, 100 TL’lik banknotlar için Para100 sınıfı, 50 TL’lik banknotlar için Para50 sınıfı, 20 TL’lik banknotlar için Para20 sınıfı, 10 TL’lik banknotlar için Para10 sınıfı, 5 TL’lik banknotlar için Para5 sınıfı oluşturulmuştur. Tüm sınıflar Banknot isimli abstract sınıftan kalıtım almıştır. Öncelikle en temel sınıf olan hesap sınıfını inceleyelim.
 
abstract class Banknot
{
    protected Banknot _banknot;
    public void Sonraki(Banknot hesap)
    {
        this._banknot = hesap;
    }
    public abstract Miktar ParaCek(int tutar);
}
 
  • _banknot değişkeninde, bir sonraki banknot bilgileri tutulmaktadır. İlk aşamada 200 TL’lik banknotlar, onrasında 100 TL’lik banknotlar kontrol edilecektir. 200 TL’den sonra 100 TL’lik banknotların kontrol edilmesi tanımlamasını Sonraki isimli metod ile gerçekleştireceğiz. Bu yöntemle 200’den sonra 100, 100’den sonra 50, 50’den sonra 20, 20’den sonra 10, 10’dan sonra 5 TL’lik banknotların kontrol edileceğini tanımlayacağız. Dikkat ettiyseniz yapı bir zincir gibi ilerliyor (adına uygun şekilde).
  • ParaCek isimli metod ile de para çekme işlemini gerçekleştireceğiz. Girilen tutar, ilgili banknotta varsa bir miktar değeri döndürecek (hangi banknottan kaç adet verilecek ve bu işlemden sonra elde ne kadar para kaldı). Örneğin 1385 TL çekilecek ise öncelikle 200 TL’lik banknotlara bakacak ve 6 tane 200 TL’lik banknot verileceğini ve verildikten sonra 185 TL’nin kaldığını belirtecek bize. Sonraki adımlarda diğer banknotlardan kaç tane verileceğini tanımlamak için kalan 185 TL’yi işleme koyacağız.

 

public class Miktar
{
    publicint Adet { get; set; }
    publicint Kalan { get; set; }
    publicint Tutar { get; set; }
}
 
Şimdi ilk banknotumuz olan 200 TL’lik banknot için Para200 sınıfını oluşturalım.
 
Para200.cs
class Para200 : Banknot
{
    public override Miktar ParaCek(int tutar)
    {
        if (tutar >= 200)
        {
            return new Miktar
            {
                Adet = tutar / 200,
                Kalan = tutar % 200,
                Tutar = 200
            };
        }
        else
        {
            return _banknot.ParaCek(tutar);
        }
    }
}
 
  • Para200 sınıfımızda, Banknot sınıfının kalıtımını aldık. Bu sayede hem ParaCek metodunu standarda uygun oluşturduk hem de Sonraki metodunu tekrar oluşturmak zorunda kalmadan Banknot sınıfının kalıtımı sayesinde kullanabileceğiz.
  • ParaCek metodunda eğer tutar (1385 TL) 200 TL’den büyük ise tutar / 200 adet (Adet = 6) 200 TL’lik (Tutar = 200) banknotlardan vereceğimizi ve kalan tutarın tutar % 200 (Kalan = 185 TL) kadar olduğunu geri döndürüyoruz. Eğer tutar 200 TL’nin altında ise bir sonraki banknota (100 TL’ye) bakmasını belirtiyoruz.
 
Benzer şekilde Para100, Para50, Para20, Para10, Para5 sınıflarını da oluşturalım.
 
Para100.cs
class Para100 : Banknot
{
    public override Miktar ParaCek(int tutar)
    {
        if (tutar >= 100)
        {
            return new Miktar
            {
                Adet = tutar / 100,
                Kalan = tutar % 100,
                Tutar = 100
            };
        }
        else
        {
            return _banknot.ParaCek(tutar);
        }
    }
}
 
Para50.cs
class Para50 : Banknot
{
    public override Miktar ParaCek(int tutar)
    {
        if (tutar >= 50)
        {
            return new Miktar
            {
                Adet = tutar / 50,
                Kalan = tutar % 50,
                Tutar = 50
            };
        }
        else
        {
            return _banknot.ParaCek(tutar);
        }
    }
}
 
Para20.cs
class Para20 : Banknot
{
    public override Miktar ParaCek(int tutar)
    {
        if (tutar >= 20)
        {
            return new Miktar
            {
                Adet = tutar / 20,
                Kalan = tutar % 20,
                Tutar = 20
            };
        }
        else
        {
            return _banknot.ParaCek(tutar);
        }
    }
}
 
Para10.cs
classPara10 : Banknot
{
    public override Miktar ParaCek(int tutar)
    {
        if (tutar >= 10)
        {
            return new Miktar
            {
                Adet = tutar / 10,
                Kalan = tutar % 10,
                Tutar = 10
            };
        }
        else
        {
            return _banknot.ParaCek(tutar);
        }
    }
}
 
Para5.cs
classPara5 : Banknot
{
    public override Miktar ParaCek(int tutar)
    {
        if (tutar >= 5)
        {
            returnnewMiktar
            {
                Adet = tutar / 5,
                Kalan = tutar % 5,
                Tutar = 5
            };
        }
        else
        {
            return _banknot.ParaCek(tutar);
        }
    }
}
 
Şimdi para çekme işlemini yapacağımız Bankamatik isimli sınıfı oluşturalım. Bu sınıfta hangi banknottan sonra hangi banknotun işleme gireceğini (zincir tanımlaması) ve para çekme işlemini yapacağız.
 
publicclassBankamatik
{
    Para200 _200 = new Para200();
    Para100 _100 = new Para100();
    Para50 _50 = new Para50();
    Para20 _20 = new Para20();
    Para10 _10 = new Para10();
    Para5 _5 = new Para5();
    Para1 _1 = new Para1();
 
    public List<Miktar> ParaCek(int tutar)
    {
        Console.WriteLine("Toplam Tutar: " + tutar);
 
        _200.Sonraki(_100);
        _100.Sonraki(_50);
        _50.Sonraki(_20);
        _20.Sonraki(_10);
        _10.Sonraki(_5);
 
        Miktar sonuc = newMiktar();
        List<Miktar> sonuclar = newList<Miktar>();
 
        do
        {
            sonuclar.Add(sonuc = _200.ParaCek(tutar));
            tutar = sonuc.Kalan;
        } while (sonuc.Kalan > 0);
 
        foreach (var s in sonuclar)
        {
            Console.WriteLine("Tutar: " + s.Tutar + "\tAdet: " + s.Adet);
        }
 
        returnnull;
    }
}
 
  • Bankamatik sınıfında global alanda Para200, Para100, Para50, Para20, Para10, Para5 değişkenlerini oluşturuyoruz (_200, _100, _50, _20, _10, _5).
  • ParaCek isimli metod içerisinde para çekme işlemini gerçekleştireceğiz. Birden fazla banknot türü verileceği için geri dönüş türünü list (List) olarak tanımladık.
  • _200.Sonraki(_100) ve diğer Sonraki metodlarının çağırıldığı kodlarda, zincir halkaları birbirine bağlanmaktadır. Yani,
    •  _200.Sonraki(_100) ile 200 TL’lik banknotlardan sonra 100 TL’lik banknotlara bakılacağı,
    • _100.Sonraki(_50) ile 100 TL’lik banknotlardan sonra 50 TL’lik banknotlara bakılacağı,
    • _50.Sonraki(_20) ile 50 TL’lik banknotlardan sonra 20 TL’lik banknotlara bakılacağı,
    • _20.Sonraki(_10) ile 20 TL’lik banknotlardan sonra 10 TL’lik banknotlara bakılacağı,
    • _10.Sonraki(_50) ile 10 TL’lik banknotlardan sonra 5 TL’lik banknotlara bakılacağı tanımlaması yapılmaktadır.
  • sonuc isimli değişkende, her bakılan banknot türünden sonra geri dönen değerler (kalan, adet ve tutar değerleri) yeniden işleme girmek için tutulmaktadır.
  • sonuclar isimli değişkende, belirtilen tutarın hangi banknotlarla verileceği bilgileri tutulmaktadır (işlemler tamamlandığında ekranda göstereceğimiz bilgiler).
  • do – while döngüsü ile tutar (ya da işleme girmiş ise kalan tutar) 0 TL’den büyük olduğu sürece yeniden işleme girmesi sağlanmaktadır. Kalan tutar 0 TL olduğu zaman döngüden çıkacak ve hangi banknottan kaç adet verilmesi gerektiği sonuclar isimli değişkende yer alacaktır.
  • sonuclar.Add metodu ile kalan tutarın işleme alınması sonucunda ilgili banknottan kaç adet verileceği bilgisi sonuclar değişkenine eklenmektedir.1
  • tutar = sonuc.Kalan işlemi ile, kalan miktarı tutar değişkenine atıyoruz ve bir sonraki döngüde bu tutarı işleme alıyoruz. Örneğin 1385 TL işleme alındığında 6 tane 200 TL banknot verileceği hesaplanacaktır. Kalan 185 TL tutar değişkenine atanarak tekrar işleme alınacak ve 1 tane 100 TL banknot verileceği hesaplanacaktır. Bu işlem sonucunda kalan 85 TL tutar değişkenine atanacak ve tekrar işleme alınacaktır. Bu şekilde tutar değişkeninin değeri (kalan değer) 0 TL olana kadar işlemler devam edecektir.
  • Son aşamada ise foreach döngüsü ile banknot değerlerini ekranda görüntülüyoruz.
İşlemlerin daha iyi anlaşılması için aşağıda algoritmanın flowchart diagramı yer almaktadır.
 
Chain of Repsonsibility FlowChart - Akış Diagramı
 
Main metodundan Bankamatik sınıfındaki ParaCek metodunu çağıralım ve parametre olarak makalenin başından beri kullandığımız 1385 TL’yi gönderelim.
 
staticvoid Main(string[] args)
{
    Bankamatik b = newBankamatik();
    b.ParaCek(1385);
 
    Console.ReadKey();
}
 
Projeyi çalıştırdığımızda ilk yaptığımız işlem sonucunun aynısını alacağız.
 
Chain of Repsonsibility Örnek Uygulama Ekran Çıktısı
 
Yepyeni makalelerde görüşmek dileğiyle :)
 
Veysel Uğur KIZMAZ
Bilgisayar Mühendisi
veysel@ugurkizmaz.com
www.ugurkizmaz.com