Decorator Pattern

Decorator tasarım kalıbı, bir sınıfın mevcut yapısına müdahale etmeden, ona runtime esnasında yeni özellikler kazandırabilmek adına kullanabileceğimiz bir yapısal tasarım kalıbıdır. Kalıbın yapısı diğer kalıpları andırsa da, odaklandığı nokta benzer yapıları kullanarak farklı yapılar oluşturmaktır. Bunu benzer boydaki ve tipteki legoları birleştirerek oda, ev, apartman, site yaptığınız bir çözüm olarak düşünebilirsiniz.

Bir alışveriş sitesini düşündüğümüzde, aklımıza belirgin bazı özellikler gelir. Örneğin ana sayfayı hayal edersek; arama kutucuğu, filtreleme özelliği ve listeleme gridi gibi üç ana özelliğin gerekli olduğunu varsayabiliriz. Bazı sitelerde ise bunlardan daha fazla özellik vardır. Örneğin; harita üstünde listeleme, karşılaştırma gibi iki özelliği daha listemize ekleyelim.

Varsayalım ki bir e-ticaret altyapı sağlayıcısı firmanın sahibisiniz ve yukarıda bahsedilen özellikleri ihtiyaca göre müşterilerin e-ticaret sitelerine ekliyor veya çıkarıyorsunuz. Bu durumda modüler bir yapıya ihtiyaç duyarsınız. Üstelik temel pratik olarak her bir özelliği birbirinden ayrı ele almak istersiniz. Öncelikle bu durumun nasıl probleme dönüştüğünü bir kod üzerinde görelim.

public class Compare {
	
	public void doCompare() {
		System.out.println("Compare.doCompare()");
	}

}

public class Filter {
	
	public void doFilter() {
		System.out.println("Filter.doFilter()");
	}

}

public class List {
	
	public void doList() {
		System.out.println("List.doList()");
	}

}

public class ListOnMap {
	
	public void doListOnMap() {
		System.out.println("ListOnMap.doListOnMap()");
	}

}

public class Search {
	
	public void doSearch() {
		System.out.println("Search.doSearch()");
	}

}

public class ECommerceApp {

	private Compare compare;
	
	private Filter filter;
	
	private List list;
	
	private ListOnMap listOnMap;
	
	private Search search;

	public ECommerceApp(Compare compare, Filter filter, List list, ListOnMap listOnMap, Search search) {

		this.compare = compare;
		this.filter = filter;
		this.list = list;
		this.listOnMap = listOnMap;
		this.search = search;
		
	}
	ce
	// Getters/Setters 
	
}

Örneğe ilk baktığımızda her bir sınıfın (Arama, filtreleme vs.) ayrı bir uzmanlık gerektirdiğini görmekteyiz. ECommerceApp sınıfı ise bu bilgileri birleştirmiş sınıf durumundadır. Ancak ilk tasarım hatası olarak, davranışsal benzerliğin görüldüğü yerlerde interface kullanımının olmaması dikkatimizi çeker. ECommerceApp sınıfına dahil edilmiş bütün sınıflar, aslında, e-ticaret uygulamasında bulunabilecek birer bileşeni temsil etmektedir. Bir bileşeni sözle tanımlamak mümkün olduğundan, bu bileşenleri birer interface arkasına almak da mümkündür. Ancak sorun burada bitmez. Müşterilerimizin bazısına harita özelliğini satmak isterken, bazısına istemeyebiliriz. Dolayısı ile sınıfın içindeki alanların sayısı da dinamik olarak değiştirilebilir halde olmalıdır. Burada decorator pattern devreye girer. Şimdi bu bilgilerle uygulamamızı yeniden kodlayalım. Öncelikle sayfamızdaki bütün parçaları bir bileşen olarak değerlendireceğimizden bu bileşenleri temsil eden bir interface hazırlayalım.

public interface Component {
	
	public void process();

}

Bu noktada tüm özelliklerimizi (Arama, listeleme vs.) bu arayüzün arkasına alabilirdik. Lakin biz decorator pattern kullanarak buradaki problemi gidermek istiyoruz. Dolayısı ile istediğimiz an uygulamamızı modifiye edebileceğimiz bir yapıyı buraya konumlandırmamız gerekir. Bu yapı kendi başına anlamsız ancak sunum bazında anlamlı olmalıdır. Bu da bir abstract sınıf ihtiyacını adresler.

public abstract class ComponentDecorator implements Component {

	protected Component component;

	public ComponentDecorator(Component component) {
		this.component = component;
	}
	
	public void process() {
		component.process();		
	}
	
}

