Factory Method - Fabrika Tasarım Deseni

Factory Method, bir üst sınıfta nesneler oluşturmak için bir interface sağlayan, ancak alt sınıfların oluşturulacak nesnelerin türünü değiştirmesine izin veren yaratıcı bir tasarım desenidir.

Daha önceki Tasarım Deseni / Kalıbı / Örüntüsü (Design Pattern) Nedir? başlıklı yazımızda tanıttığımız Creational Patterns altında yer alan Factory Method Nedir sorusunu ve kod örneklerini bu yazımızda oldukça detaylı paylaşıyoruz.

Bir lojistik yönetim uygulaması oluşturduğunuzu hayal edin. Uygulamanızın ilk sürümü yalnızca kamyonlarla taşıma işlemini gerçekleştirebiliyor. Bu nedenle kodunuzun büyük kısmı Truck sınıfında bulunur.

Bir süre sonra uygulamanız oldukça popüler hale geliyor. Her gün deniz taşımacılığı şirketlerinden deniz lojistiğini uygulamaya dahil etmek için onlarca talep alıyorsunuz.

Kodun geri kalanı zaten mevcut sınıflara bağlıysa, programa yeni bir sınıf eklemek o kadar kolay değildir.

Harika bir haber, değil mi? Peki ya kod? Şu anda kodunuzun çoğu Truck sınıfına bağlı. Uygulamaya Gemiler eklemek, tüm kod tabanında değişiklik yapılmasını gerektirir. Ayrıca, daha sonra uygulamaya başka bir ulaşım türü eklemeye karar verirseniz, muhtemelen tüm bu değişiklikleri tekrar yapmanız gerekecektir.

Sonuç olarak, ulaşım nesnelerinin sınıfına bağlı olarak uygulamanın davranışını değiştiren koşullarla dolu oldukça kötü bir kodla karşılaşacaksınız.

Çözüm

Factory Method, doğrudan nesne oluşturma çağrılarını (new operatörü kullanarak) özel bir factory methoda yapılan çağrılarla değiştirmenizi önerir. Endişelenmeyin: nesneler yine de new operatör aracılığıyla oluşturulur, ancak factory methodundan çağrılır. Factory methoduyla döndürülen nesnelere genellikle ürün denir.

Alt sınıflar, factory methoduyla döndürülen nesnelerin sınıfını değiştirebilir.

İlk bakışta, bu değişiklik anlamsız görünebilir: constructor çağrısını programın bir bölümünden diğerine taşıdık. Ancak şunu göz önünde bulundurun. Artık bir alt sınıfta factory methodunu geçersiz kılabilir ve yöntem tarafından oluşturulan ürünlerin sınıfını değiştirebilirsiniz.

Yine de küçük bir sınırlama vardır: alt sınıflar, yalnızca bu ürünlerin ortak bir temel sınıfa veya interface’e sahip olması durumunda farklı türde ürünler döndürür. Ayrıca, temel sınıftaki factory yönteminin dönüş türü bu interface olarak bildirilmelidir.

Tüm ürünler aynı interface’i takip etmelidir.

Örneğin, hem Truck hem de Ship sınıfları, deliver adlı bir yöntem bildiren Transport interface’ini uygulamalıdır. Her sınıf bu yöntemi farklı şekilde uygular: kamyonlar kargoyu karadan, gemiler kargoyu denizden teslim eder. RoadLogistics sınıfındaki factory yöntemi kamyon nesnelerini döndürürken, SeaLogistics sınıfındaki fabrika yöntemi gemileri döndürür.

Factory yöntemini kullanan kod (genellikle müşteri kodu olarak adlandırılan), çeşitli alt sınıflar tarafından döndürülen gerçek ürünler arasında bir fark görmez. Müşteri, tüm ürünleri soyut Transport olarak ele alır.

Tüm ürün sınıfları ortak bir interface uyguladıkları sürece, nesnelerini bozmadan istemci koduna geçirebilirsiniz.

Müşteri, tüm taşıma nesnelerinin deliver yöntemine sahip olması gerektiğini bilir, ancak tam olarak nasıl çalıştığı müşteri için önemli değildir.

Structure

  1. Product, yaratıcısı ve alt sınıfları tarafından üretilebilen tüm nesneler için ortak olan arabirimi bildirir.
  2. Concrete Products, ürün interface’inin farklı uygulamalarıdır.
  3. Creator sınıfı, yeni ürün nesnelerini döndüren factory methodunu bildirir. Bu methodun return türünün ürün interface’iyle eşleşmesi önemlidir.

