Java’da Factory Tasarım Deseni: Esnek ve Ölçeklenebilir Uygulamalar Geliştirmek
Giriş
Yazılım geliştirme süreçleri, günümüzün karmaşık ve sürekli değişen iş ihtiyaçlarına cevap verebilmek için esneklik, genişletilebilirlik ve sürdürülebilirlik gibi temel prensiplere odaklanmaktadır. Bu prensiplere ulaşmada tasarım desenleri (design patterns) kritik bir rol oynar. Tasarım desenleri, yazılım geliştirmede karşılaşılan yaygın sorunlara kanıtlanmış, yeniden kullanılabilir çözümler sunar. Bu desenler arasında nesne oluşturma (creational) desenleri, uygulamaların temel yapı taşları olan nesnelerin nasıl oluşturulacağı ve yönetileceği konusunda rehberlik eder. Bu makalede, nesne oluşturma desenlerinin en popüler ve yaygın kullanılanlarından biri olan Factory (Fabrika) tasarım desenini Java bağlamında detaylıca inceleyeceğiz. Factory deseni, nesne oluşturma mantığını istemci koddan soyutlayarak daha temiz, daha esnek ve daha ölçeklenebilir uygulamalar geliştirilmesine olanak tanır.
Tasarım Desenleri Nedir ve Neden Önemlidir?
Tasarım desenleri, yazılım mimarları Erich Gamma, Richard Helm, Ralph Johnson ve John Vlissides tarafından 1994 yılında yayınlanan “Design Patterns: Elements of Reusable Object-Oriented Software” adlı kitapla popüler hale gelmiştir. Bu yazarlara “Gang of Four” (GoF) adı verilmiştir. GoF, desenleri üç ana kategoriye ayırmıştır:
* Creational (Oluşturucu) Desenler: Nesnelerin nasıl oluşturulduğunu soyutlar ve bu süreci kontrol eder. Factory, Singleton, Builder, Prototype, Abstract Factory bu kategoriye girer.
* Structural (Yapısal) Desenler: Sınıfların ve nesnelerin daha büyük yapılar oluşturmak için nasıl bir araya getirileceğini ele alır. Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy bu kategoriye girer.
* Behavioral (Davranışsal) Desenler: Nesneler arasındaki iletişimi ve sorumluluk dağılımını yönetir. Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor bu kategoriye girer.
Tasarım desenleri kullanmak, yazılım projelerine birçok fayda sağlar:
* Kanıtlanmış Çözümler: Yaygın sorunlara endüstri standardı çözümler sunar.
* Kodun Anlaşılırlığı ve Okunabilirliği: Desenler, geliştiriciler arasında ortak bir dil ve yapı sağlar.
* Sürdürülebilirlik ve Genişletilebilirlik: Kodun gelecekteki değişikliklere daha az direnç göstermesini sağlar.
* Gevşek Bağlılık (Loose Coupling): Bileşenler arasındaki bağımlılıkları azaltarak sistemin daha esnek olmasını sağlar.
* Yeniden Kullanılabilirlik: Desenler, kodun farklı projelerde veya farklı bağlamlarda yeniden kullanılmasına olanak tanır.
Factory Tasarım Deseni Nedir?
Factory tasarım deseni, Creational desenler kategorisine ait olup, nesne oluşturma sorumluluğunu istemci koddan ayırmayı hedefler. Temel prensibi, bir nesne oluşturma arayüzü tanımlamak, ancak hangi somut sınıfın örnekleneceğine alt sınıfların karar vermesini sağlamaktır. Bu, istemcinin belirli bir sınıfı doğrudan new anahtar kelimesiyle örneklendirme ihtiyacını ortadan kaldırır. Bunun yerine, istemci bir fabrika (factory) nesnesiyle etkileşime girer ve bu fabrika, istemcinin gereksinimlerine göre doğru nesneyi oluşturur ve döndürür.
Bu soyutlama, özellikle aşağıdaki durumlarda büyük faydalar sağlar:
* Uygulamanın, farklı somut sınıflardan nesneler oluşturması gerektiğinde ve bu somut sınıflar gelecekte değişebileceğinde veya yenileri eklenebileceğinde.
* Nesne oluşturma sürecinin karmaşık olduğu, birden fazla adımdan oluştuğu veya belirli parametrelere bağlı olduğu durumlarda.
* İstemci kodun, oluşturulan nesnelerin somut türleri hakkında bilgi sahibi olmasını istemediğimizde, sadece ortak bir arayüz veya soyut sınıf üzerinden onlarla etkileşim kurmasını istediğimizde.
Factory deseni, “Open/Closed Prensibi”ne (bir yazılım varlığı genişletmeye açık olmalı ancak değiştirmeye kapalı olmalı) uyum sağlamaya yardımcı olur. Yeni bir ürün tipi eklendiğinde, mevcut istemci kodunu değiştirmeye gerek kalmadan sadece yeni bir somut ürün sınıfı ve/veya fabrika sınıfı eklenmesi yeterli olabilir.
Factory Deseni’nin Bileşenleri
Factory deseninin farklı türleri olsa da, temel olarak aşağıdaki bileşenleri içerir:
* Ürün (Product) Arayüzü/Soyut Sınıfı: Fabrika tarafından oluşturulacak tüm nesnelerin uygulayacağı ortak arayüzü veya miras alacağı soyut sınıfı tanımlar. Bu, istemci kodun somut ürün sınıflarını bilmeden ürünlerle çalışmasını sağlar.
* Somut Ürünler (Concrete Products): Ürün arayüzünü uygulayan veya soyut sınıfı genişleten gerçek nesne sınıflarıdır. Fabrika bu sınıfların örneklerini oluşturur.
* Fabrika (Factory) Arayüzü/Soyut Sınıfı (Opsiyonel): Eğer birden fazla fabrika türü olacaksa, fabrikalar için ortak bir arayüz veya soyut sınıf tanımlanabilir. Bu, fabrika oluşturma mantığının kendisini soyutlamak için kullanılır.
* Somut Fabrika (Concrete Factory): Ürün arayüzünü uygulayan somut ürünleri oluşturan sınıftır. İstemci genellikle bu fabrika ile etkileşime girer.
* İstemci (Client): Fabrika nesnesini kullanarak gerekli ürün nesnesini talep eden ve onunla çalışan koddur. İstemci, somut ürün sınıflarını doğrudan bilmez, sadece ürün arayüzü üzerinden çalışır.
Factory Deseni’nin Türleri
Factory deseni genellikle üç ana varyasyonda karşımıza çıkar: Simple Factory, Factory Method ve Abstract Factory. Her birinin kendine özgü kullanım senaryoları ve karmaşıklık seviyeleri vardır.
Simple Factory (Basit Fabrika)
Simple Factory, resmi bir GoF deseni olmasa da, diğer factory desenlerinin temelini oluşturan ve sıklıkla kullanılan bir yaklaşımdır. Tek bir sınıfın, belirli parametrelere göre farklı türde nesneler oluşturma sorumluluğunu üstlenmesidir. Bu fabrika sınıfı genellikle statik bir metot içerir veya bir Singleton olarak uygulanır.
Avantajları:
* Uygulaması oldukça basittir.
* Nesne oluşturma mantığını merkezileştirir.
* İstemci kodunu somut ürün sınıflarından ayırır.
Dezavantajları:
* Yeni bir ürün türü eklendiğinde fabrika sınıfının değiştirilmesi gerekir (if/else veya switch bloğuna yeni bir durum eklenmesi), bu da Open/Closed Prensibini ihlal eder.
* Fabrika sınıfı, tüm ürün türleri hakkında bilgi sahibi olmak zorunda kalır ve bu da “God Object” (Tanrı Nesnesi) riskini artırır.
* Büyük sistemlerde bakımı zorlaşabilir.
Ne Zaman Kullanılır:
* Ürün türlerinin sayısı az ve değişme olasılığı düşük olduğunda.
* Nesne oluşturma mantığının çok karmaşık olmadığı durumlarda.
Factory Method Deseni (Fabrika Metodu)
Factory Method, GoF’un Creational desenlerinden biridir. Bu desende, bir süper sınıf (creator), nesne oluşturmak için bir arayüz tanımlar, ancak hangi somut sınıfın örneğini oluşturacağına alt sınıflar (concrete creators) karar verir. Süper sınıf, alt sınıfların örnekleneceği sınıfı ertelemiş olur. Bu, istemcinin bir nesneyi doğrudan örneklendirmek yerine, bir fabrika metodunu çağırması anlamına gelir.
Yapısal Bileşenler:
* Product (Ürün): Fabrika metodunun oluşturduğu nesnelerin arayüzü veya soyut sınıfı.
* ConcreteProduct (Somut Ürün): Product arayüzünü uygulayan somut sınıflar.
* Creator (Oluşturucu): Fabrika metodunu (nesne oluşturma metodu) tanımlayan soyut sınıf veya arayüz. Bu metodun dönüş tipi Product’tır. Creator sınıfı, genellikle fabrika metodunu çağıran diğer metotları da içerir.
* ConcreteCreator (Somut Oluşturucu): Creator sınıfını genişleten ve fabrika metodunu uygulayarak belirli bir ConcreteProduct örneği döndüren sınıftır.
Avantajları:
* Open/Closed Prensibi’ne Uyum: Yeni bir ürün türü eklendiğinde, mevcut fabrika metodunu içeren sınıfları değiştirmeye gerek kalmaz; sadece yeni bir ConcreteProduct ve ona karşılık gelen yeni bir ConcreteCreator sınıfı eklenir.
* Gevşek Bağlılık: İstemci kodu, somut ürün sınıflarından tamamen ayrılır.
* Esneklik: Alt sınıflara, oluşturulacak nesnenin türü üzerinde kontrol sağlar.
* Tek Sorumluluk Prensibi: Nesne oluşturma sorumluluğunu, nesnenin kendisinden ayırır.
Dezavantajları:
* Karmaşıklık: Simple Factory’ye göre daha fazla sınıf ve arayüz gerektirir, bu da kod tabanını daha karmaşık hale getirebilir.
* Öğrenme Eğrisi: Yeni başlayanlar için kavraması ve uygulaması daha zor olabilir.
Ne Zaman Kullanılır:
* Bir sınıfın, hangi alt sınıfların örneğini oluşturması gerektiğini önceden bilmediği durumlarda.
* Bir kütüphane veya framework geliştirirken, kullanıcıların kendi bileşenlerini genişletmesini ve kendi özel ürünlerini oluşturmasını istediğinizde.
* Nesne oluşturma sürecini alt sınıflara devretmek istediğinizde.
Abstract Factory Deseni (Soyut Fabrika)
Abstract Factory, GoF’un bir diğer Creational desenidir ve “fabrikaların fabrikası” olarak da bilinir. Bu desen, birbiriyle ilişkili veya bağımlı nesne ailelerini, somut sınıflarını belirtmeksizin oluşturmak için bir arayüz sağlar. Yani, bir Abstract Factory, birden fazla Factory Method’u içerebilir ve bu factory method’lar, birbiriyle tutarlı bir ürün ailesi oluşturur.
Yapısal Bileşenler:
* AbstractProduct (Soyut Ürün): Her ürün ailesindeki ürünler için soyut arayüzler (örneğin, Button, Checkbox).
* ConcreteProduct (Somut Ürün): AbstractProduct arayüzlerini uygulayan somut ürün sınıfları (örneğin, WindowsButton, MacButton).
* AbstractFactory (Soyut Fabrika): Her ürün ailesi için bir Factory Method tanımlayan soyut arayüz. Bu metotlar, AbstractProduct türünde nesneler döndürür.
* ConcreteFactory (Somut Fabrika): AbstractFactory arayüzünü uygulayan ve belirli bir ürün ailesine ait ConcreteProduct’ları oluşturan sınıflar (örneğin, WindowsFactory, MacFactory).
* Client (İstemci): AbstractFactory arayüzü üzerinden fabrika ile etkileşime girer ve ürünleri oluşturur. İstemci, somut fabrika veya somut ürün sınıflarını bilmez.
Avantajları:
* Ürün Ailelerinin Tutarlılığı: İstemcinin her zaman birbiriyle uyumlu ürünler ailesi kullanmasını garanti eder.
* İstemciyi Somut Sınıflardan Tamamen Ayırma: İstemci, oluşturulan ürünlerin somut türleri hakkında hiçbir bilgiye sahip değildir.
* Yeni Ürün Aileleri Ekleme Kolaylığı: Yeni bir ürün ailesi eklemek, mevcut istemci kodunu etkilemez; sadece yeni bir ConcreteFactory ve ConcreteProduct sınıfları eklenir.
Dezavantajları:
* Yeni Ürün Türleri Ekleme Zorluğu: Mevcut bir ürün ailesine yeni bir ürün türü (örneğin, Radio Button eklemek) eklemek, AbstractFactory arayüzünü ve tüm ConcreteFactory uygulamalarını değiştirmeyi gerektirir. Bu, Open/Closed Prensibi’ne aykırı olabilir.
* Karmaşıklık: En karmaşık Factory desenidir ve çok sayıda sınıf ve arayüz gerektirebilir.
Ne Zaman Kullanılır:
* Bir sistemin, birbiriyle ilişkili veya bağımlı nesne ailelerinden birini seçmesi gerektiğinde.
* Bir ürün ailesinin, somut sınıflarını belirtmeden oluşturulması gerektiğinde.
* Bir sistemin, farklı platformlar veya konfigürasyonlar için farklı davranışlar sergileyen ürünler ailesi kullanması gerektiğinde (örneğin, farklı işletim sistemleri için UI bileşenleri).
Kod Örnekleri
Şimdi bu üç Factory deseni türünü, bir “Pizza Sipariş Sistemi” senaryosu üzerinden Java kod örnekleriyle inceleyelim.
Simple Factory Örneği
Bu örnekte, SimplePizzaFactory sınıfı, farklı pizza türlerini oluşturma sorumluluğunu üstlenir.
// 1. Ürün Arayüzü
interface Pizza {
void prepare();
void bake();
void cut();
void box();
}
// 2. Somut Ürünler
class CheesePizza implements Pizza {
@Override
public void prepare() {
System.out.println("Peynirli Pizza hazırlanıyor...");
}
@Override
public void bake() {
System.out.println("Peynirli Pizza pişiriliyor...");
}
@Override
public void cut() {
System.out.println("Peynirli Pizza kesiliyor...");
}
@Override
public void box() {
System.out.println("Peynirli Pizza kutulanıyor...");
}
}
class VeggiePizza implements Pizza {
@Override
public void prepare() {
System.out.println("Vejetaryen Pizza hazırlanıyor...");
}
@Override
public void bake() {
System.out.println("Vejetaryen Pizza pişiriliyor...");
}
@Override
public void cut() {
System.out.println("Vejetaryen Pizza kesiliyor...");
}
@Override
public void box() {
System.out.println("Vejetaryen Pizza kutulanıyor...");
}
}
// 3. Basit Fabrika
class SimplePizzaFactory {
public Pizza createPizza(String type) {
Pizza pizza = null;
if (type.equals("cheese")) {
pizza = new CheesePizza();
} else if (type.equals("veggie")) {
pizza = new VeggiePizza();
}
// Yeni pizza türleri eklendiğinde bu metodun değiştirilmesi gerekir.
return pizza;
}
}
// 4. İstemci
class PizzaStore {
SimplePizzaFactory factory;
public PizzaStore(SimplePizzaFactory factory) {
this.factory = factory;
}
public Pizza orderPizza(String type) {
Pizza pizza;
pizza = factory.createPizza(type); // Fabrika kullanarak pizza oluşturulur
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
}
// Kullanım
public class SimpleFactoryDemo {
public static void main(String[] args) {
SimplePizzaFactory factory = new SimplePizzaFactory();
PizzaStore store = new PizzaStore(factory);
System.out.println("--- Peynirli Pizza Siparişi ---");
Pizza cheesePizza = store.orderPizza("cheese");
System.out.println("Sipariş edilen: " + cheesePizza.getClass().getSimpleName() + "\n");
System.out.println("--- Vejetaryen Pizza Siparişi ---");
Pizza veggiePizza = store.orderPizza("veggie");
System.out.println("Sipariş edilen: " + veggiePizza.getClass().getSimpleName() + "\n");
}
}
Factory Method Örneği
Bu örnekte, farklı pizza dükkanları (New York, Chicago) kendi pizza türlerini oluşturmak için kendi fabrika metotlarını uygular.
// 1. Ürün Arayüzü (Simple Factory'deki ile aynı kalabilir)
// interface Pizza { ... }
// class CheesePizza implements Pizza { ... }
// class VeggiePizza implements Pizza { ... }
// Yukarıdaki Pizza arayüzü ve somut sınıflar burada da kullanılabilir.
// Yeniden tanımlamak yerine var olduğunu varsayalım.
// 2. Soyut Oluşturucu (Creator)
abstract class PizzaStoreFM {
public Pizza orderPizza(String type) {
Pizza pizza;
pizza = createPizza(type); // Fabrika metodu çağrılır
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
// Bu, Factory Metodudur. Alt sınıflar bunu uygulayarak hangi pizzayı oluşturacaklarına karar verir.
protected abstract Pizza createPizza(String type);
}
// 3. Somut Oluşturucular (Concrete Creators)
class NYPizzaStore extends PizzaStoreFM {
@Override
protected Pizza createPizza(String type) {
if (type.equals("cheese")) {
return new NYStyleCheesePizza(); // NY tarzı peynirli pizza
} else if (type.equals("veggie")) {
return new NYStyleVeggiePizza(); // NY tarzı vejetaryen pizza
}
return null;
}
}
class ChicagoPizzaStore extends PizzaStoreFM {
@Override
protected Pizza createPizza(String type) {
if (type.equals("cheese")) {
return new ChicagoStyleCheesePizza(); // Chicago tarzı peynirli pizza
} else if (type.equals("veggie")) {
return new ChicagoStyleVeggiePizza(); // Chicago tarzı vejetaryen pizza
}
return null;
}
}
// 4. Somut Ürünler (Concrete Products) - NY ve Chicago tarzı pizzalar
class NYStyleCheesePizza implements Pizza {
public NYStyleCheesePizza() {
System.out.println("NY Style Peynirli Pizza oluşturuluyor.");
}
@Override public void prepare() { System.out.println("NY Peynirli Pizza hazırlanıyor..."); }
@Override public void bake() { System.out.println("NY Peynirli Pizza pişiriliyor..."); }
@Override public void cut() { System.out.println("NY Peynirli Pizza kesiliyor..."); }
@Override public void box() { System.out.println("NY Peynirli Pizza kutulanıyor..."); }
}
class NYStyleVeggiePizza implements Pizza {
public NYStyleVeggiePizza() {
System.out.println("NY Style Vejetaryen Pizza oluşturuluyor.");
}
@Override public void prepare() { System.out.println("NY Vejetaryen Pizza hazırlanıyor..."); }
@Override public void bake() { System.out.println("NY Vejetaryen Pizza pişiriliyor..."); }
@Override public void cut() { System.out.println("NY Vejetaryen Pizza kesiliyor..."); }
@Override public void box() { System.out.println("NY Vejetaryen Pizza kutulanıyor..."); }
}
class ChicagoStyleCheesePizza implements Pizza {
public ChicagoStyleCheesePizza() {
System.out.println("Chicago Style Peynirli Pizza oluşturuluyor.");
}
@Override public void prepare() { System.out.println("Chicago Peynirli Pizza hazırlanıyor..."); }
@Override public void bake() { System.out.println("Chicago Peynirli Pizza pişiriliyor..."); }
@Override public void cut() { System.out.println("Chicago Peynirli Pizza kesiliyor..."); }
@Override public void box() { System.out.println("Chicago Peynirli Pizza kutulanıyor..."); }
}
class ChicagoStyleVeggiePizza implements Pizza {
public ChicagoStyleVeggiePizza() {
System.out.println("Chicago Style Vejetaryen Pizza oluşturuluyor.");
}
@Override public void prepare() { System.out.println("Chicago Vejetaryen Pizza hazırlanıyor..."); }
@Override public void bake() { System.out.println("Chicago Vejetaryen Pizza pişiriliyor..."); }
@Override public void cut() { System.out.println("Chicago Vejetaryen Pizza kesiliyor..."); }
@Override public void box() { System.out.println("Chicago Vejetaryen Pizza kutulanıyor..."); }
}
// Kullanım
public class FactoryMethodDemo {
public static void main(String[] args) {
PizzaStoreFM nyStore = new NYPizzaStore();
PizzaStoreFM chicagoStore = new ChicagoPizzaStore();
System.out.println("--- NY Peynirli Pizza Siparişi ---");
Pizza pizza = nyStore.orderPizza("cheese");
System.out.println("Ethan sipariş etti: " + pizza.getClass().getSimpleName() + "\n");
System.out.println("--- Chicago Vejetaryen Pizza Siparişi ---");
pizza = chicagoStore.orderPizza("veggie");
System.out.println("Joel sipariş etti: " + pizza.getClass().getSimpleName() + "\n");
}
}
Abstract Factory Örneği
Bu örnekte, farklı pizza dükkanları (NY, Chicago) farklı içerik fabrikalarını (ingredient factories) kullanarak kendi tarzlarına uygun pizza bileşenlerini (hamur, sos, peynir) üretir.
// 1. Soyut Ürünler (Pizza Bileşenleri)
interface Dough { String toString(); }
interface Sauce { String toString(); }
interface Cheese { String toString(); }
// 2. Somut Ürünler (NY ve Chicago tarzı bileşenler)
class ThinCrustDough implements Dough { public String toString() { return "İnce Hamur"; } }
class ThickCrustDough implements Dough { public String toString() { return "Kalın Hamur"; } }
class MarinaraSauce implements Sauce { public String toString() { return "Marinara Sos"; } }
class PlumTomatoSauce implements Sauce { public String toString() { return "Erik Domates Sos"; } }
class ReggianoCheese implements Cheese { public String toString() { return "Reggiano Peyniri"; } }
class MozzarellaCheese implements Cheese { public String toString() { return "Mozzarella Peyniri"; } }
// 3. Soyut Fabrika (Pizza Malzeme Fabrikası)
interface PizzaIngredientFactory {
Dough createDough();
Sauce createSauce();
Cheese createCheese();
}
// 4. Somut Fabrikalar (NY ve Chicago Malzeme Fabrikaları)
class NYPizzaIngredientFactory implements PizzaIngredientFactory {
@Override public Dough createDough() { return new ThinCrustDough(); }
@Override public Sauce createSauce() { return new MarinaraSauce(); }
@Override public Cheese createCheese() { return new ReggianoCheese(); }
}
class ChicagoPizzaIngredientFactory implements PizzaIngredientFactory {
@Override public Dough createDough() { return new ThickCrustDough(); }
@Override public Sauce createSauce() { return new PlumTomatoSauce(); }
@Override public Cheese createCheese() { return new MozzarellaCheese(); }
}
// 5. Soyut Pizza Sınıfı (Malzeme Fabrikasını kullanan)
abstract class PizzaAF {
String name;
Dough dough;
Sauce sauce;
Cheese cheese;
abstract void prepare(); // Bu metot, malzemelerin nasıl hazırlanacağını belirler
void bake() { System.out.println("15 dakika 350 derecede pişiriliyor."); }
void cut() { System.out.println("Pizzayı dilimlere ayırın."); }
void box() { System.out.println("Pizzayı resmi PizzaStore kutusuna koyun."); }
public void setName(String name) { this.name = name; }
public String getName() { return name; }
public String toString() {
return "---- " + name + " ----\n" +
dough + "\n" +
sauce + "\n" +
cheese + "\n";
}
}
// 6. Somut Pizzalar (Malzeme Fabrikası kullanarak kendi malzemelerini belirler)
class CheesePizzaAF extends PizzaAF {
PizzaIngredientFactory ingredientFactory;
public CheesePizzaAF(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
@Override
void prepare() {
System.out.println("Hazırlanıyor: " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
}
}
class VeggiePizzaAF extends PizzaAF {
PizzaIngredientFactory ingredientFactory;
public VeggiePizzaAF(PizzaIngredientFactory ingredientFactory) {
this.ingredientFactory = ingredientFactory;
}
@Override
void prepare() {
System.out.println("Hazırlanıyor: " + name);
dough = ingredientFactory.createDough();
sauce = ingredientFactory.createSauce();
cheese = ingredientFactory.createCheese();
// Ekstra sebzeler eklenebilir
}
}
// 7. Soyut Pizza Dükkanı (Abstract Factory'yi kullanan istemci)
abstract class PizzaStoreAF {
public PizzaAF orderPizza(String type) {
PizzaAF pizza = createPizza(type); // Alt sınıflar pizzayı oluşturur
pizza.prepare();
pizza.bake();
pizza.cut();
pizza.box();
return pizza;
}
protected abstract PizzaAF createPizza(String type); // Fabrika metodu
}
// 8. Somut Pizza Dükkanları
class NYPizzaStoreAF extends PizzaStoreAF {
@Override
protected PizzaAF createPizza(String item) {
PizzaAF pizza = null;
PizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory(); // NY tarzı malzeme fabrikası
if (item.equals("cheese")) {
pizza = new CheesePizzaAF(ingredientFactory);
pizza.setName("New York Tarzı Peynirli Pizza");
} else if (item.equals("veggie")) {
pizza = new VeggiePizzaAF(ingredientFactory);
pizza.setName("New York Tarzı Vejetaryen Pizza");
}
return pizza;
}
}
class ChicagoPizzaStoreAF extends PizzaStoreAF {
@Override
protected PizzaAF createPizza(String item) {
PizzaAF pizza = null;
PizzaIngredientFactory ingredientFactory = new ChicagoPizzaIngredientFactory(); // Chicago tarzı malzeme fabrikası
if (item.equals("cheese")) {
pizza = new CheesePizzaAF(ingredientFactory);
pizza.setName("Chicago Tarzı Peynirli Pizza");
} else if (item.equals("veggie")) {
pizza = new VeggiePizzaAF(ingredientFactory);
pizza.setName("Chicago Tarzı Vejetaryen Pizza");
}
return pizza;
}
}
// Kullanım
public class AbstractFactoryDemo {
public static void main(String[] args) {
PizzaStoreAF nyStore = new NYPizzaStoreAF();
PizzaStoreAF chicagoStore = new ChicagoPizzaStoreAF();
System.out.println("--- NY Peynirli Pizza Siparişi ---");
PizzaAF pizza = nyStore.orderPizza("cheese");
System.out.println("Ethan sipariş etti: " + pizza.getName() + "\n" + pizza);
System.out.println("--- Chicago Vejetaryen Pizza Siparişi ---");
pizza = chicagoStore.orderPizza("veggie");
System.out.println("Joel sipariş etti: " + pizza.getName() + "\n" + pizza);
}
}
Factory Deseni’nin Avantajları
Factory tasarım deseni, yazılım geliştirmede birçok önemli avantaj sunar:
* Gevşek Bağlılık (Loose Coupling): En önemli avantajlarından biridir. İstemci kodu, somut ürün sınıflarını doğrudan örneklendirmek yerine bir fabrika aracılığıyla nesne talep eder. Bu, istemcinin somut sınıflar hakkında bilgi sahibi olmasını engeller ve bileşenler arasındaki bağımlılığı azaltır. Bağımlılık azaldıkça, sistemin bir parçası değiştiğinde diğer parçaların etkilenme olasılığı azalır.
* Esneklik ve Genişletilebilirlik: Yeni ürün türleri veya ürün aileleri eklendiğinde, mevcut istemci kodunu değiştirmeye gerek kalmaz. Özellikle Factory Method ve Abstract Factory desenlerinde, sadece yeni bir somut ürün sınıfı ve/veya fabrika sınıfı eklenmesi yeterlidir. Bu, uygulamanın gelecekteki değişikliklere daha kolay adapte olmasını sağlar.
* Open/Closed Prensibi’ne Uyum: Yazılım varlıklarının genişletmeye açık, değiştirmeye kapalı olması gerektiğini belirten bu prensibe uyum sağlar. Yeni bir ürün tipi eklemek, mevcut fabrika veya istemci kodunu değiştirmeyi gerektirmez, sadece yeni sınıflar eklenir.
* Kodun Okunabilirliği ve Bakımı: Nesne oluşturma mantığını tek bir yerde (fabrika sınıfında) merkezileştirir. Bu, kodun daha düzenli ve anlaşılır olmasını sağlar. Karmaşık nesne oluşturma süreçleri, fabrika içinde kapsüllendiği için istemci kod daha temiz kalır.
* Test Edilebilirlik: Gevşek bağlılık sayesinde, test yazarken gerçek nesneler yerine mock veya stub nesneler kullanılabilir. Bu, birim testlerini daha kolay ve izole hale getirir. Örneğin, bir fabrika arayüzünü taklit ederek, istemci kodun doğru nesneleri oluşturup oluşturmadığını test edebilirsiniz.
* Sorumlulukların Ayrılması (Separation of Concerns): Nesne oluşturma sorumluluğunu, nesnelerin iş mantığı sorumluluğundan ayırır. Her sınıfın tek bir sorumluluğu olması gerektiğini belirten Tek Sorumluluk Prensibi’ne (Single Responsibility Principle) uyumu artırır.
Factory Deseni’nin Dezavantajları
Factory deseni birçok fayda sağlasa da, bazı dezavantajları da bulunmaktadır:
* Karmaşıklık: Özellikle Simple Factory dışındaki Factory Method ve Abstract Factory desenleri, daha fazla sınıf ve arayüz gerektirir. Basit senaryolarda bu durum, gereksiz “aşırı mühendislik” (over-engineering) olarak algılanabilir ve kod tabanını gereksiz yere karmaşık hale getirebilir.
* Öğrenme Eğrisi: Tasarım desenlerine yeni başlayan geliştiriciler için, özellikle Abstract Factory gibi daha soyut desenleri kavramak ve doğru bir şekilde uygulamak zor olabilir.
* Soyutlama Maliyeti: Ek soyutlama katmanları, kodun takip edilmesini ve hata ayıklamasını bazen daha zor hale getirebilir. Her zaman bir fabrika kullanmak yerine, basit durumlarda doğrudan new anahtar kelimesini kullanmak daha pratik olabilir.
* Abstract Factory’de Yeni Ürün Türü Ekleme Zorluğu: Abstract Factory deseninin temel dezavantajlarından biri, mevcut bir ürün ailesine yeni bir ürün türü eklendiğinde ortaya çıkar. Bu durumda, AbstractFactory arayüzünün ve tüm ConcreteFactory uygulamalarının değiştirilmesi gerekir. Bu, Open/Closed Prensibi’ne aykırı bir durum oluşturur ve ciddi bakım maliyetlerine yol açabilir.
Ne Zaman Kullanmalı?
Factory tasarım desenini ne zaman kullanacağınıza karar vermek, projenizin ihtiyaçlarına ve karmaşıklığına bağlıdır:
* Bir sınıfın hangi alt sınıfların örneğini oluşturması gerektiğini önceden bilemediğinizde: Programın çalışma zamanında hangi somut sınıfın örneğini oluşturacağına karar vermesi gerektiği durumlarda (örneğin, kullanıcı girdisine, konfigürasyon ayarlarına veya ortam değişkenlerine göre).
* Bir kütüphane veya framework geliştirirken: Kullanıcıların kendi bileşenlerini (ürünlerini) genişletmesini ve sizin kodunuzun bu bileşenleri standart bir arayüz üzerinden kullanmasını istediğinizde.
* Birbiriyle ilişkili nesne aileleri oluşturmanız gerektiğinde (Abstract Factory): Birbirine bağımlı birden fazla ürün türünü bir arada oluşturmanız ve bu ürünlerin her zaman tutarlı bir aileye ait olmasını garanti etmeniz gerektiğinde.
* Nesne oluşturma sürecini istemci koddan ayırmak istediğinizde: İstemci kodun, oluşturulan nesnelerin somut türleri hakkında bilgi sahibi olmasını istemediğinizde ve sadece ortak bir arayüz üzerinden onlarla etkileşim kurmasını istediğinizde.
* Tek bir sınıfın birden fazla alt sınıfından sorumlu olmasını istemediğinizde: new operatörünün yaygın kullanımını azaltarak, bir sınıfın çok fazla bağımlılığa sahip olmasını engellemek istediğinizde.
Diğer Tasarım Desenleriyle İlişkisi
Factory deseni, diğer GoF desenleriyle birlikte veya onların bir parçası olarak da kullanılabilir:
* Singleton: Bir fabrika sınıfının kendisi, sistemde yalnızca bir örneğinin bulunması gereken durumlarda Singleton deseni ile uygulanabilir. Bu, fabrika nesnesinin global bir erişim noktası olmasını sağlar.
* Prototype: Factory Method deseni, bazen Prototype deseni ile birlikte kullanılabilir. Fabrika metodu, yeni nesneler oluşturmak yerine, mevcut prototip nesneleri kopyalayarak (clone) döndürebilir. Bu, özellikle nesne oluşturma maliyetinin yüksek olduğu durumlarda faydalıdır.
* Builder: Hem Factory hem de Builder desenleri nesne oluşturma desenleridir. Factory deseni, genellikle tek bir adımda veya basit bir parametreye göre bir nesne oluştururken, Builder deseni karmaşık nesnelerin adım adım, farklı konfigürasyonlarla oluşturulmasını sağlar. Karmaşık bir nesne oluşturma süreci varsa Builder daha uygun olabilir; basit nesneler için Factory daha iyidir.
* Strategy: Fabrika, istemcinin bir strateji seçmesine izin verebilir ve bu stratejiyi uygulayan bir nesneyi döndürebilir. Örneğin, bir ödeme fabrika, farklı ödeme yöntemleri (kredi kartı, PayPal) için strateji nesneleri oluşturabilir.
Sonuç
Factory tasarım deseni, modern Java uygulamalarında esneklik, genişletilebilirlik ve sürdürülebilirlik sağlamak için vazgeçilmez bir araçtır. Simple Factory, Factory Method ve Abstract Factory olmak üzere farklı varyasyonları, nesne oluşturma sorumluluğunu istemci koddan soyutlayarak, uygulamaların daha gevşek bağlı ve yönetilebilir olmasına olanak tanır.
Doğru Factory desenini seçmek, projenin karmaşıklığına, ürün türlerinin sayısına ve gelecekteki değişiklik beklentilerine bağlıdır. Basit senaryolar için Simple Factory yeterli olabilirken, daha dinamik ve genişletilebilir sistemler için Factory Method veya Abstract Factory daha uygun olacaktır. Ancak, desenleri aşırı kullanmaktan kaçınmak ve her zaman en basit çözümü tercih etmek önemlidir.
Factory deseni, nesne yönelimli programlamanın temel prensiplerini (soyutlama, kapsülleme, Open/Closed Prensibi) uygulamamıza yardımcı olarak, daha robust, bakımı kolay ve ölçeklenebilir yazılım sistemleri geliştirmemizin önünü açar. Bu deseni ustalıkla kullanmak, yazılım mimarinizi önemli ölçüde güçlendirecek ve geliştirme süreçlerinizi daha verimli hale getirecektir.