Factory Pattern

Factory Pattern, nesneleri temiz ve esnek bir şekilde oluşturmak için kullanılan yaratıcı bir tasarım modelidir. Nesneleri doğrudan uygulamanın farklı bölümlerinde oluşturmak yerine Factory Pattern, nesne oluşturma mantığını ayrı bir fabrika sınıfına veya yöntemine taşır.

Bu model özellikle nesne oluşturmanın koşullara, yapılandırmaya, kullanıcı girişine, ortam ayarlarına veya iş kurallarına bağlı olduğu durumlarda kullanışlıdır. Bir nesneyi kullanan kodun o nesnenin tam olarak nasıl oluşturulduğunu bilmesine gerek olmadığından ana uygulama kodunun daha temiz tutulmasına yardımcı olur.

Giriş

Object-Oriented Programming'da new anahtar sözcüğüyle nesneler oluşturmak normal ve basittir. Ancak uygulamalar büyüdükçe nesne oluşturma daha karmaşık hale gelebilir. Bir sınıfın duruma bağlı olarak farklı bağımlılıklara, farklı konfigürasyon değerlerine veya farklı uygulamalara ihtiyacı olabilir.

Nesne oluşturma mantığı denetleyiciler, hizmetler, komutlar veya iş sınıfları arasında tekrarlanırsa kodun bakımı zorlaşır. Oluşturma sürecindeki herhangi bir değişikliğin birçok yerde güncellenmesi gerekir.

Factory Pattern, nesne oluşturmayı merkezileştirerek bu sorunu çözer. Uygulama fabrikadan doğru nesneyi yaratmasını ister ve fabrika hangi sınıfın somutlaştırılması gerektiğine karar verir.

Factory Pattern Nedir?

Factory Pattern, nesnelerin oluşturulmasından sorumlu bir yöntem veya sınıf sağlayan bir tasarım modelidir. client code nesneyi doğrudan oluşturmaz. Bunun yerine fabrikayı çağırır ve bilinen bir türü, arayüzü veya ana sınıfı izleyen bir nesneyi alır.

Basit anlamda fabrika bir üretim merkezi gibidir. Belirli bir nesne türü talep edersiniz ve fabrika doğru örneği döndürür.

Örneğin bir uygulama kredi kartı, PayPal ve banka havalesi gibi birden fazla ödeme yöntemini destekleyebilir. Ödeme hizmetinin içine nesne oluşturma mantığını yazmak yerine PaymentFactory, seçilen ödeme türüne göre doğru ödeme nesnesini oluşturabilir.

Factory Pattern Neden Önemlidir?

Factory Pattern önemlidir çünkü nesne oluşturmayı nesne kullanımından ayırır. Bu, uygulamanın bakımını ve genişletilmesini kolaylaştırır.

Kod birçok yerde doğrudan nesneler oluşturduğunda belirli sınıflara sıkı sıkıya bağlı hale gelir. Sınıf adı, yapıcı parametreleri veya oluşturma kuralları değişirse uygulamanın birçok bölümünün güncellenmesi gerekebilir.

Factoryda oluşturma kuralları tek bir konuma yerleştirilir. Uygulamanın geri kalanı, tam oluşturma ayrıntılarına değil, bir arayüze veya ortak türe bağlıdır.

Factory Pattern Olmadan Sorun

Birden fazla ödeme yöntemini destekleyen bir ödeme sistemi hayal edin. Factory olmadan ödeme hizmeti şuna benzer bir mantık içerebilir:

if ($type === 'credit_card') {
    $payment = new CreditCardPayment();
} elseif ($type === 'paypal') {
    $payment = new PayPalPayment();
} elseif ($type === 'bank_transfer') {
    $payment = new BankTransferPayment();
} else {
    throw new InvalidArgumentException('Invalid payment method.');
}

Bu kod küçük bir örnekte işe yarayabilir ancak birden fazla yerde tekrarlandığında sorun yaratır. Yeni bir ödeme yönteminin eklenmesi durumunda tekrarlanan her koşulun güncellenmesi gerekebilir.

Bu aynı zamanda iki sorumluluğu da birbirine karıştırıyor: hangi nesnenin oluşturulacağına karar vermek ve iş operasyonunu işlemek. Factory Pattern bu sorumlulukları ayırır.

