Chain of Responsibility pattern behavioral patterns gurubuna ait olan ve özünde birbirini takip eden iş dizisine ait process'leri redirect ve handle etmek yada istekte bulunan-confirm eden süreçleri için çözüm olarak ortaya çıkmış bir tasarım desendir.
Yukarıda tanım yaparken birbirini takip eden iş dizesinden kasıt birbirlerine Loosly Coupled bir şekilde zincir gibi bağlı olan süreçleri process etmek için kullanabileceğimiz bir pattern dir.
Bir örnek ile ele alacak olursak; veznede çalışan bir kişi için günlük nakit para çekim miktarı 40 bin TL olan bir banka düşünelim ve bu bankaya gelen bir müşteri veznede bulunan kişiden 240 bin TL para çekmek istediğini söyledi. Banka kuralları gereği bu işlemin sırasıyla veznedar, yönetici, müdür ve bölge sorumlusu tarafından sırasıyla onaylaması gerekmekte. Bakacak olduğumuzda zincir şeklinde birbirine bağlı olan bir onay yapısı bulunmakta.
Akış olarak özetleyecek olursak;
- Müşteri 480 bin TL lik para çekme isteğini veznedar'a iletir.
- Veznedar bu isteği alır ve kontrol eder eğer onaylayabileceği bir tutar ise onaylar, onaylayabileceği bir tutar değilse yöneticisine gönderir,
- Yönetici isteği alır onaylayabileceği bir tutar değilse müdüre iletir,
- Müdür kontrol eder eğer onaylayabileceği bir tutar değilse bölge sorumlusunun onayına gönderir,
- Bölge sorumlusu onaylar ve para müşteriye verilir.
Yukarıda bahsettiğimiz bu örneğimizi Chain of Responsibility pattern uygulayarak geliştirelim.
İlk olarak Withdraw adında domain model tanımlayalım.
public class Withdraw
{
public string CustomerId { get; }
public decimal Amount { get; }
public string CurrencyType { get; }
public string SoruceAccountId { get; }
public Withdraw(string customerId, decimal amount, string currencyType, string soruceAccountId)
{
CustomerId = customerId;
Amount = amount;
CurrencyType = currencyType;
SoruceAccountId = soruceAccountId;
}
}
Sornasında abstract bir Employee sınıfı tanımlayalım ve içerisinde aşağıdaki gibi property'lerinı yazalım.
public abstract class Employee
{
protected Employee NextApprover;
public void SetNextApprover(Employee supervisor)
{
this.NextApprover = supervisor;
}
public abstract void ProcessRequest(Withdraw req);
}
Yukarıda bulunan NextApprover isimli property o sınıfa ait kişinin yöneticisi olarak atanan kişidir ve ProcessRequest metodunda ilgili condition'ı yazıp sırasıyla NextApprover'ları call edeceğiz.
Veznedar, Yonetici, Mudur ve BolgeSorumlusu sınıfları yukarıda tanımladığımız Employee sınıfından inherit olacak şekilde aşağıdaki gibi oluşturalım.
public class Veznedar : Employee
{
public override void ProcessRequest(Withdraw req)
{
if (req.Amount <= 40000)
{
Console.WriteLine("{0} tarafından para çekme işlemi onaylandı #{1}",
this.GetType().Name, req.Amount);
}
else if (NextApprover != null)
{
Console.WriteLine("{0} TL işlem tutarı {1} max. limitini aştığından işlem yöneticiye gönderildi.", req.Amount, this.GetType().Name);
NextApprover.ProcessRequest(req);
}
}
}
public class Yonetici : Employee
{
public override void ProcessRequest(Withdraw req)
{
if (req.Amount <= 70000)
{
Console.WriteLine("{0} tarafından para çekme işlemi onaylandı #{1} TL",
this.GetType().Name, req.Amount);
}
else if (NextApprover != null)
{
Console.WriteLine("{0} TL işlem tutarı {1} max. limitini aştığından işlem yöneticiye gönderildi.", req.Amount, this.GetType().Name);
NextApprover.ProcessRequest(req);
}
}
}
public class Mudur : Employee
{
public override void ProcessRequest(Withdraw req)
{
if (req.Amount <= 150000)
{
Console.WriteLine("{0} tarafından para çekme işlemi onaylandı #{1} TL",
this.GetType().Name, req.Amount);
}
else if (NextApprover != null)
{
Console.WriteLine("{0} TL işlem tutarı {1} max. limitini aştığından işlem yöneticiye gönderildi.", req.Amount, this.GetType().Name);
NextApprover.ProcessRequest(req);
}
}
}
public class BolgeSorumlusu : Employee
{
public override void ProcessRequest(Withdraw req)
{
if (req.Amount <= 750000)
{
Console.WriteLine("{0} tarafından para çekme işlemi onaylandı #{1} TL",
this.GetType().Name, req.Amount);
}
else
{
throw new Exception(
$"Limit banka günlük işlem limitini aştığından para çekme işlemi #{req.Amount} TL onaylanmadı.");
}
}
}
Son olarak ise domain modeli initialize edip chain'i oluşturup process metodunu call edelim.
static void Main(string[] args)
{
var withdraw = new Withdraw("a6e193dc-cdbb-4f09-af1a-dea307a9ed15", 480000, "TRY", "TR681223154132432141412");
Employee veznedar = new Veznedar();
Employee yonetici = new Yonetici();
Employee mudur = new Mudur();
Employee bolgeSorumlusu = new BolgeSorumlusu();
veznedar.SetNextApprover(yonetici);
yonetici.SetNextApprover(mudur);
mudur.SetNextApprover(bolgeSorumlusu);
withdraw.Process(veznedar);
Console.ReadKey();
}
Yukarıdaki gibi 480000 TL lik bir işlem için istekte bulunduğumuzda console çıktısı aşağıdaki gibi olacaktır.
İşlem sırasıyla veznedar, yönetici, müdür bölge sorumlusunun önünde düşecektir. Son olarak ise bölge sorumlusunun onaylayabileceği bir tutar olduğundan onay verip banka müşterisine ödeme işlemini yapacaktır.
Chain of Responsibility pattern bir chain halinde process edilmesi gereken operasyonlar için rahatlıkla kullanabileceğimiz bir pattern dir. Yazılım dünyasında kullanım olarak diğer desing pattern'lar arasında %30-%40 lık bir orana sahip olduğu iddia edilir ve sıkça kullanılmaktadır.