Chain Of Responsibility Design Pattern
Chain of responsibiliy yani sorumluluk zinciri tasarım kalıbı, effektif ve gevşek bağlı bir şekilde onay akışı tasarımı yapmamızı sağlar. Chain of responsibility patterni en iyi örnek üzerinden anlayabiliriz. Bir şirket çalışanı olarak şirketten ihtiyaç avansı talebinde bulunmanız gerekiyor. 10.000 TL civarı bir miktar talep ediyorsunuz ve bu talep bir onay akışına giriş yapıyor. 2000 TL’ye kadar olan talepleri yöneticiniz handle ederken üstü miktarlar grup yöneticisine gidiyor. Grup yöneticisi 5000 TL’ye kadar olan talepleri handle edebilirken üstü talepler, direktör onayına gidiyor ve direktör uygun bulursa onaylıyor. Bu gibi onay akışını özellikle de onaycıları if else bloklarıyla kodlamaya kalkarsanız tamamen değişime kapalı ve tightly coupled bir sistem elde etmiş oluyorsunuz ve yeni bir onaycının sisteme dahil olması gerektiğinde ya da onaycıların sırasının değişmesi gerektiğinde kod değişikliklerine gitmeniz gerekebiliyor. Bu gibi onay akışı caselerini chain of responsilibilty pattern ile efektif bir şekilde kodlayabiliriz.
Chain of responbilty pattern bize her onaycının kendi içerisinde kendinden bir sonraki onaycıyı tutması gerektiğini ve bu tutacağı onaycılara gevşek bağlı(loosely coupled) olmamız gerektiğini yani bunları inject etmemiz gerektiğini söyler.
Aşağıda Calisan classında parent olmak üzere abstract bir calisan classı tanımlanmıştır ve bu Calisan classı kendi içerisinde kendinden sonra onay verecek diğer calisanı NextApprover olarak tutmaktadır.
public abstract class Calisan { protected Calisan SıradakiOnayci; public void SiradakiOnayciyiSetEt(Calisan employee) { this.SıradakiOnayci= employee; } public abstract void ProcessRequest(Withdraw req); }
Direktor classı onay akışındaki son onaycıdır. Dolayısıyla kendinden sonra bir onaycı yoktur ve 750000 Tlye kadar olan talepleri handle edebilir.
public class Direktor : Calisan { 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ı."); } } }
Grup yoneticisi 150000 Tlye kadar olan talepleri handle edebilir. Üstü talepleri kendinden sonraki onaycıya gönderir.
public class GrupYoneticisi : Calisan { 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 (SıradakiOnayci!= 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); SıradakiOnayci.ProcessRequest(req); } } }
Yönetici 70000 Tlye kadar olan talepleri handle edebilir. Üstü talepleri kendinden sonraki onaycıya gönderir.
public class Yonetici : Calisan { 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 (SıradakiOnayci!= 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); SıradakiOnayci.ProcessRequest(req); } } }
Sorumlu 40000Tlye kadar olan talepleri handle edebilir. Üstü talepleri kendinden sonraki onaycıya gönderir.
public class Sorumlu : Calisan { 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 (SıradakiOnayci!= 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); SıradakiOnayci.ProcessRequest(req); } } }
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; } }
Aşağıda onay akışı için oluşturduğumuz onaycıları bir diğer onaycıya nextApprover olarak inject ediyoruz. İşin güzelliği de tam burada. Hiç biri birbirine sıkı sıkıya bağlı değil ve akış çok kolay bir şekilde değiştirilebilir durumda.
class Program { static void Main(string[] args) { var withdraw = new Withdraw("a6e193dc-cdbb-4f09-af1a-dea307a9ed15", 480000, "TRY", "TR681223154132432141412"); Calisan sorumlu = new Sorumlu(); Calisan yonetici = new Yonetici(); Calisan grupYoneticisi = new GrupYoneticisi(); Calisan direktor = new Direktor(); sorumlu.SiradakiOnayciyiSetEt(yonetici); yonetici.SiradakiOnayciyiSetEt(grupYoneticisi); grupYoneticisi.SiradakiOnayciyiSetEt(direktor); sorumlu.ProcessRequest(withdraw); Console.ReadKey(); } }