Tüm alt sınıfları yöntemin kendi sürümlerini uygulamaya zorlamak için Factory yöntemini soyut olarak ilan edebilirsiniz. Alternatif olarak, temel factory yöntemi bazı varsayılan ürün türlerini döndürür.

Not, ismine rağmen, ürün oluşturma, yaratıcının birincil sorumluluğu değildir. Genellikle, Creator sınıfının zaten ürünlerle ilgili bazı temel business logiclere sahiptir. Factory methodu bu mantığı somut ürün sınıflarından ayırmaya yardımcı olur. İşte bir analoji: Büyük bir yazılım geliştirme şirketi programcılar için bir eğitim departmanı görevindedir. Ancak, şirketin bir bütün olarak birincil işlevi kod yazmaktır, programcıları üretmek değil.

4. Concrete Products, temel fabrika yöntemini geçersiz kılar, böylece farklı türde bir ürün döndürür.

Factory yönteminin her zaman yeni nesneler oluşturması gerekmediğini unutmayın. Ayrıca bir önbellekten, nesne havuzundan veya başka bir kaynaktan var olan nesneleri de döndürebilir.

# Factory Method Pseudocode

Bu örnek, istemci kodunu somut kullanıcı interface sınıflarına bağlamadan platformlar arası kullanıcı interface öğeleri oluşturmak için Factory Methodunun nasıl kullanılabileceğini gösterir.

Baz Dialog sınıfı, penceresini oluşturmak için farklı UI elemanlarını kullanır. Çeşitli işletim sistemlerinde, bu unsurlar biraz farklı görünür, ancak hala sürekli davranmaları gerekir. Windows’ta bir düğme Linux’taki hala bir düğmedir.

Cross-platform dialog örneği.

Factory methodu devreye girdiğinde, her işletim sistemi için Dialog kutusunun mantığını yeniden yazmanıza gerek yoktur. Temel Dialog sınıfı içinde düğmeler üreten bir fabrika yöntemi bildirirsek, daha sonra fabrika yönteminden Windows stili düğmeler döndüren bir Dialog alt sınıfı oluşturabiliriz. Alt sınıf daha sonra Dialog kutusunun kodunun çoğunu temel sınıftan devralacak, ancak fabrika yöntemi sayesinde ekranda Windows görünümlü düğmeler oluşturabilecektir.

Elbette bu yaklaşımı diğer UI öğelerine de uygulayabilirsiniz. Ancak, diyaloğa eklediğiniz her yeni fabrika yöntemiyle Abstract Factory desenine yaklaşırsınız. Korkmayın, bu model hakkında daha sonra konuşacağız.

// Creator sınıfı, bir Product sınıfının nesnesini döndürmesi gereken Factory methodunu bildirir. 
// Oluşturucunun alt sınıfları genellikle bu yöntemin implementasyonunu sağlar.
class Dialog is
    // Creator, fabrika yönteminin bazı varsayılan implementasyonlarını da sağlayabilir.
    abstract method createButton():Button

    // Adına rağmen, Creator'un birincil sorumluluğunun product oluşturmak olmadığını unutmayın. 
    // Genellikle fabrika yöntemiyle döndürülen product nesnelerine dayanan bazı temel iş mantığını içerir. 
    // Alt sınıflar, fabrika yöntemini geçersiz kılarak ve ondan farklı bir product türü döndürerek 
    // bu iş mantığını dolaylı olarak değiştirebilir.
    method render() is
        // Bir Product nesnesi oluşturmak için Factory methodunu çağırın.
        Button okButton = createButton()

        // Şimdi product'ı kullanıyoruz.
        okButton.onClick(closeDialog)
        okButton.render()


// Concrete creator ortaya çıkan ürünün türünü değiştirmek için factory methodunu geçersiz kılar. 
class WindowsDialog extends Dialog is
    method createButton():Button is
        return new WindowsButton()

class WebDialog extends Dialog is
    method createButton():Button is
        return new HTMLButton()


// Product interface'i, tüm somut productların implemente etmesi gereken işlemleri bildirir.
interface Button is
    method render()
    method onClick(f)

// Concrete product ürün interface'inin çeşitli implementasyonlarını sağlar. 
class WindowsButton implements Button is
    method render(a, b) is
    // Windows stilinde bir düğme oluşturma.
    method onClick(f) is
    // Yerel bir işletim sistemi tıklama olayını bağlayın.

class HTMLButton implements Button is
    method render(a, b) is
    // Bir düğmenin HTML sunumunu döndürür.
    method onClick(f) is
    // Bir web tarayıcısı tıklama olayını bağlayın.