PHP'da Temel Factory Pattern Örneği

Aşağıdaki örnek, PHP'daki basit bir Factory Pattern uygulamasını göstermektedir:

interface PaymentMethod
{
    public function pay(float $amount): bool;
}

class CreditCardPayment implements PaymentMethod
{
    public function pay(float $amount): bool
    {
        // Process credit card payment
        return true;
    }
}

class PayPalPayment implements PaymentMethod
{
    public function pay(float $amount): bool
    {
        // Process PayPal payment
        return true;
    }
}

class BankTransferPayment implements PaymentMethod
{
    public function pay(float $amount): bool
    {
        // Process bank transfer payment
        return true;
    }
}

class PaymentFactory
{
    public static function create(string $type): PaymentMethod
    {
        return match ($type) {
            'credit_card' => new CreditCardPayment(),
            'paypal' => new PayPalPayment(),
            'bank_transfer' => new BankTransferPayment(),
            default => throw new InvalidArgumentException('Invalid payment method.'),
        };
    }
}

Bu örnekte PaymentFactory, doğru ödeme nesnesinin oluşturulmasından sorumludur. Uygulamanın geri kalanı, tam sınıf oluşturma mantığını bilmeden PaymentMethod arayüzünü kullanabilir.

Uygulama Kodunda Factoryyı Kullanma

Factoryyı oluşturduktan sonra ödeme kodu daha basit hale gelir:

$paymentMethod = PaymentFactory::create('paypal');
$paymentMethod->pay(100);

Uygulama fabrikadan bir ödeme yöntemi ister ve ardından ödeme yöntemini çağırır. Ödeme kodunun uzun bir koşullu bloğa ihtiyacı yoktur.

Bu tasarım kodu daha temiz hale getirir ve genişletilmesini kolaylaştırır. Yeni bir ödeme yönteminin eklenmesi halinde fabrika tek yerden güncellenebilmektedir.

Factory Pattern ve Arayüzler

Factory Pattern arayüzlerle çok iyi çalışır. Bir arayüz, oluşturulan nesnelerin hangi davranışı sağlaması gerektiğini tanımlarken, fabrika hangi uygulamanın döndürüleceğine karar verir.

Ödeme örneğinde, tüm ödeme sınıfları PaymentMethod arayüzünü uygular. Bu, uygulamanın herhangi bir ödeme nesnesini aynı şekilde kullanabileceği anlamına gelir.

Bu kombinasyon polimorfizmi destekler. Farklı ödeme sınıfları dahili olarak farklı davranabilir ancak ana kod bunları aynı tür olarak değerlendirebilir.

Factory Pattern ve Polimorfizm

Polimorfizm, farklı nesnelerin aynı yönteme farklı şekillerde yanıt vermesine olanak tanır. Factory Pattern, ortak bir arayüzü veya ana sınıfı paylaşan nesneleri döndürdüğü için sıklıkla polimorfizmle birlikte çalışır.

Örneğin, CreditCardPayment, PayPalPayment ve BankTransferPayment'in hepsinin bir ödeme yöntemi vardır. Factory bunlardan birini döndürür ve uygulama, tam nesne türünü kontrol etmeden ödemeyi çağırır.

Bu, koşullu mantığı azaltır ve kodun uzantıya açık kalmasına yardımcı olur.

Gerçek Dünyadan Örnek: Bildirim Factorysı

Factory Pattern'nun gerçek dünyadaki ortak kullanım örneği bir bildirim sistemidir. Bir uygulama e-posta, SMS veya anlık bildirim yoluyla bildirim gönderebilir.

interface NotificationSender
{
    public function send(string $message): bool;
}

class EmailSender implements NotificationSender
{
    public function send(string $message): bool
    {
        // Send email
        return true;
    }
}

class SmsSender implements NotificationSender
{
    public function send(string $message): bool
    {
        // Send SMS
        return true;
    }
}

class PushNotificationSender implements NotificationSender
{
    public function send(string $message): bool
    {
        // Send push notification
        return true;
    }
}

