Builder Pattern

Nesne yaratma ile ilgilenen tasarım kalıplarımızdan builder pattern bu yazımızın konusu olacak. Bu tasarım kalıbımız nesne yaratması kompleksleşmiş sınıflarımız için elverişli bir kalıptır. Detaylara girmeden evvel belirteyim ki Lombok gibi projelerin sağladığı çözümler artık bu tasarım kalıbının uygulanmasını kolaylaştırmıştır. Her ne kadar bu kalıbı artık bir annotasyon seviyesinde uygulamak mümkün olsa da, o annotasyonun arkasında gerçekleşen olayları kavrayabilmek adına bu yazımızda detaylara ineceğiz.

Abstract Factory Pattern yazımızda bir araç üretim hattına değinmiştik. Bir aracın, iki ayrı parçanın montajı ile meydana gelebileceğini varsayarak hareket etmiştik. Oysa ki gerçekte bir araç çok daha fazla bileşenden oluşmaktadır. Programlama esnasında da karşımıza çok fazla bileşenden oluşan sınıflar çıkabilmektedir.

Bir mobilya mağazasından, kendiniz montelemek üzere, bir dolap aldığınızı varsayalım. Bazı mobilya üreticileri mobilyalarını modüler olarak tasarlamaktadır. Öyle ki, oluşturmak istediğiniz dolaba göre, uygun malzemelerden uygun sayıda sipariş vermeniz gerekir. Ancak nihayetinde bir dolap almaktasınız. Bu durumda dolap oluştururken uygun malzemeleri, doğru sayıda iletmeniz sorunu çözmeli.

Bir dolabın parçalarını kapak ve gövde olarak düşünelim. Bu durumda karşımıza oldukça basit bir java sınıfı çıkacaktır.

public class Cabinet {

	int bodySize;
	
	int doorType;
	
	public Cabinet(int bodySize, int doorType) {
		this.bodySize = bodySize;
		this.doorType = doorType;
	}
	
}

Görüldüğü üzere küçük bir sınıf işimizi görüyor. Sınıfı kullanırken de uygun bir şekilde constructor çağrısı yapabilirsiniz. Lakin bu dolaba bir özellik daha eklendiğini varsayalım. Örneğin sipariş verilirken dolap kapağının kolunu ahşap, metal veya cam olarak güncelleyebilelim. Bu durumda 3. bir alanı eklememiz gerekiyor. Bir yandan yukarıda anlattığımız standart dolap yapısını da bozmak istemiyoruz. Dolayısı ile yeni alanla birlikte yeni bir constructor daha ekleme kararı alalım.

public class Cabinet {

	int bodySize;
	
	int doorType;
	
	int doorHandleType;

	public Cabinet(int bodySize, int doorType) {
		this.bodySize = bodySize;
		this.doorType = doorType;
	}

	public Cabinet(int bodySize, int doorType, int doorHandleType) {
		this.bodySize = bodySize;
		this.doorType = doorType;
		this.doorHandleType = doorHandleType;
	}
	
}

Nesnemiz karmaşıklaştıkça ortaya giderek uzayan bir constructor(lar) ortaya çıkmaktadır. Buna teleskopik constructor ismi verilir. Tıpkı teleskopu kademe kademe açtığınız gibi, constructorları da alt alta dizdiğimizde gittikçe uzayan bir yapı görürüz. Constructorda bulunan parametre sayısı artarsa; bu sınıftan nesne yaratmak istediğimizde hata yapma olasılığımız da bu duruma bağlı olarak artar. Zira gönderilen parametrelerin sırasının karışması çok kolaydır.

public Example(int p1, int p2)
public Example(int p1, int p2, int p3)
public Example(int p1, int p2, int p3, String p4) 
public Example(int p1, int p2, int p3, String p4, double p5) 
public Example(int p1, int p2, int p3, String p4, double p5, double p6) 
...

Eğer ki alışveriş esnasında dolabımızı çok çok özelleştirmek istemiyorsak kısıtlı sayıda malzeme seçeriz. Örneğin iki kapak ve bir uygun gövde şeklinde belirtiriz. Ancak bu tecrübe süresince bize sunulan bazı opsiyonları varsayılan şekilde korumak isteyebiliriz. Örneğin dolabın varsayılan malzemesi ahşap ise, bunu özellikle tekrar belirtmek istemeyiz. Müşteri tecrübesi açısından yaşanan bu durum, yazılım geliştirici tecrübesi tarafından da benzer şekilde yorumlanmaktadır. Dolabı yaratmak için uygun constructor arayışına girmek oldukça eforlu olabilir. Aynı zamanda o sınıfın tamamını kapsayan bir constructor ile çalışmak istemeyebiliriz. O halde sadece elimizdeki malzemelerle uygun dolabı yaratabilecek bir nesne yaratma yöntemine ihtiyacımız vardır. Burada builder tasarım kalıbı devreye girer.

Builder pattern ile odaklanacağımız konu, teleskopik constuctorların ortadan kaldırılmasıdır. Ancak bu durumda farklı bir problem daha ortaya çıkar. Constructor olmadan nesne yaratmak nasıl mümkün olacaktır? Aslında burada bütün constructorları yok etmeyeceğiz. Bu sorun gibi gözüken başlık, yine bu kalıp tarafından uygun şekilde adreslenmiştir. Öncelikle çözümü kod üzerinde görelim.

public class Cabinet {

	private int bodySize;
	
	private int doorType;
	
	private int doorHandleType;
	
	public Cabinet(Builder builder) {
		this.bodySize = builder.bodySize;
		this.doorType = builder.doorType;
		this.doorHandleType = builder.doorHandleType;
	}
	
