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ı: 2328