class Application is
    field dialog: Dialog

    // Uygulama, mevcut yapılandırmaya veya ortam ayarlarına bağlı olarak bir creator'un tipini seçer.
    method initialize() is
        config = readApplicationConfigFile()

        if (config.OS == "Windows") then
            dialog = new WindowsDialog()
        else if (config.OS == "Web") then
            dialog = new WebDialog()
        else
        throw new Exception("Error! Unknown operating system.")

    // İstemci kodu, temel interface aracılığıyla da olsa concrete creator instance'ı ile çalışır. 
    // İstemci, temel interface aracılığıyla creatorla çalışmaya devam ettiği sürece, 
    // onu herhangi bir creatorun alt sınıfından geçirebilirsiniz.
    method main() is
        this.initialize()
        dialog.render()

💡 Uygulanabilirlik

Factory Method Uygulama-1

🐛 Kodunuzun birlikte çalışması gereken nesnelerin tam türlerini ve bağımlılıklarını önceden bilmiyorsanız Factory methodunu kullanın.

Factory Method, ürün üretim kodunu, ürünü gerçekten kullanan koddan ayırır. Bu nedenle, ürün yapım kodunu kodun geri kalanından bağımsız olarak genişletmek daha kolaydır.

Örneğin, uygulamaya yeni bir ürün türü eklemek için yalnızca yeni bir creator alt sınıfı oluşturmanız ve içindeki Factory yöntemini override etmeniz gerekir.

Factory Method Uygulama-2

🐛 Library veya framework’ünüzün kullanıcılarına dahili bileşenlerini extend etmenin bir yolunu sağlamak istediğinizde Factory Method kullanın.

⚡ Inheritance, muhtemelen bir library veya framework’ün varsayılan davranışını genişletmenin en kolay yoludur. Ancak framework, standart bir bileşen yerine alt sınıfınızın kullanılması gerektiğini nasıl anlar?

Çözüm, framework genelinde bileşenleri oluşturan kodu tek bir fabrika yöntemine indirgemek ve bileşenin kendisini genişletmenin yanı sıra herkesin bu yöntemi geçersiz kılmasına izin vermektir.

Bunun nasıl işe yarayacağını görelim. Açık kaynaklı bir UI framewrok kullanarak bir uygulama yazdığınızı hayal edin. Uygulamanızın yuvarlak düğmeleri olmalıdır, ancak framework yalnızca kare düğmeler sağlar. Standart Button sınıfını muhteşem bir RoundButton alt sınıfıyla genişletirsiniz. Ancak şimdi ana UIFramework sınıfına varsayılan bir alt sınıf yerine yeni düğme alt sınıfını kullanmasını söylemeniz gerekiyor.

Bunu başarmak için, bir temel framework sınıfından bir UIWithRoundButtons alt sınıfı oluşturur ve onun createButton yöntemini geçersiz kılarsınız. Bu yöntem, temel sınıftaki Button nesnelerini döndürürken, alt sınıfınızın RoundButton nesnelerini döndürmesini sağlarsınız. Şimdi UIFramework yerine UIWithRoundButtons sınıfını kullanın.

Factory Method Uygulama-3

🐛 Var olan nesneleri her seferinde yeniden oluşturmak yerine yeniden kullanarak sistem kaynaklarından tasarruf etmek istediğinizde Fabrika Yöntemini kullanın.

⚡ Veritabanı bağlantıları, dosya sistemleri ve ağ kaynakları gibi büyük, kaynak yoğun nesnelerle uğraşırken bu ihtiyacı sıklıkla yaşarsınız.

Mevcut bir nesneyi yeniden kullanmak için ne yapılması gerektiğini düşünelim:

  1. Öncelikle, oluşturulan tüm nesneleri takip etmek için biraz depolama alanı oluşturmanız gerekir.
  2. Birisi bir nesne istediğinde, program o havuzun içinde boş bir nesne aramalıdır.
  3. … ve ardından müşteri koduna geri gönderin.
  4. Boş nesne yoksa, program yeni bir tane oluşturmalıdır (ve havuza eklemelidir).

Burada yazılması gereken çok fazla kod var! Bu nedenle programı yinelenen kodlarla kirletmemeniz için hepsi tek bir yere yerleştirilmelidir.

Muhtemelen bu kodun yerleştirilebileceği en bariz ve uygun yer, nesnelerini yeniden kullanmaya çalıştığımız sınıfın constructor’ıdır. Ancak, bir constructor tanım gereği her zaman yeni nesneler döndürmelidir. Mevcut instanceları döndüremez.

Bu nedenle, mevcut nesneleri yeniden kullanmanın yanı sıra yeni nesneler oluşturabilen düzenli bir yönteme sahip olmanız gerekir. Bu bir fabrika yöntemine çok benziyor.