class NotificationFactory
{
    public static function create(string $channel): NotificationSender
    {
        return match ($channel) {
            'email' => new EmailSender(),
            'sms' => new SmsSender(),
            'push' => new PushNotificationSender(),
            default => throw new InvalidArgumentException('Invalid notification channel.'),
        };
    }
}

Bu fabrika, uygulamanın seçilen kanala göre doğru bildirim göndericisini oluşturmasına olanak tanır.

Bildirim Factorysını Kullanma

Bildirim hizmeti fabrikayı şu şekilde kullanabilir:

$sender = NotificationFactory::create('email');
$sender->send('Your order has been shipped.');

Kod basittir ve EmailSender'ın dahili olarak nasıl oluşturulduğunu bilmenize gerek yoktur. SMS veya anlık bildirim seçilirse aynı arama kodu çalışmaya devam edebilir.

Factory Yöntemi Modeli

Factory Yöntem Kalıbı, nesne oluşturma işleminin alt sınıflar tarafından geçersiz kılınabilecek bir yönteme devredildiği bir varyasyondur. Tek başına bir fabrika sınıfı yerine, oluşturma yöntemi sınıf hiyerarşisinin bir parçasıdır.

Bu, bir üst sınıf bir süreci tanımladığında kullanışlıdır, ancak alt sınıflar bu süreç sırasında hangi nesnenin yaratılması gerektiğine karar verir.

Örneğin, bir rapor oluşturucu ortak rapor oluşturma adımlarını tanımlayabilirken alt sınıflar bir PDF dışa aktarıcı, Excel dışa aktarıcı veya CSV dışa aktarıcı oluşturulup oluşturulmayacağına karar verir.

PHP'da Factory Yöntemi Örneği

interface Exporter
{
    public function export(array $data): string;
}

class PdfExporter implements Exporter
{
    public function export(array $data): string
    {
        return 'PDF exported';
    }
}

class ExcelExporter implements Exporter
{
    public function export(array $data): string
    {
        return 'Excel exported';
    }
}

abstract class ReportGenerator
{
    abstract protected function createExporter(): Exporter;

    public function generate(array $data): string
    {
        $exporter = $this->createExporter();

        return $exporter->export($data);
    }
}

class PdfReportGenerator extends ReportGenerator
{
    protected function createExporter(): Exporter
    {
        return new PdfExporter();
    }
}

class ExcelReportGenerator extends ReportGenerator
{
    protected function createExporter(): Exporter
    {
        return new ExcelExporter();
    }
}

Bu örnekte, ReportGenerator üst sınıfı oluşturma sürecini tanımlar. Alt sınıflar hangi ihracatçının oluşturulması gerektiğine karar verir.

Bu, oluşturma mantığı alt sınıfa bağlı olduğunda kullanışlıdır.

Basit Factory ve Factory Yöntemi

Basit bir fabrika genellikle girdiye dayalı olarak nesneler oluşturan ayrı bir sınıf veya statik yöntemdir. Birçok uygulama için anlaşılması kolay ve pratiktir.

Factory Yöntem Kalıbı kalıtımı kullanır ve alt sınıfların nesne oluşturmayı denetlemesine olanak tanır. Oluşturma süreci daha büyük bir iş akışına bağlandığında daha yapılandırılmış ve kullanışlı olur.

Her iki yaklaşım da faydalıdır. En iyi seçim, uygulamanın karmaşıklığına ve tasarım hedefine bağlıdır.

Laravel'da Factory Pattern

Laravel uygulamalarında fabrika tarzı tasarım birçok yerde karşımıza çıkabilir. Test verileri oluşturmak için Laravel model fabrikalar kullanılır. Hizmet kapsayıcıları nesneler oluşturabilir ve çözümleyebilir. Geliştiriciler ayrıca ödeme yöntemleri, bildirim kanalları, dosya aktarıcılar veya rapor oluşturucular için özel fabrika sınıfları da oluşturabilir.

Örneğin, bir Laravel uygulaması, yapılandırmaya veya kullanıcı seçimine dayalı olarak ödeme ağ geçidi nesneleri oluşturan bir PaymentFactory hizmetini kullanabilir.

Bu, kontrol cihazlarını temiz tutar. Ödeme oluşturma mantığını bir denetleyicinin içine yerleştirmek yerine denetleyici, fabrikayı dahili olarak kullanan bir hizmeti arayabilir.