	@Override
	public String toString() {
		return "Cabinet [bodySize=" + bodySize + ", doorType=" + doorType + ", doorHandleType=" + doorHandleType + "]";
	}

	public static class Builder {
		
		private int bodySize;
		
		private int doorType;
		
		private int doorHandleType;		
		
		public Cabinet build() {
			
			return new Cabinet(this);
			
		}

		public Builder bodySize(int bodySize) {
			this.bodySize = bodySize;
			return this;
		}

		public Builder doorType(int doorType) {
			this.doorType = doorType;
			return this;
		}

		public Builder doorHandleType(int doorHandleType) {
			this.doorHandleType = doorHandleType;
			return this;
		}
		
	}
	
}

İlk odaklandığımız başlık olan teleskopik constructor konusunun artık ortada olmadığını görmekteyiz. Bu noktaya geri döneceğiz. İlk durumdan farklı olarak Cabinet sınıfımıza bir static inner sınıf eklenmiş durumda. Bu sınıfın ismi Builder olarak belirlenmiş. Ancak bunun bir önemi olmadığını belirteyim. Bu konudan bahsetme sebebim ise, kodu gören başka bir yazılımcının inner sınıfı gördüğünde ne iş yaptığını anlaması oldukça kolaydır. Zira takip ettiğimiz isimlendirme formatı yazılı olmayan kurallara uygundur. Bu çözümü uygularken, benzer bir isimlendirme yaklaşımını takip etmenizi öneririm.

Yine bu inner sınıfı incelemeye devam ettiğimizde üst sınıf ile aynı isimlere sahip fieldlar görürüz. Aynı zamanda, sınıf içerisinde, her bir field için setleme işlemi yapan methodlar bulunur. Bu methodların setterlardan farklı olduğunu görürüz. Zira aynı zamanda inner sınıfın kendisini geri döndürmekle görevlidirler. İsimlerinde ise “set” ibaresini görmeyiz. Bu konu da yine standartlaşma ile alakalıdır ve tasarım kalıbımızın kullanım şekline doğrudan etki vermez.

Inner sınıf içerisinde bulunan build() methodu ise özel olarak dikkatimizi çekmektedir. Bu method, inner sınıfa ait instance ile üst sınıfa gider ve constructor çağrısı yapar. Ayrıca bu çağrının sonucunu return eder. Böylece Cabinet sınıfından bir nesne yaratmamıza yardımcı olur.

Cabinet sınıfındaki yeni constructor methodumuza baktığımızda, artık tek parametreli hale geldiğini görürüz. Bu method input olarak inner sınıfın kendisini alır. Constructor içerisinde yapılan işlemlere baktığımızda ise iki sınıf arasında doğrudan bir atama görürüz. Bu da iki sınıfın aynı isimde alanlara sahip olmasının işleri ne kadar kolaylaştırdığını göstermektedir.

Bu kalıbın kullanımını görmek için bir test sınıfı oluşturalım. Test içerisinde üç ayrı nesne oluşturup, bununla ilgili çıktıları gözlemleyelim.

public class Test {

	public static void main(String[] args) {
		
		Cabinet cabinet1 = new Cabinet.Builder()
				.bodySize(10)
				.doorType(2)
				.doorHandleType(1)
				.build();
		
		System.out.println(cabinet1.toString());
		
		Cabinet cabinet2 = new Cabinet.Builder()
				.doorHandleType(3)
				.doorType(3)
				.bodySize(15)
				.build();
		
		System.out.println(cabinet2.toString());
		
		Cabinet cabinet3 = new Cabinet.Builder()
				.bodySize(20)
				.build();
		
		System.out.println(cabinet3.toString());
	}
}

Bu örnek ile builderın kullanımını da görmüş olduk. Burada dikkatinizi çekmesi gereken bazı noktalar var. cabinet1 ve cabinet2 nesnelerinin oluşturulmasına odaklanalım. Nesneler oluşturulurken değer setlenmek istenen alanların sırası birbirinden tamamen farklı şekilde ilerlemiş. Ancak buna rağmen bir problem yaşanmaz. Haliyle constructor çağrısında yapılacak olan “Parametre sırasını hatalı gönderme” sorunundan kurtulmuş oluyoruz. cabinet3 ise daha farklı bir konsept ile sadece bodySize alanının setlenmesi ile yaratılmış. Bu da uzunca bir constructor methodu ile karşı karşıya kaldığımızda çözmemiz gereken, “İhtiyacımız olmayan parametrelerin de değerlerinin ne olduğu ile ilgilenme” sorununu çözmüş oluyor. Kodun çıktısına bakarak bunları teyit edelim.

Çıktı:
Cabinet [bodySize=10, doorType=2, doorHandleType=1]
Cabinet [bodySize=15, doorType=3, doorHandleType=3]
Cabinet [bodySize=20, doorType=0, doorHandleType=0]

Bu kalıp sayesinde nesne yaratmayı adım adım, parametre sırasıyla ilgilenmeden yapabilir duruma geliriz. Aynı zamanda nesnenin potansiyel farklı şekillerini de oluşturmak, tüm parametrelerle ilgilenmemek mümkündür. Constructor seviyesinden baktığımızda sadeleşme söz konusudur. Alt sınıfların yaratılması gerekliliği bu kalıbın dezavantajıdır. Eğer çok fazla sınıf ile uğraşıyorsanız, hepsine birer builder oluşturmak can sıkıcı olabilir. Factory kalıplarından farklı olarak bu kalıp bir anda bir nesneyi yaratmaz. Bu paragrafın başında ilettiğim gibi adım adım çalışır. Ayrıca bir farklılık olarak özyinelemeli (recursive) çağrılar bu kalıpta mevcuttur.

Leave a Reply

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