State Design Pattern, nesnelerin durumlarını temsil etmek ve bu durumların değişimini yönetmek için kullanılır.
Shipment domain'ini düşünelim. En basit haliyle kargonun aşağıdaki gibi durumları olur.
- OutForDelivery
- Delivered
- NotDelivered
Eğer kargo yurt dışından geliyorsa bunlara aşağıdaki durumlar da eklenir.
- InTransit
- InCustoms
- InCustomsCleared
Bu durumlar ile ilgili business'ları düşünelim.
Birincisi; tüm durumlar sırayla işletilmeli. Örneğin; OutForDelivery yapılmadan Delivered veya NotDelivered yapılamamalı.
İkincisi; durumlar aynı durumdayken tekrar işletilememeli. Örneğin; Delivered durumundayken tekrar Delivered yapılamamalı.
Üçüncüsü; kargo yurt içinden geliyorsa InTransit, InCustoms ve InCustomsCleared yapılamamalı. Tam tersi bir kargo yurt dışından geliyorsa bu durumlar işletilmeli.
Dördüncüsü; kargo bir kereye mahsus NotDelivered yapıldıktan sonra Delivered yapılabilir. Sonrasında bir daha NotDelivered yapılırsa Delivered yapılamamalı.
Bu kuralların olduğu Shipment sınıfı aşağıdaki gibi olur.
Görüldüğü üzere; sınıf oldukça büyüdü, kompleksleşti ve geliştirme yapılması zorlaştı. Sonradan yeni durumlar eklendiğinde her tarafa dokunmak gerekecek.
State Design Pattern, her durumun ayrı bir sınıf olarak tasarlanması gerektiğini ve nesnenin durumuna göre davranışını değiştirmesi gerektiğini söyler. State, ConcreteState ve Context olmak üzere 3 öğeden oluşur.
Örneğimize göre; OrderPlacedState ve DeliveredState gibi durumlar ConcreteState öğesidir. ShipmentState ise State öğesidir. Yani state'lerin arayüzüdür. Shipment ise Context öğesidir. Yani state'lerin yönetildiği sınıftır. Mevcut state'i saklar ve bu state'i değiştirmek için State sınıfına yönlendirme yapar.
Kodlamaya state'lerin arayüzünü oluşturmakla başlayalım. Burada interface de tercih edilebilir abstract da. Ben abstract tercih edeceğim çünkü state'lerin ilgilenmediği metotları override etmesini istemiyorum.
Artık state'leri oluşturabiliriz. Shipment nesnesi oluştuğunda ilk olarak state'i OrderPlaced olur. Nesne OrderPlaced state'indeyken InTransit veya OutForDelivery state'lerine geçiş yapabilir.
Shipment nesnesi OrderPlaced state'indeyken nesne InTransit state'ine geçirilmek istendiğinde bu sınıftaki InTransit metotu çağrılacaktır. Gerekli işlemler yapıldıktan sonra shipment nesnesinin state'i InTransit state'ine çekilir. Aynı mantığı OutForDelivery metotunda da görebilirsiniz.
Şimdi ise InTransit state'ini oluşturalım. Nesne InTransit state'indeyken InCustoms state'ine geçiş yapabilir.
Nesne InCustoms state'indeyken CustomsCleared state'ine geçiş yapabilir.
Nesne CustomsCleared state'indeyken OutForDelivery state'ine geçiş yapabilir.
Nesne OutForDelivery state'indeyken Delivered veya NotDelivered state'ine geçiş yapabilir.
Nesne OutForDelivery state'indeyken NotDelivered çağrıldığında state hemen NotDelivered'e çekilmez. Yani kargonun son bir kere daha dağıtıma çıkma durumunu sağlıyoruz.
Delivered ve NotDelivered state'lerinde ise herhangi bir override yapmıyoruz çünkü bunlar son state'dir ve başka bir state'e geçilemez.
Shipment sınıfı ve kullanımı aşağıdaki gibi olacak.
Eğer context bir entity ise State öğesini veritabanına direkt kaydedemezsiniz. Eğer Entity Framework kullanıyorsanız HasConversion özelliğini kullanabilirsiniz. Bu özellik sayesinde veritabanına kaydederken state'in ismi kaydedebilir veritabanından çekerken state'in ismine göre ilgili state nesnesini oluşturabilirsiniz. Örnek kod aşağıdadır.
Özetle; hem her state'in kendine ait sorumlulukları olduğu için Single Responsibility prensibine uygun hem kolay şekilde yeni state'ler eklenebildiği için Open-Closed prensibine uygun.
Örnek koda aşağıdaki Github adresimden erişebilirsiniz.
Vesselam.
Yorum bırak
Yanıtla
Yanıtlamayı iptal et