Yaratımsal Kalıplar (Creational Patterns)
Yaratımsal kalıpları (creational patterns) ele aldık ama bu konu hala bir yazıyı hakediyor. Çünkü yaratımsal kalıpların arasındaki ilişkileri ve hepsinin bir arada nesne yaratma problemini nasıl çözdüklerini görmek, nesne yaratma problemini ve çözümlerini bir arada görmek açısından çok faydalı olacaktır.
GoF’da 5 tane yaratımsal kalıp vardır. Bunlardan Singleton, diğer dördünden farklı ele alınabilecek yapıdadır. Çünkü Singleton, “nesne yaratma” hakkında bir kalıp değildir. Yani Singleton, nesnenin ya da nesnelerin nasıl yaratılacağıyla ilgili değildir, o çok daha özel bir problemi çözer: Bir sınıftan sadece ve sadece bir tane nesne nasıl yaratılır? Dolayısıyla Singleton‘u kenara koyarsak, geriye kalan dört kalıbı ve aralarındaki ilişkileri ele alabiliriz.
Singleton‘u çıkardığımızda geriye kalan dört kalıptan ikisi, Factory Method ve Abstract Factory, diğer ikisinden yani Builder ve Prototype‘tan görevleri itibariyle ayrılırlar. Dolayısıyla, geriye kalan dört kalıp, iki ayrı grupta ele alınabilir. Aralarındaki fark, ilk grubun yani Factory Method ve Abstract Factory kalıplarının nesne yaratmayı soyutlamalarına rağmen diğer iki kalıbın, yani Builder ve Prototype‘ın nesne yaratmayı soyutlamak yerine nesne yaratma mekanizmasını gerçekleştiriyor olmalarıdır. Bir başka deyişle, Factory Method ve Abstract Factory kalıplarının esas hedefi, nesne yaratma kodunun ortalıkta gezinmesini yani nesne yaratan kodların tekrarını önlemek ve bu kodları tekrar kullanılabilecek şekilde soyutlamaktır. Dolayısıyla Factory Method ve Abstract Factory kalıplarındaki metotlarda nesnelerin nasıl yaratılacakları, bu kalıpların çözdüğü bir şey değildir. Factory Method ve Abstract Factory kalıpları, “nesneleri nerede yaratalım?” sorusunun cevabıdırlar. Bu sorunun cevabını Factory Method ya da Abstract Factory olarak verdikten sonra soru değişir. Nesnenin nerede yaratılacağı belli olduğuna göre soru artık “nesneyi nasıl yaratalım?”dır. Builder ve Prototype kalıpları ise GoF’un bu soruya verdikleri cevaplardır.
Malum,”nesneyi nasıl yaratalım?” sorusu klasik olarak iki cevaba sahiptir: ya kurucu çağrısı ile bütün gerekli veriyi parametre olarak geçerek nesne yaratma ya da önce varsayılan kurucu çağrısı ile en temel durumda nesne yaratma ve sonra da set metotları ile nesneyi istenilen duruma getirme. Bu ikinci durum “JavaBean modeli” olarak bilinir. Buna bir de Joshua Bloch’ın Effective Java kitabında tavsiye ettiği statik factory metodu yaklaşımı eklenirse, alternatifler 3’e çıkar.
Bu iki sorulu ayrım ve cevapları aşağıdaki şekilde özetlenmiştir:
Şimdi bu ayrımı aşağıdaki örnekte ele alalım.
Örneğin elimizde bir tane Car sınıfı var ve uygulamada bu sınıfın değişik nesnelerini oluşturmamız gerekiyorsa, nesne oluşturan kodları bir Factory Method’un içine koyarız. Yok elimizde Car ile beraber Engine, Brake, Wheel, Door vb. sınıflar da varsa ve bu sınıfların nesnelerini biz bir arada oluşturuyorsak, bu durumda her birisi için gerekli olan Factory Method‘unu bir sınıfın içine koyar ve Abstract Factory‘i elde ederiz. (Açıkçası Factory Method, AbstractFactory‘nin tek nesne için olan halidir. Bu yüzden ikisinin Factory adıyla tek bir kalıp olarak ele alınması daha iyi bir durum olur. Fakat GoF, bir hata yapıp ikisini ayırmış ve nihayetinde karmaşaya yol açmıştır. Bu konuda buraya bakılabilir.) Hatta eğer farklı Car nesneleri için Brake, Wheel, Door vb. sınıfların farklı durumda nesneler oluşturuluyorsa, bu halde her nesne ailesi için ayrı bir Abstract Factory sınıfı oluştururuz.
Buraya kadar sistemde var olan Car, Brake, Wheel, Door vb. sınıfların nesnelerinin nerede oluşturulacağı ile ilgili gerekli yapıyı kurguladık ama ister Abstract Factory olsun ister Factory Method olsun, nihayetinde nesne oluşturan üretici yani factory metotlarının içinde nesneleri gerçekte nasıl üreteceğimizi konuşmadık. İşte şimdi de bu kısma geldiğimizde elimizde hangi alternatiflerin olduğunu ele almalıyız.
Factory Method ve Abstract Factory kalıplarında nesneleri üretmek için temelde beş yöntemimiz vardır. İlk ikisi ve handikapları şöyledir:
- Kurucu (constructor) çağrısı: En iyi bilineni parametreli kurucu çağrısı ile nesne oluşturmak. Kurucular, isimlerinin aynı olmasınan dolayı karıştırılılar ve çoğu zaman hataya yol açarlar. Ayrıca artan parametre sayısı kurucu çağrılarını gittikçe zorlaştırır.
- JavaBean yaklaşımı: Varsayılan kurucu ile nesneyi oluşturup set metotlarıyla nesneyi istenilen duruma getirmek. Bu yaklaşım da kurucu çağrısı gibi problemlidir, hataya yol açabilir.
Yukarıdaki iki alternatif de basit nesnelerin oluşturulması için kullanılmalıdır. Basit nesnelerden kasıt ise kurucusuna geçilen parametre sayısı ya da JavaBean yaklaşımında çağrılacak set metodu sayısı 3-4’ü geçmeyendir. Hele JavaBean yaklaşımında set edilecek durum parametreleri arasında bağımlılık varsa ve bu set metotlarının çağrı sırasını belirliyorsa bu durumda hataya çok elverişli bir durum var demektir, kaçınılmalıdır. Bu halde aşağıdaki yollar tercih edilmelidir.
Peki, geriye kalan üç alternatif nedir:
- Joshua Bloch’ın statik factory metotları: Joshua Bloch, Effective Java kitabında, nesne oluşturmak için, sınıfta tanımlanan karmaşık yani çok argüman alan kurucular yerine, anlamlı isimlere sahip statik factory (nesne üretici) metotları önermektedir. Buna göre kurucular private yapılır ve var olan her kurucu için,aynı sınıfta, statik, nesne yaratan yani factory metotlar oluşturulur öyle ki bu metotların isimleri, yarattıkları nesnenin durumunu göz önüne alacak şekilde verilir. “createAuthenticatedUnvalidatedCorporateUser” gibi isimlerle, hem kullanıcının durumuyla ilgili geçilecek ikisi muhtemelan boolean tipinde, üç ya da daha fazla argümandan kurtulunmuş olur hem de kurucuların isimlerinin aynı olmasının getirdiği karıştırma problemi oradan kalkar. Bu konuda ayrıntılı bilgi için Bloch’ın Effective Java kitabına bakılmalıdır.
- Builder (İnşacı): Eğer nesne karmaşık ve bu karmaşıklık nesnenin oluşturulma şeklini de belirliyorsa, nesnenin oluşturulması bir süreç gerektiriyor demektir. Bu durumda nesnenin yaratılmasından değil de oluşturulmasından, inşa edilmesinden bahsedilir. Bu şekilde, nesneyi bir süreç dahilinde oluşturmayı öneren kalıp, Builder ya da İnşacı kalıbıdır. Dolayısıyla karmaşık nesneler, bir süreç dahilinde bu kalıp ile oluşturulmalıdırlar.
- Prototype (Örnek Nesne): Prototype da Builder gibi, oluşturulması zor dolayısıyla da yüksek karmaşıklıktaki nesnelerin yaratılması içindir ama ufak bir farkla. Prototype, yeni nesneyi, var olan bir nesneden oluşturur. Yani önce var olan örnek (prototype) nesneyi kopyalayar (clone) sonra istenilen duruma getirir. Bu yüzden, Prototype kalıbının odağı, durumdan (state) kaynaklanan karmaşıklıktır. Bu da şu anlama gelir: Eğer nesnenizin karmaşıklığı, nesnenin sahip olduğu durumlardan kaynaklanıyorsa, yani nesneniz iş için belirlenmiş bir kaç durumdan birinde olması gerekiyor ve developerların hepsinin bu durumları detaylarıyla bilmesi zor ise, o nesneyi bu durumlardan birinde oluşturmak zor ve hataya elverişli bir durum var demektir. Bu durumda nesneyi sıfırdan oluşturmak yerine, arzu ettiğiniz durumda olan bir nesneyi kopyalayarak elde etmek daha kolay olur. Bu da Prototype kalıbıdır.
Yukarıdaki alternatiflerle ilgili şunlar söylenebilir:
- Nesneleri daima bu işe hasredilmiş üretici (factory) sınıflarında oluşturun. Oluşturulacak nesneler birbirleriyle ilgili değillerse, her birisi için bir Factory Method kullanın, yok nesneler bir arada kullanılıyor ve muhtemelen de beraber oluşturuluyorsa, bu durumda nesne ailelerinden bahsediliyor demektir ve bunun için Abstract Factory‘i kullanın.
- Factory Method, AbstractFactory‘nin tek nesne için olan halidir. Dolayısıyla Abstract Factory, Factory Method‘u kullanır. (Aslen her iki kalıp da Factory isimli genel kalıbın altında ele alınmalıdır.)
- Factory Method (dolayısıyla da AbstractFactory), nesne oluşturmak için Builder ya da Prototype kalıplarını kullanabileceği gibi, nesneyi yaratmak için kurucu cağrısı, JavaBean yöntemini ya da Bloch’ın statik factory metotlarını da tercih edebilir.
- Belki garip gelecek ama yukarıdakinin tersine, Builder da Factory Method‘u (dolayısıyla da AbstractFactory) kullanabilir çünkü nesneyi inşa sürecinde muhtemelen pek çok, esas nesnenin ihtiyacı olan diğer, muhtemelen farklı türlerden nesneler de oluşturulacaktır. Benzer şekilde esas nesnenin inşa sürecinde gerekli diğer nesnelerin yaratılması için, kurucu cağrısı, JavaBean modeli ya da Bloch’ın statik factory metotları da kullanılabilir.
Aşağıdaki şekil, yukarıda anlatılan seçenekleri ve kalıplar arasındaki ilişkileri özetlemektedir.
Bu yazıda nesne yaratmayla ilgili hem kalıpları hem de klasik diğer yolları ve Bloch’un önerisini ve aralarındaki ilişkileri ele aldık. Bu şekilde konu ile ilgili büyük resmi görmeye ve daha analitik bir anlayışa sahip olmaya çalıştık.
Toplam görüntülenme sayısı: 1711