Problem :
Uygulamamızda görseldeki gibi birden fazla abonelik tipi olduğunu düşünelim. Müşterinin yaptığı seçimlere göre farklı hesaplamalar yapmamız gerekiyor.
Farklı abone tipleriyle beraber aynı zamanda aylık ya da yıllık seçimine görede değişken bir hesaplama olayı gittikçe karmaşık hale getiriyor.
İleride yeni bir paket gelebilir. Ya da paket üzerinde hesaplamalarda değişiklikler olabilir diyebiliriz. Premium hesap için ise müşterinin sistemde kaç adet kullanıcı yönetmek istediğini opsiyonel olarak seçtirdiğimiz diğer abonelik hesaplamalarında farklı bir yere gittiğini görüyoruz.
Çözüm :
Builder Pattern ile bu sorunu çözümleyeceğiz. Her bir abonelik tipi kendi özelinde hesaplama yapabilecek ancak hiç birisi direkt olarak cağırılmayacak. Abonelik hesaplarını yöneten bir direktörümüz olacak.
Direktörümüz göreve başlamadan önce ilgili builder ona atanacak ve içerisinde methodlarına erişim sağlayarak tüm abonelik tiplerini oluşturmamız bize liderlik edecek.
Böylece kod tarafında biz ne değer verecektir, ne hesaplayacaktık gibi konuları daha sağlıklı bir şekilde yöneteceğiz. Hem genişleyebilir hem de okunabilir bir tasarım olacak.
Projede bulunan sınıflardan biraz örnekler vererek ilerleyelim. Detaylı inceleme için yazı sonunda github repo linkini paylaşıyor olacağım.
İlk olarak “SubscriptionPackage” adında tüm oluşturucular tarafından kullanılacak modelimizi oluşturduk.
Bu model kendi içerisinde bulunan tüm özelliklerin değiştirebildiği methodlar içeriyor.
Görselde gördüğümüz “kullanıcı sayısı, depolama değeri, birim fiyatı” gibi değerlerin set edileceği ara yüzümüzü “IPackageBuilder” adında oluşturdum.
Abonelik sistemine dahil olan bir paket oluşturucu belirlediğimiz tüm methodları implemente etmesini zorunlu kılacağız.
Birden fazla abonelik yapımız olacağını düşünerek oluşturucular için temel method oluşturuyorum. Tüm oluşturucuların “BasePackageBuilder” üzerinden miras alarak oluşmasını sağladım.
Base method içerisinde set işlemleri yaparken aslında domain(SubscriptionPackage) üzerinde bulunan set methodlarını kullanıyorum. Böylece yine DDD kurallarına uyarak domainin kendi kurallarına kendisinin karar vermesine olanak sağlıyorum.
Her oluşturucu kendi içerisinde SubscriptionPackage alacak ve diretörün belirlediği şekilde değerleri set edecek. Her bir abonelik tipi için ayrı sınıf oluşturmadan da yapabilirdik ancak oluşturucular arasında ileriye yönelik farklılıklar olabilir.
Şuan için örneğimizde Premium abonelik tipinde kullanıcı sayısına göre farklı hesaplama olmasına rağmen diğerlerinde bu değer hep aynı. Bu tarz durumlarda esnek olmak adına ayrı sınıflar oluşturdum.
Her bir oluşturucu base sınıftan miras alacağı için sadece kendisine özgü farklı hesaplamalar için base sınıftan gelen methodları ezerek dilediği değeri set edilecek şekilde özgürde olacak.
Projeyi açtığınızda PremiumPackageBuilder dışında tüm sınıfların base sınıftan gelen methodlar dışında başka bir iş yapmadığını göreceksiniz. PremiumBuilder için ise kullanıcı sayısı değişimine göre farklı bir hesaplama yapmamız gerekiyor. Buradaki formüle takılmayın, ben kendim rastgele bir formül belirledim.
Her bir oluşturucu içerisinde base sınıftan gelen “SetUnitPrice” methodunu burada ezerek set unitPrice değerini değiştiriyoruz. Base sınıfa göz attığınız zaman “unitPrice*12” gibi bir method ile “totalPrice” değerini hesaplıyor.
Bizde haliyle “unitPrice” değerini değiştirdiğimizde “totalPrice” methodunda her hangi bir değişiklik yapmadan sürecimizi devam ettirebiliyoruz. Bu method özelinde de talep edilen kullanıcını sayısı 1000’e böldükten sonra var olan birim tutarı ile çarptım ve -1 yaparak işlemi tamamladım.
Dediğim gibi buradaki formüle hiç takılmayın. Bu abonelik sistemizde yaptığın analizlere göre değişkenlik gösterebilir. Sadece PremiumBuilder değil diğer tüm oluşturucular için farklı hesaplama yapabilirsiniz.
Son olarak abonelik paketlerinin varsayılan değerlerinin kim tarafından set edileceği yere geldik. Oluşturucu ben değer falan bilmem üzerime düşen görevi yaparım deyip işini bitirecek. Zaten böylede olmalı, ona gerekli değerleri “PackageDirector” verecek. Talep edilen abonelik paketi oluşturma konusunda da iletişimde olduğumuz tek sınıf olacak.
Direktör’e atadığımız builder sonrasında talep ettiğimiz method bizim sorumluluğumuzda. Direktör’e gidip “FreePackageBuilder” set edip, premium hesaplama istersek doğru sonuca ulaşamayız. Bu tarzda engellemeler yapmak istiyorsakda bunu yine direktör içerisinde karara bağlayabiliriz.
Örneğin “BuildFreeMonthlyPackage” methodu cağırıldığı ama gönderilen “IPackageBuilder” “FreePackageBuilder” değil ise exception patlatabiliriz. Bunun örneğini yapalım dersenizde lütfen iletişimde kalalım.
Oluşturuculara verdiğimiz “birim fiyatı, kullanıcı adedi” gibi değerler static. Bunları dinamik hale getirmemiz tabii gerekecektir. Şuan için örneklemede daha hızlı ilerlemek açısından böyle tercih ettim.
Ne gibi sonuçlar aldığımıza biraz göz atalım.
Talep : Aylık ödeme abonelik bilgileri
Talep : 5k, 10k ve 15k kullanıcılı premium abonelik paket bilgileri
Sonuç ekranında yıllık tutarların daha uygun olduğunu görebiliyoruz. Sayfanın başında bulunan görsele yakın değerler elde ettik.
Dediğim gibi formüller size kalmış.
Amaçladığımız sonuca ulaştık. BuilderPattern ile birden fazla abonelik paketi için hesaplama yapabiliyoruz.
Repo URL : https://github.com/gsmtcnr/DesignPattern.Creational.BuilderPattern
No Comments