Tasarım Kalıpları: Factory Method (Üretici Metot) – II
Bir önceki yazıda Factory Method kalıbına giriş yapmıştık ve sınıf diyagramı ile tipik bir uygulamasını görmüştük. Şimdi bu uygulamanın kodlarına kısaca göz atalım.
Önce Product yapısına bakalım:
public interface Product { public void doThis(); } public class ProductA implements Product { private int id; private String name; public ProductA(int id, String name) { this.id = id; this.name = name; } @Override public void doThis(){ System.out.println("do() in ProductA"); } ... } public class ProductB implements Product { private int no; private String description; public ProductB(int no, String description) { this.no = no; this.description = description; } @Override public void doThis(){ System.out.println("do() in ProductB"); } ... }
Şimdi de bu Product yapısındaki nesneleri oluşturan üretici metotlara bakalım:
public interface Factory { public Product create(); } public class ProductAFactory implements Factory { @Override public Product create() { ProductA productA = new ProductA(1, "Product-A-1"); return productA; } } public class ProductBFactory implements Factory { @Override public Product create() { ProductB productB = new ProductB(2, "Product-B-2"); return productB; } }
Bu durumda Client ve Test nesneleri de şöyle olur:
public class Client { private Product productA; private Product productB; private Factory productAFactory; private Factory productBFactory; public Client(Factory productAFactory, Factory productBFactory){ productA = productAFactory.create(); productB = productBFactory.create(); } public void start(){ productA.doThis(); productB.doThis(); } }
public class Test { public static void main(String[] args) { Client client = new Client(new ProductAFactory(), new ProductBFactory()); client.start(); } }
Hem bir önceki yazıda verdiğimiz sınıf diyagramı hem de yukarıdaki kodlardan anladığımıza göre artık Client’ın Product’ları doğrudan yaratmak yerine, bunu üretici metotlara havale ettiğini ve Product’larla arayüzleri üzerinden haberleştiğini görüyoruz. Bu durum ise Factory arayüzünü yerine getiren ve üretici metotlara sahip üretici sınıflarla başarılır. Bu şekilde Client’ın hem Product’lara hem de onları üreten Factory’lere bağımlılığı arayüz seviyesine iner.
Factory arayüzü sayesinde, uygulamadaki sınıflar, bu sınıfları kullanan client kodlarından olabildiğinde yalıtılmış olur. Bu yaklaşımın en temel problemi, her yeni Product sınıfı için yeni bir Factory alt sınıfa ihtiyaç duyurmasıdır. Yani, yeni bir Product nesnesi olan ProductC için ProductCFactory alt sınıfına ihtiyaç duyulur.
Bu kalıp üzerinde ihtiyaca göre bazı oynamalar tabi olarak yapılabilir. Örneğin Factory tipi arayüz ya da soyut (abstract) sınıf yerine somut (concrete) sınıf da olabilir. Bu durumda create() factory metodu parametre alarak, hangi Product nesnesinin yaratılacağına karar verebilir. Ve her yeni Product nesnesi için yeni bir factory nesnesine ihtiyaç da kalmaz. Bu durumda Factory sınıfı Singleton da olabilir ve istenirse farklı implementationlar için Factory sınıfı hala extend edilebilir. Bu durumdaki Factory sınıfı ile factory metodu ve Client aşağıda olduğu gibidir.
public class Factory { public Product create(String productType){ Product product = null; if(productType.equalsIgnoreCase("a")) product = new ProductA(1, "Product-A-1"); else if(productType.equalsIgnoreCase("b")) product = new ProductB(2, "Product-B-2"); return product; } } public class Client { private Product productA; private Product productB; public Client(Factory factory){ productA = factory.create("a"); productB = factory.create("b"); } public void start(){ productA.doThis(); productB.doThis(); } }
Yukarıdaki durumun bir özel hali olarak, client, kullanacağı nesnenin yaratılması için gerekli bazı bilgileri sağlama durumunda olabilir. Bu durumda yukarıdaki örneğe benzer şekilde client, üretici metoda bu bilgileri geçmelidir. Burada dikkat edilmesi gereke husus, clientın geçeceği bu bilgileri hakikatten sadece clientın biliyor olmasıdır. Factory methodun da erişebileceği bilgileri clientın geçmesi hem anlamlı değildir hem gereksiz bağımlılık oluşturarak bu kalıbı kullanmaktan doğan faydayı azaltır.
Bu kalıbın çok sıklıkla, Factory sınıfının üzerindeki factory metotlarını statik yaparak kullanılması da söz konusudur. Bu durumda Client sınıfı Factory sınıfının nesnesini oluşturmak zorunda kalmaz ve Factory sınıfının alt sınıflarını oluşturmaktan da kaçınılmış olur.
public class Factory { public static Product create(String productType){ Product product = null; if(productType.equalsIgnoreCase("a")) product = new ProductA(1, "Product-A-1"); else if(productType.equalsIgnoreCase("b")) product = new ProductB(2, "Product-B-2"); return product; } }
public class Client { private Product productA; private Product productB; public Client(){ productA = Factory.create("a"); productB = Factory.create("b"); } public void start(){ productA.doThis(); productB.doThis(); } }
Bu yaklaşımın problemi, büyüyebilen (scalable) olmamasıdır. Yani oluşturulacak nesnelerin sayısı arttıkça, iflerin sayısı da artacaktır. Dolayısıyla bu çözüm zaten Open-Closed Principle ( OCP)’a aykırıdır. Hatta bir müddet sonra ilgili-ilgisiz her türlü nesnenin kendisiyle oluşturulduğu devasa bir metota sahip olabilirsiniz. Dolayısıyla bu yaklaşım bir ters-kalıptır (anti-pattern). Eğer birbiriyle ilgili birden fazla nesneyi yaratma probleminiz varsa bu durumda zaten Abstract Factory kullanılabilir.
Factory Method’un her zaman nesne oluşturması gerekmez. Nesne havuzu (object pool) gibi yapılarda Factory Method havuzdan bir nesne de geri döndürebilir.
J. Bloch “Effective Java 2nd Ed.” isimli kitabının ilk maddesinde, yapılandırıcı kullanmak yerine statik factory metotları kullanmayı tavsiye ediyor. Çünkü metotların ismi olmasına karşın yapılandırıcıların ayırt edici isimleri yoktur. Ve özellikle de karmaşık nesnelerde pek çok yapılandırıcı vardır ama hangisinin çağrılacağı çok zaman zaman alan bir kodlama gerektirir.
Sonuçlar
Factory Method kalıbı, nesne yaratma işinin kontrolünü sisteme bırakarak, clientın sadece kullanmak istediği nesneleri istemekle yetinebileceğini göstermektedir. Bir nesneyi kullanmayı bilmek o nesnenin arayüzünü (interface) bilmek anlamına gelmektedir. Yapılandırıcılar çoğu kez karmaşık metotlardır ve çağrılabilmeleri için pek çok bilgiye ihtiyaç duyarlar. Bu bilgileri toparlamanın sorumluluğu olabildiğince client tarafında olmamalıdır. Client, nasıl yaratıldığını bilmeden nenelerle haberleşebilmelidir. Bu kalıp ile bu esneklik sağlanır.
Aynı zamanda client da Product nesnelerinin gerçek tiplerini bilmek zorunda olmadan onlarla haberleşebilmektedir: Polymorphism!
Kullanımlar
Java APIlerinde Factory Method’un pek çok kullanımı vardır.
- java.sql paketinde Connection üzerinde createStatement() metotları farklı Statement nesneleri üretilir.
- Aynı paketteki Statement üzerindeki executeQuery() metodu ile de ResultSet nesnesi üretilir.
- javax.persistence paketinde EntityManagerFactory üzerindeki createEntityManager() metotları EntityManager nesneleri üretir.
Bazen factory metodunun statik olduğu da görülür:
- java.sql.Driver getConnection()
- javax.persistence.Persistence createEntityManagerFactory()
- java.util.Calendar getInstance()
- java.util.ResourceBundle getBundle()
- java.text.NumberFormat getInstance()
Buradaki amaç factory methodunun override, içindeki sınıfın da extend edilmesini önlemektir.
Diğer Kalıplarla İlişkiler
Factory Method, Abstract Factory kalıbında sıklıkla kullanılır. Somut sınıfla gerçekleştirilmesi durumunda Factory Method, bir Singleton içinde bulunabilir.
Toplam görüntülenme sayısı: 2334
isimsiz
15 Mart 2015 @ 15:20
Hocam özellikle tasarım kalıpları serinizin çok faydasını görüyorum. Çok teşekkür ediyorum. Kesinlikle devamını bekliyorum
Akin
15 Mart 2015 @ 16:18
Tesekkur ederim ama bir de isminizi alabilseydim 🙂
Erdem
07 Eylül 2016 @ 10:34
Selamlar Akın Bey,
Yazılarınız çok bilgilendirici ve doyurucu. Okumak gerçekten keyif veriyor. Özellikle tasarım kalıpları ve OOP yazılarınız gerçekten de ufkumu açtı. Tavsiyeniz üzerine GoF’un kitabını da alacağım. Singleton başlığını bitirdikten sonra eski yazdığım projelerin kodlarına bakıp yeni düzenlemeler bile yaptım (Singleton’ın aslında ne olduğunu bilmeden kullanmışım) 🙂 Bu yazınız hakkında da ufak bir sorum olacak. Her Product için ProductFactory yazmak yerine global bir Factory sınıfı oluştursak ve bu sınıf içerisindeki karar yapısıyla değer döndürmenin tasarımsal bir sakıncası var mıdır? Yukarıda yaptığınız örnekler üzerine düşünürken kodumu farklı bir şekilde düzenledim farklı olan kısımlar :
public interface ImplFactory {
public ImplProduct create(int type);
}
******************************************************
public class Factory implements ImplFactory{
@Override
public ImplProduct create(int product_type) {
if (product_type == 1) {
ProductA productA = new ProductA(1, “Product-A-1”);
return productA;
} else {
ProductB productB = new ProductB(2, “Product-B-2”);
return productB;
}
}
}
******************************************************
public class Client {
private ProductA productA;
private ProductB productB;
private Factory factory;
public Client(ImplFactory imlFactory){
if ( factory == null ) {
factory = new Factory();
productA = (ProductA) factory.create(1);
productB = (ProductB) factory.create(2);
}
}
public void start(){
productA.doThis();
productB.doThis();
}
}
******************************************************
public class Snippet {
public static void main(String[] args) {
Client client = new Client(new Factory());
client.start();
}
}
Erdem
07 Eylül 2016 @ 12:56
Kusura bakmayın bir yanlışlık gördüm Factory class’ının constructoru şu şekilde olması gerekiyor :
public Client(Factory factory) {
productA = (ProductA) factory.create(1);
productB = (ProductB) factory.create(2);
}
Akin
12 Eylül 2016 @ 16:28
Erdem bey, ilginize ve zaman ayırıp bana mesaj gondermenize tesekkur ederim. Lakin bahsettiğiniz hatayı ben bulamadım. Ben o yazıda hic bir “create()” metoduna “int” bbir değer geçmyorum zaten, hep gerektiğinde String gecmişim. Dhaa detaylı ayzabilir misinzi hatayı?
Selamlar,
Akin
12 Eylül 2016 @ 17:10
Merhaba Erdem bey,
Aslında bahsettiginzi durum, verdigim orneklerden son ikisinde ifade ediliyor. Bu yaklasımın problemi yazıda da ifade edildiği gibi soyledir:
“Bu yaklaşımın problemi, büyüyebilen (scalable) olmamasıdır. Yani oluşturulacak nesnelerin sayısı arttıkça, iflerin sayısı da artacaktır. Dolayısıyla bu çözüm zaten Open-Closed Principle ( OCP)’a aykırıdır. Hatta bir müddet sonra ilgili-ilgisiz her türlü nesnenin kendisiyle oluşturulduğu devasa bir metota sahip olabilirsiniz. Dolayısıyla bu yaklaşım bir ters-kalıptır (anti-pattern). Eğer birbiriyle ilgili birden fazla nesneyi yaratma probleminiz varsa bu durumda zaten Abstract Factory kullanılabilir.”
Selamlar,
Erdem
19 Eylül 2016 @ 12:21
Selamlar Akın bey,
Öncelikle hata benim size yazdığım ilk mesajdaki kod üzerinde vardı. Bahsettiğim hata o idi.Sizin yazınızda herhangi bir hata yok.
Cevabınız gerçekten çok açıklayıcı ve net olmuş. İlgilenip cevap verdiğiniz için teşekkür ederim. Blogunuz yazdığım kod ve hazırladığım projeler için bana yeni bir kapı açtı ve bakış açısı kattı. Epeydir öğrenmem gerektiğini düşündüğüm konuları bir an önce öğrenmem gerektiğimin farkına vardım.
Gerek kaynak kitap tavsiyeleriniz, paylaştığınız bilgiler ve yaptığınız yorumlar çok iyi. Genç bir yazılım mühendisi olarak blogunuzu severek takip ediyorum.
Akin
20 Eylül 2016 @ 16:11
İlginize ve guzel dusuncelerinize mutesekkirim. İyi işleri arttırmaya devam etmek lazım.
Selamlar,