Symfony'da Factory Pattern

Symfony uygulamaları ayrıca fabrika sınıflarından ve hizmet yapılandırmasından da yararlanır. Bir fabrika karmaşık hizmetler oluşturabilir, uygulamaları seçebilir veya özel yapım mantığı gerektiren nesneler hazırlayabilir.

Büyük Symfony projelerinde fabrikalar sıklıkla bağımlılık enjeksiyonu ve hizmet tanımlarıyla kullanılır. Bu, geliştiricilere nesne oluşturma üzerinde güçlü bir kontrol sağlarken iş sınıflarının iş davranışına odaklanmasını sağlar.

Factory Pattern Ne Zaman Kullanılmalı?

Factory Pattern, nesne oluşturmanın basit olmadığı veya uygulamanın birden fazla ilgili sınıf arasında seçim yapması gerektiğinde kullanışlıdır.

Factory Pattern'yu şu durumlarda kullanın:

  • Oluşturulacak tam sınıf girişe, yapılandırmaya veya iş kurallarına bağlıdır.

  • Nesne oluşturma mantığı birçok yerde tekrarlanıyor.

  • Yapıcılar karmaşıktır veya birden fazla bağımlılık gerektirir.

  • Uygulama somut sınıflar yerine arayüzlere bağlı olmalıdır.

  • İlerleyen süreçte yeni uygulamalar eklenebilir.

  • Yaratma mantığını business logicndan ayırmak istiyorsunuz.

Factory özellikle birden fazla sağlayıcıyı, formatı, kanalı, stratejiyi veya entegrasyonu destekleyen sistemlerde kullanışlıdır.

Factory Pattern Ne Zaman Kullanılmamalı?

Factory Pattern her zaman gerekli değildir. Nesne oluşturma basitse ve değişme olasılığı düşükse, fabrika eklemek gereksiz karmaşıklık yaratabilir.

Aşağıdaki durumlarda Factory Pattern'dan kaçının:

  • Oluşturulacak tek bir basit sınıf var.

  • Yapıcı basittir ve özel bir mantığa ihtiyaç duymaz.

  • Factory, gerçek değer katmadan yalnızca yeni ambalajlar yapacaktı.

  • Proje çok küçük ve ekstra soyutlama gerektirmiyor.

  • Desen, kodun anlaşılmasını zorlaştırır.

İyi Design Patterns kodu basitleştirmelidir. Bir fabrika açıklıktan çok kafa karışıklığı katıyorsa buna gerek kalmayabilir.

Factory Pattern'nun Faydaları

Factory Pattern, nesne yönelimli yazılım tasarımında birçok pratik fayda sağlar.

Ana faydalar şunları içerir:

  • Nesne oluşturma mantığını merkezileştirir.

  • Yinelenen oluşturma kodunu azaltır.

  • İş mantığını inşaat mantığından ayırır.

  • Polimorfizmi ve arayüz tabanlı tasarımı destekler.

  • Yeni uygulamaların eklenmesini kolaylaştırır.

  • Orta ve büyük projelerde sürdürülebilirliği artırır.

  • Bağımlılık eklemeyle birleştirildiğinde testi kolaylaştırabilir.

Bu avantajlar Factory Pattern'yu gerçek dünya uygulamaları için en kullanışlı Design Patternsnden biri haline getiriyor.

Factory Pattern ve Temiz Kod

Factory Pattern, nesne oluşturma işlemini ait olmadığı yerlerden uzak tutarak clean code'yu destekler. Denetleyiciler, hizmetler ve etki alanı sınıfları genellikle karmaşık nesne oluşturma kurallarına değil, ana sorumluluklarına odaklanmalıdır.

Örneğin, bir ödeme hizmetinin ödeme davranışını işlemesi gerekir. Her ödeme sağlayıcısının nasıl oluşturulacağına karar veren uzun kod blokları içermemelidir.

Oluşturma mantığını bir fabrikaya taşımak, kodun okunmasını ve değiştirilmesini kolaylaştırır.

Factory Pattern ve Açık Kapalı Prensibi