Abstract sınıfımıza baktığımızda görürüz ki Component interface ile kendisine biçim belirlenmiştir. Aynı şekilde sınıfımız Component tipinde bir de saha taşır. Bu saha constructor vasıtası ile sınıfın sunumlarına enjekte edilmektedir. Sahaya enjekte edilen değerin işlevi, yine process() methodu üzerinden tetiklenir. Bu sayede bir döngüsellik yakalarız. Ancak bu tek başına yeterli olmamaktadır. E-ticaret uygulamamızdaki özelliklerin runtime esnasında bu döngüselliği kullanabilmesi gerekir. Buna uygun olarak sınıflarımızı kodlayalım.

public class Compare extends ComponentDecorator {
	
	public Compare(Component component) {
		super(component);
	}

	@Override
	public void process() {
		System.out.println("Compare component");
		this.component.process();
	}

}

public class Filter extends ComponentDecorator {
	
	public Filter(Component component) {
		super(component);
	}

	@Override
	public void process() {
		System.out.println("Filter component");
		this.component.process();
	}

}

public class List extends ComponentDecorator {
	
	public List(Component component) {
		super(component);
	}

	@Override
	public void process() {
		System.out.println("List component");
		this.component.process();
	}

}

public class ListOnMap extends ComponentDecorator {
	
	public ListOnMap(Component component) {
		super(component);
	}

	@Override
	public void process() {
		System.out.println("ListOnMap component");
		this.component.process();
	}

}

public class Search extends ComponentDecorator {
	
	public Search(Component component) {
		super(component);
	}

	@Override
	public void process() {
		System.out.println("Search component");
		this.component.process();
	}

}

Görüldüğü üzere tüm özellikler ComponentDecorator sınıfından extend edilmiş olup, kendi arayüz tiplerindeki (Component) bir sahayı yapıcı fonksiyonlarından içeri alırlar. Ayrıca process() methodu da sınıfa enjekte edilmiş componentin process methodunu tetikler. Aynı zamanda bu method içerisinde, ilgili özelliğin (List vs.) kendi işlevi de gerçekleştirilmektedir.

Son olarak ECommerceApp sınıfımızı da düzenlememiz gerekmektedir. Uygulamamızı artık bir componentler bütünü halinde değerlendirmekteyiz. Bu sınıfımız da durumdan nasibini alır. Ancak bu sınıf halkanın sonunda olmalı ve sadece hangi componentleri taşıyacağından nihai olarak haberdar olmalıdır.

public class ECommerceApp implements Component {

	@Override
	public void process() {
		System.out.println("E-commerce app is ready!");
	}
	
}

Kalıbımızı test edebilmek adına bir test sınıfı oluşturalım. Bu sınıf içerisinde 2 componentli bir e-ticaret uygulaması oluşturalım.

public class DecoratorTest {

	public static void main(String[] args) {
		
		Component component = new Search(new Filter(new ECommerceApp())); 
		
		component.process();
		
	}
}
Çıktı:
Search component
Filter component
E-commerce app is ready!

Farklı kombinasyonlarla e-ticaret uygulamaları oluşturulabilir. Buradaki modülarite gerçekten elverişli bir hale gelmiştir ve e-ticarete ait bileşenlerin fonksiyonlarını bilmekten de uzaklaşmış duruma konumlanırız. Böylece tıpkı kat kat giyinen bir insanın temsilinde olduğu gibi, elbiseler kendi içinde ilişkili ve bu ilişki nihayetinde insan ile birleşen haldedir.

Decorator pattern ile yeni sınıflara veya sınıf içi yeni alanlara ihtiyaç duymadan nesnemizin sunumunu değiştirebiliriz. Bunu runtime esnasında yapabilmemiz, programlarımızı yazarken de esnekliğe sahip olmamızı sağlar. Bu sayede sunuma eklenecek her davranış veya özelliği kendi sınıfı içerisinde kodlar ve tekil sorumluluk noktasında daha güçlü bir koda sahip oluruz. Aynı şekilde sınıfımızı değişime kapalı ve genişlemeye açık hale getirerek, open/closed prensibine de sadık kalmış oluruz. Tasarım noktasında baktığımızda ise decorator sınıflarını oluşturmak çeşitli güçlüklerle karşılaşmamıza sebep olabilir. Özellikle hangi davranışın/özelliğin o dekorun bir parçası olduğunu veya mevcut durumun nasıl parçalara bölüneceğini ve nerede genelleşip, nerede özelleşeceğini anlamak zor olabilir.

Leave a Reply

Your email address will not be published. Required fields are marked *