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.
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.
İ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.
Ö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.
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
- Product, yaratıcısı ve alt sınıfları tarafından üretilebilen tüm nesneler için ortak olan arabirimi bildirir.
- Concrete Products, ürün interface’inin farklı uygulamalarıdır.
- 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.
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:
- Öncelikle, oluşturulan tüm nesneleri takip etmek için biraz depolama alanı oluşturmanız gerekir.
- Birisi bir nesne istediğinde, program o havuzun içinde boş bir nesne aramalıdır.
- … ve ardından müşteri koduna geri gönderin.
- 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?
- Tüm ürünlerin aynı interface’i takip etmesini sağlayın. Bu interface, her üründe anlamlı olan yöntemleri bildirmelidir.
- 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.
- 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. - Ş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.
- Ç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
veGroundMail
; Ulaştırma sınıflarıPlane
,Truck
veTrain
‘dir.AirMail
sınıfı yalnızcaPlane
nesnelerini kullanırken,GroundMail
hemTruck
hem deTrain
nesneleri ile çalışabilir. Her iki durumu da ele almak için yeni bir alt sınıf (örneğinTrainMail
) oluşturabilirsiniz, ancak başka bir seçenek daha var. İstemci kodu, hangi ürünü almak istediğini kontrol etmek içinGroundMail
sınıfının fabrika yöntemine bir argüman iletebilir. - 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.