Factory Pattern dikkatli bir şekilde tasarlandığında Açık Kapalı Prensibini destekleyebilir. Açık Kapalı Prensibi, yazılımın genişletmeye açık, değişiklik yapmaya kapalı olması gerektiğini söylüyor.

Bir fabrika ile uygulamaya sınırlı değişikliklerle sıklıkla yeni nesne türleri eklenebilir. Örneğin, yeni bir ödeme sınıfı eklemek, ödeme hizmeti değişmeden kalırken fabrikanın güncellenmesini gerektirebilir.

Daha gelişmiş tasarımlarda fabrikalar, doğrudan değişiklikleri daha da azaltmak için yapılandırmayı, hizmet kapsayıcılarını veya haritaları kullanabilir.

Yapılandırma Haritası ile Factory Pattern

Bir fabrika, uzun bir match veya switch deyimi kullanmak yerine, anahtarları sınıf adlarına bağlayan bir yapılandırma haritası kullanabilir.

class PaymentFactory
{
    private array $methods = [
        'credit_card' => CreditCardPayment::class,
        'paypal' => PayPalPayment::class,
        'bank_transfer' => BankTransferPayment::class,
    ];

    public function create(string $type): PaymentMethod
    {
        if (!isset($this->methods[$type])) {
            throw new InvalidArgumentException('Invalid payment method.');
        }

        return new $this->methods[$type]();
    }
}

Bu yaklaşım, özellikle uygulama listesi büyüdüğünde fabrikanın genişletilmesini kolaylaştırabilir.

Factory Pattern ile Dependency Injection

Gerçek uygulamalarda, oluşturulan nesnelerin API istemcileri, günlükçüler, yapılandırma nesneleri veya database bağlantıları gibi bağımlılıklara ihtiyacı olabilir. Bu durumda fabrika, yapıcısı aracılığıyla bağımlılıkları alabilir ve bunları yarattığı nesnelere aktarabilir.

class PaymentFactory
{
    public function __construct(
        private LoggerInterface $logger
    ) {
    }

    public function create(string $type): PaymentMethod
    {
        return match ($type) {
            'credit_card' => new CreditCardPayment($this->logger),
            'paypal' => new PayPalPayment($this->logger),
            default => throw new InvalidArgumentException('Invalid payment method.'),
        };
    }
}

Bu tasarım, fabrikanın bağımlılığa ihtiyaç duyduğu durumlarda statik yöntemler kullanmaktan daha iyidir. Aynı zamanda fabrikanın test edilmesini de kolaylaştırır.

Statik Factory Yöntemleri

Bazı fabrikalar basit nesne oluşturma için statik yöntemler kullanır. Statik fabrika yöntemleri, herhangi bir bağımlılığa ihtiyaç duyulmadığında kullanışlı olabilir.

Ancak, oluşturma süreci karmaşık hale geldiğinde statik fabrikaların test edilmesi zorlaşabilir. Factorynın bağımlılıklara veya konfigürasyona ihtiyacı varsa nesne tabanlı fabrika genellikle daha iyidir.

Genel bir kural olarak, basit durumlar için statik fabrika yöntemleri kabul edilebilirken, bağımlılık enjeksiyonlu fabrika hizmetleri daha büyük uygulamalar için daha iyidir.

Factory Pattern ile Yapılan Yaygın Hatalar

Yaygın bir hata, nesne oluşturma basit olsa bile her sınıf için bir fabrika oluşturmaktır. Bu, gereksiz dosyalar ekler ve projede gezinmeyi zorlaştırır.

Bir diğer hata ise fabrika içerisine çok fazla business logic koymaktır. Bir fabrika nesne yaratmaya odaklanmalıdır. İş kuralları genellikle hizmetlerde, etki alanı sınıflarında veya kullanım senaryolarında kalmalıdır.

Üçüncü bir hata, ilgisiz nesnelerin aynı fabrikadan iade edilmesidir. Bir fabrika aynı aileye ait nesneler yaratmalı veya aynı arayüzü izlemelidir.

Dördüncü hata, daha iyi bir organizasyon düşünülmeden çok fazla büyüyen uzun bir anahtar ifadesi kullanmaktır. Factory çok büyürse haritalara, ayrı fabrikalara veya bir servis konteynerine ihtiyaç duyabilir.

