Decorator Tasarım Deseni

You can access the English version via this link.

Bu yazıda Decorator Tasarım Deseniyle ve .Net Core üzerinde bağımlılık enjekte etme yöntemiyle nasıl hayata geçirilebileceğiyle ilgileneceğiz.

Photo by Iza Gawrych on Unsplash

Decorator tasarım deseni yapısal (structural) tasarım desenleri kategorisine girmektedir. Bu tasarım kalıbının kullanılacağı yerleri saymak gerekirse başlıca iki madde ortaya çıkmaktadır.

  • Bir varlığa yeni özellikler eklememiz gereken yerler
  • Kod üzerinde değişiklik yapmadan objenin davranışını değiştirmemiz gereken yerler

İki durumda da bir varlığı paketleyerek davranışlarını istediğimiz gibi yönetebileceğimiz yeni bir varlık elde etmiş oluyoruz. Bu sebeple bu tasarım desenini matruşkalara benzetebiliriz. Decorator tasarım deseninin yapısına kısaca göz attıktan sonra yukarıda bahsedilen iki duruma ait örnek senaryolar ve kod parçaları ile devam edeceğiz.

Not: Örnek projeye GitHub üzerinden ulaşılabilir.

Decorator Tasarım Deseni Bileşenleri

Decorator Tasarım Deseni UML

Component: Sistemimizde belirli bir görevi yerine getirmek için tanımlanmış varlıktır. Soyut sınıf veya arayüz olarak tanımlanan bileşenimizdir.

Concrete Component: Component olarak tanımlanmış varlığın implemente edilmiş bir versiyondur.

Decorator: Component varlığının özelliklerini üzerine alan ve tanımlanacak ek özellikleri de üzerinde barındıracak varlıktır. Bu varlık Component gibi soyut sınıf veya arayüz olabilir. Decorator olarak tanımladığımız varlık hem bir Component olarak tanımlanır hem de bünyesinde Component tipinden bir obje barındırır. Bir diğer deyişle Decorator ile Component arasında hem IS A hem HAS A ilişkisi vardır.

Concrete Decorator: Decorator olarak tanımlanmış varlığın implemente edilmiş bir versiyonudur.

Decorator Tasarım Deseni İle Yeni Özellik Nasıl Eklenir?

IProductRepository adında bir arayüz varlığı düşünelim. GetById adındaki metot aracılığıyla Id değeri verilen ürüne ait bilgileri sunmak gibi bir özelliğe sahip olsun. DbProductRepository adında bu arayüzün bir implementasyonunu oluşturalım. Sıklıkla yaptığımız arayüz oluşturma ve bir örneğini hazırlama işlemini yerine getirdiğimiz zaman Component ve Concreate Component objelerini hazırlamış oluyoruz. Aşağıda bu yapıya ait UML diyagramı ve kod parçası yer almaktadır.

Component & Concrete Component

Buraya kadar kodlamamızı yaptıktan sonra admin kullanıcısının varlığından bahsedilmeye başlansın. Admin kullanıcısı için standart kullanıcılar gibi ürün verilerini okuyabilmenin yanı sıra ürün verilerini sisteme ekleyebilmek gibi bir özellik de talep edilsin. Kodlarımızda değişiklik yapmadan yeni özellik eklemek istediğimiz noktada Decorator tasarım kalıbı yardımımıza koşmaktadır. IAdminProductRepository adında bir arayüz tasarlayarak işe başlayalım. Bu arayüz IProductRepository arayüzünü kalıtım alacaktır. Bu sayede ürünleri okuyabilme özelliğine kavuşacaktır. Bunun yanı sıra kalıtım aldığı arayüzde olmayan CreateProduct adında eklemek istediğimiz özelliği tanımlıyoruz. Bu şekilde Decorator varlığımızı yaratmış oluyoruz.

Decorator & Component & Concrete Component

IAdminProductRepository arayüzünü implemente eden bir sınıf tasarlayarak Decorator tasarım desenimizin implemente edilmesini tamamlamış oluyoruz.

Concrete Decorator & Decorator & Component & Concrete Component

Varlıkların sistem içerisinde bağımlılıklarının yönetilmesi işini Microsoft Dependency Injection paketini kullanarak aşağıdaki kod parçasında görüldüğü gibi yapabiliriz.

Sistemimiz IProductRepository objesini çözerek eskisi gibi çalışmaya devam edebilir. Ürün ekleme özelliğini kullanmak istediğimiz yeni servislerimizde IAdminProductRepository varlığından bir örnek talep etmemiz gerekir. Bu sayede eklenen yeni özellikleri kullanabiliriz. Decorator tasarım deseni sayesinde Open-Close prensibine uyumlu bir şekilde geliştirme yapmış oluruz.

Decorator Tasarım Deseni İle Objenin Davranışı Nasıl Değiştirilir?

Aşağıdaki kod parçasına baktığınız zaman, IoC konteynerinden IProductRepository tipinden bir varlık talep edip Id değeri ile ürün sorgulaması yapılmıştır. Sisteme zerk ettiğimiz objeden dolayı biliyoruz ki bu sorgulama veri tabanından yapılmıştır.

Sistemimizin daha hızlı çalışmasına ihtiyaç olduğunu düşünelim. Ürün bilgilerinin daha hızlı sorgulanması için önbellek (cache) yapısı kurduğumuzu varsayalım. Bu durumda IProductRepository üzerinden sorgulama yaptığımız zaman önce önbellek içerisinde arama yapılmasını istemekteyiz. Aradığımız veriyi bulamazsak veri tabanında arama işleminin yürütülmesini isteyeceğiz. Bu beklentiyi yerine getirmek için DbProductRepository sınıfında değişiklik yapabiliriz. Veri tabanı sorgusundan önce önbellek üzerinde sorgularımızı yürütebiliriz. Ancak bu yaklaşımda var olan kodlarda değişiklik yaptığımız için Open-Close prensibini çiğnemiş olacaktık. Ayrıca bir sınıf içerisinde hem veri tabanı hem önbellek yönetimi yaptığımız için Single-Responsibility prensibini de çiğnemiş olacaktık. SOLID kurallarına bağlı kalmak için Decorator tasarım deseni bize yardımcı olacaktır.

CacheProductRepository adında bir sınıf tasarlayalım. Bu sınıf hem IProductRepository arayüzünün bir implemtasyonu hem de IProductRepository arayüzünde bir objenin sahibi olacak. Önce önbellekte arama yapacak, cevap bulamazsa üzerinde bulundurduğu diğer bir IProductRepository objesine görevi devredecektir. CacheProductRepository objesinin sahibi olduğu repository varlığından cevap geldiği zaman veriyi daha sonraki sorgulamalar için önbelleğe kaydedecektir.

Bu sınıfı tasarladıktan sonra CompositionRoot üzerinde bağımlılıkları aşağıdaki gibi düzenleyebilmekteyiz.

Bağımlılıkların sisteme tanıtılmasının ardından IProductRepository aracılığı ile aldığımız hizmetin davranışında istediğimiz değişikliği sağlamış olacağız.

Umarım yararlı bir yazı olmuştur. Diğer yazılarıma göz atmak için bu linke tıklayabilirsiniz.

var software = ConvertFrom(caffeine)

var software = ConvertFrom(caffeine)