🗎 Nasıl Implemente Edeceğiz?

  1. Tüm ürünlerin aynı interface’i takip etmesini sağlayın. Bu interface, her üründe anlamlı olan yöntemleri bildirmelidir.
  2. Creator sınıfının içine boş bir fabrika yöntemi ekleyin. Yöntemin dönüş türü, ortak ürün interface’siyle eşleşmelidir.
  3. Creator’un kodunda, Product constructor’lara yapılan tüm referansları bulun. Product constructor kodunu fabrika yöntemine ayıklarken, bunları tek tek fabrika yöntemine yapılan çağrılarla değiştirin. Return edilen ürünün türünü kontrol etmek için fabrika yöntemine geçici bir parametre eklemeniz gerekir. Bu noktada fabrika yönteminin kodu oldukça çirkin görünür. Hangi ürün sınıfını somutlaştıracağını seçen büyük bir switch operatörüne sahip olabilir. Ama merak etmeyin, yakında düzelteceğiz.
  4. Şimdi, fabrika yönteminde listelenen her ürün türü için bir dizi creator alt sınıf oluşturun. Alt sınıflarda fabrika yöntemini geçersiz kılın ve temel yöntemden uygun yapı kodu bitlerini çıkarın.
  5. Çok fazla ürün türü varsa ve hepsi için alt sınıflar oluşturmak mantıklı değilse, alt sınıflarda temel sınıftan kontrol parametresini yeniden kullanabilirsiniz. Örneğin, aşağıdaki sınıf hiyerarşisine sahip olduğunuzu düşünün: birkaç alt sınıfa sahip temel Mail sınıfı: AirMail ve GroundMail; Ulaştırma sınıfları Plane, Truck ve Train‘dir. AirMail sınıfı yalnızca Plane nesnelerini kullanırken, GroundMail hem Truck hem de Train nesneleri ile çalışabilir. Her iki durumu da ele almak için yeni bir alt sınıf (örneğin TrainMail ) oluşturabilirsiniz, ancak başka bir seçenek daha var. İstemci kodu, hangi ürünü almak istediğini kontrol etmek için GroundMail sınıfının fabrika yöntemine bir argüman iletebilir.
  6. Tüm çıkarmalardan sonra, temel fabrika yöntemi boşaldıysa, onu soyut yapabilirsiniz. Kalan bir şey varsa, bunu yöntemin varsayılan davranışı yapabilirsiniz.

⚖️ Factory Method Avantajları ve Dezavantajları

  • ✔️ Creator ile concrete ürünler arasındaki sıkı bağlantıdan kaçınırsınız.
  • ✔️ Single Responsibility Principle. Ürün oluşturma kodunu programda tek bir yere taşıyarak kodun desteklenmesini kolaylaştırabilirsiniz.
  • ✔️ Open/Closed Principle. Mevcut müşteri kodunu bozmadan programa yeni ürün türlerini tanıtabilirsiniz.
  • ❌ Modeli uygulamak için birçok yeni alt sınıf tanıtmanız gerektiğinden kod daha karmaşık hale gelir. En iyi durum senaryosu, kalıbı mevcut bir içerik oluşturucu sınıfları hiyerarşisine dahil ettiğiniz zamandır.

⇄ Diğer Desenlerle İlişkisi

  • Birçok desen, Factory Method (daha az karmaşık ve alt sınıflar aracılığıyla daha fazla özelleştirilebilir) kullanılarak başlar ve Abstract Factory, Prototype veya Builder (daha esnek, ancak daha karmaşık) doğru gelişir.
  • Abstract Factory sınıfları genellikle bir dizi Factory Methoduna dayanır, ancak bu sınıflarda yöntemleri oluşturmak için Prototype‘ıda kullanabilirsiniz.
  • Koleksiyon alt sınıflarının koleksiyonlarla uyumlu farklı türde yineleyiciler döndürmesine izin vermek için Factory Methodunu Iterator ile birlikte kullanabilirsiniz.
  • Prototype kalıtım üzerine kurulu değildir, dolayısıyla dezavantajları da yoktur. Öte yandan, Prototype, klonlanmış nesnenin karmaşık bir şekilde başlatılmasını gerektirir. Factory Methodu, kalıtımı temel alır ancak bir başlatma adımı gerektirmez.
  • Factory Method, Template Methodunun bir uzmanlığıdır. Aynı zamanda, bir Factory Methodu, büyük bir Template Methodunda bir adım olarak hizmet eder.

<> Kod Örneği

Factory Method Example

Aşağıdaki public repo adresine giderek adresine giderek örnek Factory method kodlarını inceleyebilirsiniz.

Github repoya gitmek için Tıklayınız.