Factory Pattern için En İyi Uygulamalar

Factory Pattern'yu doğru kullanmak için geliştiricilerin tasarımı odaklı ve pratik tutması gerekir.

Yararlı en iyi uygulamalar şunları içerir:

  • Factoryları yalnızca nesne oluşturmanın gerçekten karmaşık olduğu durumlarda oluşturun.

  • Mümkün olduğunda arayüzleri veya ana türleri döndürün.

  • Factory sınıflarını yaratma mantığına odaklanın.

  • İş iş akışlarını fabrikaların içine yerleştirmekten kaçının.

  • Hizmete ihtiyaç duyan fabrikalar için bağımlılık eklemeyi kullanın.

  • PaymentFactory veya NotificationFactory gibi anlaşılır adlar kullanın.

  • İlgili nesne türlerini aynı fabrikada tutun.

  • Bakımı zorlaştığında büyük fabrikaları yeniden düzenleyin.

Bu uygulamalar Factory Pattern'nun temiz ve kullanışlı kalmasına yardımcı olur.

Factory Pattern vs Strategy Pattern

Factory Pattern ve Strategy Pattern farklıdır ancak birlikte çalışabilirler. Factory Pattern nesneler oluşturmaya odaklanır. Strategy Pattern, değiştirilebilir davranışın seçilmesine ve kullanılmasına odaklanır.

Örneğin bir fabrika müşteri tipine göre indirim stratejisi oluşturabilir. Oluşturulan strateji daha sonra indirimi hesaplar. Bu durumda Factory oluşturmayı, Strategy ise davranışı ele alır.

Farkı anlamak, geliştiricilerin doğru sorun için doğru modeli seçmesine yardımcı olur.

Factory Pattern vs Singleton Pattern

Singleton Pattern, bulut sunucusu sayısını kontrol eder ve yalnızca bir bulut sunucusunun mevcut olmasını sağlar. Factory Pattern, nesnelerin nasıl oluşturulduğunu kontrol eder ve duruma bağlı olarak farklı örnekler veya farklı sınıflar oluşturabilir.

Singleton tek bir paylaşılan nesneyle ilgilidir. Factory esnek nesne oluşturmayla ilgilidir.

Modern uygulamalarda Factory Pattern, genellikle küresel durumu getirmediği için Singleton'dan daha güvenli ve daha esnektir.

Factory Pattern Kullanmadan Önce Pratik Kontrol Listesi

Bir fabrikayı kullanmadan önce geliştiriciler şu soruları sorabilir:

  • Nesne oluşturma birkaç yerde tekrarlanıyor mu?

  • Oluşturulacak sınıf girdiye mi yoksa konfigürasyona mı bağlı?

  • Oluşturulan sınıflar aynı arayüzü paylaşıyor mu?

  • Daha sonra yeni uygulamalar eklenecek mi?

  • Bir fabrika ana kodu daha temiz hale getirecek mi?

  • Nesne yaratma bir fabrikayı hak edecek kadar karmaşık mı?

  • Bağımlılık ekleme veya hizmet kapsayıcısı yardımcı olabilir mi?

Bu soruların birçoğunun cevabı evet ise Factory Pattern iyi bir tasarım tercihi olabilir.

Sonuç

Factory Pattern, nesne oluşturmayı merkezileştiren ve onu ana business logicndan ayıran yaratıcı bir tasarım modelidir. Geliştiricilerin, oluşturma mantığını uygulamaya yaymadan giriş, yapılandırma veya iş kurallarına dayalı nesneler oluşturmasına yardımcı olur.

Factory Pattern özellikle arayüzler ve polimorfizmle iyi çalışır. Sürdürülebilirliği artırır, tekrarları azaltır ve yeni uygulamalar eklendiğinde uygulamaların genişletilmesini kolaylaştırır.

Ancak Factory yalnızca gerçek bir sorunu çözdüğünde kullanılmalıdır. Basit nesne oluşturma her zaman bir fabrikaya ihtiyaç duymaz. Doğru uygulandığında Factory Pattern, temiz Object-Oriented Programming ve gerçek dünyada yazılım geliştirme için en pratik ve kullanışlı Design Patternsnden biridir.