Yazılım projeleri büyüdükçe nesnelerin nasıl ve ne zaman oluşturulduğunu yönetmek tam bir kaosa dönüşebilir. Sürekli bir nesne türetmek zamanla sınıfları da birbirine bağlar (tight coupling sorunu). Projenizde esnekliği kaybedersiniz. Bu sebeple design pattern çözümleri bulunmaktadır. Nesne türetme süreçlerini tek bir merkezden yöneten ve bunları soyutlayan Factory Pattern en sık kullanılan çözümlerden birisidir.
Factory Pattern
Factory Pattern, en çok kullanılan tasarım desenlerinden biridir. Temel amacı ise bir nesnenin üretim mantığını, o nesneyi kullanacak olan bölümden (istemci alan) gizlemektir. Yani nesneyi çağıracağınız yer (örneğin bir Controller içinden kod yazıyorsunuz) concrete sınıfın ne olduğunu veya nasıl ayağa kaldırıldığını bilmek zorunda kalmaz. İstemci olan sadece kendi kriterlerine uygun nesne talep eder ve gerisini buradaki design pattern çözer.
Bu desen sisteminizde aynı interface veya abstract class paylaşan ancak arka planda farklı iş kurallarına sahip birden fazla sınıf ailesi olduğunda mükemmel çalışır. Örneklerle daha iyi anlaşılır olacaktır.
Ödeme sistemleri (Stripe, Iyzico gibi) veya bildirim servisleri (SMS, e-posta, push notification) üzerinde sıkça uygulanır.

Factory Pattern Nasıl Uygulanır
Bu yapıyı anlamak için bir senaryo oluşturalım. Senaryomuzda bir hukuk ve akademik araştırma platformu için arama motoru geliştirdiğimizi düşünelim. Kullanıcılar sistemde üç farklı türde arama yapabiliyor olmalı. Mevzuatlar, mahkeme kararları veya hukuki eserler üzerinde arama yapılıyor fakat hepsinin arama algoritmaları ve hedef veri tabanları farklı olsun.
Eğer bu deseni kullanmasaydık, arama butonuna basıldığı an çalışan kod bloğumuz muhtemelen şu şekilde görünecekti:
public enum SearchType { Mevzuat, MahkemeKarari, Eserler }
public class SearchController
{
public void ExecuteSearch(SearchType type, string keyword)
{
if (type == SearchType.Mevzuat)
{
MevzuatSearch mevzuatService = new MevzuatSearch();
mevzuatService.Search(keyword);
}
else if (type == SearchType.MahkemeKarari)
{
MahkemeKarariSearch mahkemeService = new MahkemeKarariSearch();
mahkemeService.Search(keyword);
}
else if (type == SearchType.Eserler)
{
BookSearch bookService = new BookSearch();
bookService.Search(keyword);
}
}
}Çok kötü bir yapı. Neden kötü? Yarın sisteme yeni bir tür eklenirse bu sınıf içerisinde yeni bir if-else yazmamız gerekecek. Bu durum SOLID’in Open/Closed prensibini doğrudan ihlal eder. Bu prensipleri dikkate almasanız bile arama türlerine göre olan nesnelerin “SearchController” içerisinde sıkı bağlandığını görüyorsunuz.
Şimdi factory pattern uygulayarak yeniden yazalım.
İlk işimiz hepsinin kullanabileceği ortak bir interface oluşturmak.
public interface ISearchService
{
void Search(string keyword);
}Şimdi bu arama türlerine göre ayrılan sınıfları birbirine bağlayalım.
public class MevzuatSearch : ISearchService
{
public void Search(string keyword) => Console.WriteLine($"Mevzuat veri tabanında '{keyword}' arandı.");
}
public class MahkemeKarariSearch : ISearchService
{
public void Search(string keyword) => Console.WriteLine($"Mahkeme kararlarında '{keyword}' arandı.");
}
public class EserSearch : ISearchService
{
public void Search(string keyword) => Console.WriteLine($"Eserler arşivinde '{keyword}' arandı.");
}Şimdi factory sınıfımızı oluşturalım. Alacağı parametreye göre ilgili sınıfı türetecektir.
public static class SearchFactory
{
public static ISearchService CreateSearchService(SearchType type)
{
return type switch
{
SearchType.Mevzuat => new MevzuatSearch(),
SearchType.MahkemeKarari => new MahkemeKarariSearch(),
SearchType.ResmiEser => new EserSearch(),
_ => throw new ArgumentException("Geçersiz arama türü!")
};
}
}Factory kısmı da tamam. Kötü senaryoyu revize ederek controller tarafından yeni haliyle yeniden çağırabiliriz.
public class SearchController
{
public void ExecuteSearch(SearchType type, string keyword)
{
// İstemci somut sınıfları görmüypor sadece arayüzle konuşuyor
ISearchService searchService = SearchFactory.CreateSearchService(type);
// Polymorphism sayesinde doğru arama metodu tetiklenir
searchService.Search(keyword);
}
}Factory pattern uyguladıktan sonra, sisteme yeni bir arama türü (örneğin uluslararası antlaşmalar diyelim) eklemek artık çocuk oyuncağıdır. Yapılması gereken tek şey ISearchService arayüzünden türeyen yeni bir sınıf yazmak ve bunu SearchFactory içerisindeki switch bloğuna eklemektir. SearchController sınıfı bu değişiklikten hiç haberdar bile olmaz. Bu durumda projeniz daha güvenli bir şekilde büyüyecektir.
Hatalardan Kaçının
Factory desenini uygularken bazı geliştiricilerin sıkça düştüğü hatalar var. Bunlar design pattern yapısını bozan ve amacına aykırı olan maddelerdir.
Her şey için factory oluşturmak: Sadece birkaç tane somut sınıfı olan bir projede gelecekte de genişlemesi beklenmeyeceği için bu tarz factory yapı oluşturmak gereksiz olacaktır.
Factory içinde business logic yürütmek: Factory tek göreve sahiptir, o da nesneyi türetmek ve geri döndürmek. Nesne üretilirken veri tabanına yeni bir iş kuralı oluşturursa bu design pattern amacına uymaz.
Factory sınıfının çok büyümesi: Simple factory kullanırken, sisteme her yeni sınıf eklendiğinde fabrika içindeki switch-case bloğunu sürekli büyütmek bir süre sonra yönetilemez hale gelebilir. Bu gibi durumlarda strategy pattern gibi reflection mekanizmalarına geçilebilir. Bu süreci iyi yönetmelisiniz.



Yorum bırakın