Prototype Pattern

Bir başka creational, yani nesne yaratma süreçleriyle ilgilenen, tasarım kalıbımız olan prototype pattern, nesne kopyalama ihtiyaçlarımıza karşılık veren bir kalıptır. Tarihin eski dönemlerinde, henüz sanayileşme gerçekleşmemişken, üretim yapmak ustaların maharetine bakardı. Özellikle benzer nitelikte bir ürünü ortaya çıkarmak ciddi zaman alan bir iş idi. İnsanlık bir süre sonra bu tarz durumlara alternatif çözümler üretme arayışına girdi. Örneğin duvar yapımı için toprağı pişirerek tuğlalar yapmayı öğrendik. Bu tuğlalara şekil veren ahşap kalıplar ürettik. Kalıp mantığı tarihin ilerleyen dönemlerine doğru hayatımızın her alanına girdi. Keza şu anda soyut bir kavram olan tasarım kalıpları üzerine konuşmaktayız.

Kalıp üretme veya kalıpla üretme ihtiyacı, şüphesiz, bizi ciddi bir zahmetten kurtaracak eylemleri gerçekleştirmek içindir. Zaman zaman programlama esnasında da belirli çıktılara kolayca ulaşmak isteriz. Günümüzde docker gibi sanallaştırma çözümleri çok yaygındır. Bunu temel olarak fiziksel olmayan bir sunucunun, fiziksel sunucuda, ihtiyaca istinaden çalıştırılması gibi düşünebilirsiniz. Yine varsayalım ki, her ayağa kalkan sunucu için bir java nesnesi ile konfigürasyon yapmak istiyoruz. Bu nesne üzerinde tüm sunucular için ortak olan bazı özellikler bulunsun. Örneğin her sunucu mail atmak için belirli başka bir sunucuya bağlanıyor olsun. Ancak ayağa kalkan her sunucu, açılış saatini ayrıca bu konfigürasyon nesnesinde tutmak istesin. Burada yapılacak şey, belirli bir java objesini oluşturup, ilgili sunucunun onun özel alanlarını doldurmasını sağlamaktır. Yani her sunucuya dağıtacağımız, belirli bir kalıp ile üretilen bir konfigürasyon nesnemiz olacaktır. Eğer bu konfigürasyon nesnesi farklı farklı kaynaklardan besleniyorsa oluşturmak zahmetli olabilir. Bu kaynaklar bir dosya, veri tabanı veya bir web servis olabilir. Hızlıca ayağa kalkmasını istediğimiz bir sistemde bu tarz durumlar bizi yavaşlatacaktır.

Benzer bir durum programlamanın kendisiyle de karşımıza çıkar. Örneğin bir otel yönetim uygulaması kodladığımızı düşünelim. Oteller yerleşmeye hazır bir odayı kendi standartlarına göre tanımlar. Mesela temiz çarşaflar serilmiş, terlik bırakılmış, banyosu temizlenmiş ve mini barı doldurulmuş bir oda, ilgili otel için hazır durumda olabilir. Odaları hazırlayan görevliler, sistemden bir odayı hazır statüsüne çektiğinde, uygulama üzerinde bu tanımları doğrudan belirli bir statüye (OK, Temiz, true vs) getirmek isteyebilirsiniz. Öncelikle odayı temsilen bir sınıfı kodlayalım.

public class Room {

	private boolean isBathroomCleaned;
	
	private boolean isSlippersReady;
	
	private boolean isMiniBarFilled;

	public Room(boolean isBathroomCleaned, boolean isSlippersReady, boolean isMiniBarFilled) {
		this.isBathroomCleaned = isBathroomCleaned;
		this.isSlippersReady = isSlippersReady;
		this.isMiniBarFilled = isMiniBarFilled;
	}

	public boolean isBathroomCleaned() {
		return isBathroomCleaned;
	}

	public void setBathroomCleaned(boolean isBathroomCleaned) {
		this.isBathroomCleaned = isBathroomCleaned;
	}

	public boolean isSlippersReady() {
		return isSlippersReady;
	}

	public void setSlippersReady(boolean isSlippersReady) {
		this.isSlippersReady = isSlippersReady;
	}

	public boolean isMiniBarFilled() {
		return isMiniBarFilled;
	}

	public void setMiniBarFilled(boolean isMiniBarFilled) {
		this.isMiniBarFilled = isMiniBarFilled;
	}
	
}

Bu sınıf 3 ayrı saha, bu sahalara ait getter-setter ikilisi ve yapıcı fonksiyondan oluşmaktadır. Tasarım kalıbımızı anlaşılır kılabilmek adına basit bir sınıf kullanmaktayız. Her 3 sahası da true değer taşıyan bir obje (yani oda) kullanıma hazır odayı temsil etmektedir. Bir test sınıfı üzerinden hazır odayı temsili olarak oluşturalım.

public class Test {

	public static void main(String[] args) {
		
		Room readyToUseRoom = new Room(true,true,true); 
		
	}
}

Bu nesnemiz, bir anlamda, kullanıma hazır herhangi bir odayı temsil etmektedir. Pekala, kullanıma hazır olmayan, ancak görevliler tarafından düzenlenmeyi bekleyen, başka odalarımız da olabilirdi. Bunları da temsili olarak oluşturalım.

Room room1 = new Room(false,true,true);
Room room2 = new Room(true,false,true);
Room room3 = new Room(true,true,false);

Görüldüğü üzere bir odada terlikler hazır değilken, diğerinde mini bar doldurulmamış durumda. Bir başka odanın ise banyosu temizlenmemiş durumda. Biz bu odaları hazır duruma getirmek için ilgili alanların setter methodlarını çalıştırabilirdik. Lakin, bu sınıf eğer karmaşık bir yapıya sahip olsaydı (Alanları primitif tipler yerine kompleks sınıflar olabilirdi) iç içe giden bir döngü ile uğraşmak zorunda kalırdık.

Alternatif olarak kullanıma hazır durumda olan readyToUseRoom nesnesini, hazır olan oda ile eşleştirmeyi düşünebiliriz. Ancak bu da ayrı bir soruna yol açmaktadır. Java atama operatörü referans üzerinden çalışmaktadır. Yani eşleştirme esnasında bir kopyalama eylemi yerine, yansıtma eylemi gerçekleştiririz. Bu durumun çıktısı aşağıda verilmiştir.

public class Test {

	public static void main(String[] args) {
		
		Room readyToUseRoom = new Room(true,true,true); 
		
		Room room1 = new Room(false,true,true);
		
		room1 = readyToUseRoom;
		room1.setBathroomCleaned(false);
		
		System.out.println("room1.isBathroomCleaned() -> " + room1.isBathroomCleaned());
		System.out.println("readyToUseRoom.isBathroomCleaned() -> " + readyToUseRoom.isBathroomCleaned());
		
	}
}
Çıktı:
room1.isBathroomCleaned() -> false
readyToUseRoom.isBathroomCleaned() -> false

Görüldüğü gibi atama sonrası room1 nesnesi üzerinde yapılan değişiklikler, readyToUseRoom nesnesine de etki etmektedir. BU arzulanan bir durum değildir.

Bu noktada çözüm prototype pattern kullanmaktır. Buradaki ihtiyaç, belirli bir durumu (state) tanımlayan bir nesnenin, en baştan oluşturulması yerine, onun kullanıma hazır kopyalarını (yansılarını-referansını değil) anlık olarak oluşturabilme kabiliyetidir. Javada bu işleme klonlama ismi verilmektedir. Yani bir sınıfa ait bir nesneyi alıp, onun referanstan bağımsız olarak, alanları aynı değerleri taşıyan farklı bir nesne daha oluşturmaktır.

Javada sınıflarınıza klonlanabilme kabiliyeti kavuşturmak isterseniz Cloneable arayüzünü implement edebilirsiniz

public class Room implements Cloneable {

	private boolean isBathroomCleaned;

	private boolean isSlippersReady;

	private boolean isMiniBarFilled;

	public Room(boolean isBathroomCleaned, boolean isSlippersReady, boolean isMiniBarFilled) {
		this.isBathroomCleaned = isBathroomCleaned;
		this.isSlippersReady = isSlippersReady;
		this.isMiniBarFilled = isMiniBarFilled;
	}

	//...
	
	//Klonlama methodu
	public Room clone() {
		Object clone = null;
		
		try {
			clone = super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return (Room) clone;
	}
}

Klonlama kabiliyeti kazanan sınıfımızla az önceki örneğimizi tekrar deneyelim. Lakin bu defa atamayı yaparken readyToUseRoom nesnesinin kendisini doğrudan atamayacağız. Bunun yerine clone() methodundan gelen çıktıyı kullanacağız. Odaklanmamız gereken başlık ise referans kullanımından kurtuluyor olmamızdır.

room1 = readyToUseRoom.clone();
room1.setBathroomCleaned(false);

System.out.println("room1.isBathroomCleaned() -> " + room1.isBathroomCleaned());
System.out.println("readyToUseRoom.isBathroomCleaned() -> " + readyToUseRoom.isBathroomCleaned());
Çıktı:
room1.isBathroomCleaned() -> false
readyToUseRoom.isBathroomCleaned() -> true

Buradaki çıktıda, ana nesneden (readyToUseRoom) klonlanmış nesnede (room1) yapılan değişikliklerin, ana nesneye etki etmediğini gözlemleyebilirsiniz. Bir önceki durumda referans kullanımı sebebiyle iki değişkene de aynı değer atanmış, bu değişkenlerin üzerinde yapılan her değişiklik diğer nesneyi de etkilemişti.

Bu kalıbı Cloneable arayüzü olmadan da tasarlayabilmek mümkündür. Lakin bu defa kendi klonlama methodlarınızı ve kopyalama mantığınızı oluşturmanız gerekir. Biz ise örneğimizde ata sınıftan gelen clone kabiliyetini kullandık.

Bu kalıbımızın özellikle karışık yapılara sahip sınıflarda, nesne kopyalama işlerimizi kolaylaştıracağı aşikardır. Ancak bu karmaşıklık büyüdükçe klonlama mantığı da kendi içinde minik bir uygulama mantığına doğru evrilebilir veya döngüsel çağrımlar (A->B->C->A) sebebiyle uygulamamız klonlama yaparken hataya sürüklenebilir. Yine de istenen nesneyi istenen state ile initialize etmek için, yazmamız gereken kodları (constructorlar, setterlar, getterlar, ilk değerler vs) ortadan kaldırabilmesi, bu kalıbın kullanılması için önemli artılardandır.

Leave a Reply

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