Takip et

İş Kuralları Neden Esneklik İster? Yazılımda Sık Karşılaşılan Zorluklar

Takılabilir İş Kuralları: Strateji Deseniyle Esnek Çözümler

Yazılım geliştirme dünyasında iş kuralları, uygulamanın kalbini oluşturur. Ancak bu kurallar, sürekli değişen iş gereksinimleriyle birlikte çoğu zaman bir kabusa dönüşebilir. Peki, bu değişimi kucaklayarak, uygulamalarımızı daha esnek, sürdürülebilir ve kolay yönetilebilir hale nasıl getirebiliriz? İşte bu noktada “Strateji Deseni” devreye giriyor. Bu makalede, iş kurallarınızı bir heykeltıraşın çekiç ve iskarpela kullanarak mermeri yontması gibi, özenle ve esnek bir şekilde nasıl tasarlayabileceğinizi adım adım keşfedeceğiz.

Günümüz iş dünyasında tek sabit şey değişimin kendisidir. Şirketler, pazar koşullarına, mevzuat değişikliklerine veya müşteri beklentilerine hızla adapte olmak zorundadır. Bu adaptasyon yeteneği, yazılım sistemlerinin altında yatan iş kurallarının da aynı derecede esnek olmasını gerektirir. “İş kuralları”, bir şirketin hedeflerine ulaşmak için uyması gereken politikaları, prosedürleri ve kısıtlamaları ifade eder. Bunlar, bir müşterinin indirim almaya hak kazanıp kazanmadığından, bir siparişin onaylanma sürecine, hatta bir finansal işlemin nasıl raporlanacağına kadar çok geniş bir yelpazeyi kapsar.

Bir yazılım sisteminde bu iş kuralları başlangıçta basit ve yönetilebilir görünse de, zamanla karmaşıklık kaçınılmaz olarak artar. Yeni ürünler, farklı müşteri segmentleri, bölgesel düzenlemeler veya promosyon kampanyaları, mevcut kuralların değişmesini veya tamamen yeni kuralların eklenmesini gerektirir. Eğer bu kurallar, uygulamanın temel mantığına sıkıca bağlı (hard-coded) ve dağınık bir şekilde kodlanmışsa, ortaya çıkan sorunlar ciddi boyutlara ulaşabilir. Monolitik kod yapıları, if-else yığınları veya switch-case cehennemleri, yeni bir kural eklemeyi veya mevcut bir kuralı değiştirmeyi adeta bir mayın tarlasına dönüştürür. Geliştiriciler, “bu değişikliğin başka neleri etkileyeceğini bilemiyorum” endişesiyle hareket eder, bu da hata oranını artırır ve teslimat sürelerini uzatır. Bakım maliyetleri fırlar, yeni özelliklerin geliştirilmesi yavaşlar ve nihayetinde yazılım, işin dinamik temposuna ayak uydurmakta zorlanan bir darboğaza dönüşür. Esneklik, yalnızca gelecekteki değişikliklere uyum sağlamakla kalmaz, aynı zamanda yazılımın ömrünü uzatır, geliştirme süreçlerini hızlandırır ve genel kod kalitesini artırır. Bu nedenle, iş kurallarını “takılabilir” (pluggable) ve “modüler” (modular) bir şekilde tasarlamak, modern yazılım mimarisinin temel taşlarından biridir. Bu sayede her bir kuralı bağımsız bir bileşen olarak ele alabilir, test edebilir, değiştirebilir veya ekleyebiliriz. İşte bu vizyonu gerçeğe dönüştürmenin en etkili yollarından biri Strateji Deseni’dir.

Strateji Deseni Nedir ve Neden Hayat Kurtarır?

Tasarım desenleri, yazılım mühendisliğinde sıkça karşılaşılan sorunlara kanıtlanmış çözümler sunan şablonlardır. Bu şablonlardan biri olan Strateji Deseni (Strategy Pattern), Gang of Four (GoF) tarafından tanımlanmış davranışsal (behavioral) desenlerden biridir. Temel amacı, bir algoritma ailesini tanımlamak, her bir algoritmayı ayrı bir sınıf içine almak ve onları değiştirilebilir hale getirmektir. Bu sayede, algoritma kullanan istemci, uygulamanın geri kalanını değiştirmeden çalışma zamanında (runtime) farklı algoritmalar arasından seçim yapabilir. Başka bir deyişle, “ne yapılması gerektiğini” belirlerken, “nasıl yapılacağının” ayrıntılarını soyutlarız.

Strateji Deseni, genellikle bir görevi tamamlamak için birden fazla yol olduğunda ve bu yolların koşullara veya isteklere göre dinamik olarak değiştirilmesi gerektiğinde kullanılır. Örneğin, bir e-ticaret uygulamasında farklı indirim hesaplama yöntemleri (yüzde indirimi, sabit tutar indirimi, sadakat indirimi) veya bir raporlama sisteminde farklı çıktı formatları (PDF, Excel, HTML) olabilir. Bu senaryolarda, Strateji Deseni, her bir farklı yaklaşımı ayrı bir “strateji” olarak ele alarak, kodunuzu daha okunabilir, sürdürülebilir ve esnek hale getirir. Desenin temel bileşenleri şunlardır:

1. Strateji Arayüzü (Strategy Interface/Abstract Strategy): Tüm somut stratejilerin uygulayacağı ortak bir arayüz veya soyut sınıftır. Bu arayüz, stratejinin ana işlevini tanımlayan bir veya daha fazla metod içerir. İstemci kodu, bu arayüz üzerinden somut stratejilerle iletişim kurar, böylece hangi stratejiyi kullandığını bilmek zorunda kalmaz. Bu, “polimorfizm” ve “bağımlılıkların tersine çevrilmesi” (Dependency Inversion Principle) prensiplerinin güzel bir örneğidir.

2. Somut Stratejiler (Concrete Strategies): Strateji arayüzünü uygulayan ve belirli bir algoritmayı veya iş kuralını gerçekleştiren sınıflardır. Her somut strateji, bir yöntemin farklı bir uygulamasını veya bir iş kuralının farklı bir varyasyonunu temsil eder. Örneğin, “YüzdeIndirimStratejisi”, “SabitTutarIndirimStratejisi” gibi.

3. Bağlam (Context): Stratejiyi kullanan sınıftır. Bağlam sınıfı, bir Strateji arayüzüne referans tutar ve istemciden gelen isteği bu stratejiye delege eder. Bağlam, hangi somut stratejinin kullanılacağına dair mantığı içerebilir (genellikle bir Fabrika Deseni ile birleştirilerek) veya bu seçim mantığı istemci tarafından Bağlam’a dışarıdan enjekte edilebilir. Bağlamın kendisi, farklı stratejilerin nasıl uygulandığını bilmez, sadece onları kullanır.

Strateji Desenini kullanmanın başlıca avantajları şunlardır:

* Esneklik ve Modülerlik: Yeni stratejiler eklemek veya mevcut stratejileri değiştirmek çok kolaydır, çünkü her strateji bağımsız bir birimdir. Bu, uygulamanın temel yapısını bozmadan genişlemeyi sağlar.
* Kod Tekrarını Azaltma: Benzer mantığa sahip ancak farklı davranış sergileyen kod parçacıklarını tek bir arayüz altında toplar, böylece kod tekrarını önler.
* Test Edilebilirlik: Her strateji bağımsız olduğu için, birim testleri (unit tests) yazmak ve hata ayıklamak çok daha kolaydır. Her stratejiyi izole bir şekilde test edebilirsiniz.
* Gevşek Bağlantı (Loose Coupling): Bağlam sınıfı, belirli somut stratejilere doğrudan bağımlı değildir; sadece strateji arayüzüne bağımlıdır. Bu, bileşenler arasında daha az bağımlılık olduğu anlamına gelir, bu da sistemin daha az kırılgan olmasını sağlar.
* Tek Sorumluluk Prensibi (Single Responsibility Principle – SRP): Her strateji sınıfı, yalnızca kendi algoritmasını veya iş kuralını uygulamaktan sorumludur. Bağlam sınıfı ise sadece stratejiyi kullanmaktan sorumludur. Bu, SOLID prensiplerine uygun bir yapı sağlar.

> # Uzman İpucu:
> Strateji desenini uygulamadan önce, ilgili iş kurallarının gerçekten farklı algoritmalar mı yoksa sadece aynı algoritmanın farklı parametreleri mi olduğunu iyi analiz edin. Bazen bir yapılandırma (configuration) değişikliği, bir strateji değişiminden daha uygun olabilir. Ancak, davranışsal farklılıklar söz konusuysa, Strateji Deseni vazgeçilmezdir.

Adım Adım Strateji Deseni Uygulaması: Sipariş İndirimleri Senaryosu

Şimdi, Strateji Deseni’nin nasıl çalıştığını somut bir e-ticaret senaryosu üzerinden adım adım inceleyelim. Bir e-ticaret platformunda, siparişlere uygulanacak farklı indirim stratejilerimiz olduğunu varsayalım:
* Müşterinin belirli bir tutarın üzerinde alışveriş yapması durumunda sabit yüzde indirimi.
* Belirli ürün kategorilerindeki ürünler için sabit tutar indirimi.
* Sadık müşterilere özel ekstra yüzde indirimi.

Bu indirim mantıkları zamanla değişebilir veya yeni indirim türleri eklenebilir. Strateji Deseni ile bu karmaşıklığı nasıl yöneteceğimize bakalım. Örnek kodları C# dilinde vereceğim.

Adım 1: Strateji Arayüzünü Tanımlama

İlk olarak, tüm indirim stratejilerimizin uyması gereken bir arayüz tanımlıyoruz. Bu arayüz, indirim hesaplama işlemini soyutlayacak tek bir metod içerecek.


public interface IIndirimStratejisi
{
    /// 
    /// Belirli bir sipariş için uygulanacak indirimi hesaplar.
    /// 
    /// Siparişin toplam tutarı.
    /// Uygulanacak indirim miktarı.
    decimal IndirimHesapla(decimal siparisTutari);
}

Bu IIndirimStratejisi arayüzü, indirim hesaplama işleminin temel kontratını tanımlar. Hangi indirim stratejisi olursa olsun, IndirimHesapla metodunu uygulamak zorundadır. Bu, bağımsız stratejiler yaratmamıza olanak tanır ve gelecekteki değişiklikleri çok daha kolay hale getirir. İstemci kodu bu arayüze bağımlı olacağı için, altındaki somut uygulamaların detaylarını bilmek zorunda kalmayacaktır.

Adım 2: Somut Stratejileri Uygulama

Şimdi, tanımladığımız IIndirimStratejisi arayüzünü uygulayan somut indirim stratejilerini oluşturalım.

html


// Yüzde bazında indirim uygulayan strateji
public class YuzdeIndirimStratejisi : IIndirimStratejisi
{
private readonly decimal _yuzde;

public YuzdeIndirimStratejisi(decimal yuzde)
{
if (yuzde < 0 || yuzde > 100)
throw new ArgumentOutOfRangeException(nameof(yuzde), "Yüzde 0 ile 100 arasında olmalıdır.");
_yuzde = yuzde;
}

public decimal IndirimHesapla(decimal siparisTutari)
{
return siparisTutari * (_yuzde / 100);
}
}

// Sabit tutar bazında indirim uygulayan strateji
public class SabitTutarIndirimStratejisi : IIndirimStratejisi
{
private readonly decimal _sabitTutar;

public SabitTutarIndirimStratejisi(decimal sabitTutar)
{
if (sabitTutar < 0) throw new ArgumentOutOfRangeException(nameof(sabitTutar), "Sabit tutar negatif olamaz."); _sabitTutar = sabitTutar; } public decimal IndirimHesapla(decimal siparisTutari) { // İndirim, sipariş tutarını aşamaz return Math.Min(_sabitTutar, siparisTutari); } } // Sadık müşteriler için özel indirim stratejisi public class SadikMusteriIndirimStratejisi : IIndirimStratejisi { private readonly decimal _ekstraYuzde; public SadikMusteriIndirimStratejisi(decimal ekstraYuzde) { if (ekstraYuzde < 0 || ekstraYuzde > 100)
throw new ArgumentOutOfRangeException(nameof(ekstraYuzde), "Ekstra yüzde 0 ile 100 arasında olmalıdır.");
_ekstraYuzde = ekstraYuzde;
}

public decimal IndirimHesapla(decimal siparisTutari)
{
// Örnek olarak, sadık müşterilere %10 standart indirim + ekstra %_ekstraYuzde
// Gerçekte bu, daha karmaşık bir kural seti içerebilir.
decimal standartIndirim = siparisTutari * (10m / 100); // %10 standart indirim
decimal ekstraIndirim = siparisTutari * (_ekstraYuzde / 100);
return standartIndirim + ekstraIndirim;
}
}

Her bir somut strateji, kendi indirim hesaplama mantığını IndirimHesapla metodu içinde barındırır. Bu sınıflar birbirlerinden bağımsızdır ve kendi içlerindeki iş kurallarına odaklanırlar. Herhangi bir stratejiyi değiştirmek veya yeni bir tane eklemek, diğer stratejileri etkilemeyecektir.

Adım 3: Bağlam Sınıfını Oluşturma

Şimdi, hangi indirim stratejisinin kullanılacağına karar veren ve bu stratejiyi uygulayan Siparis veya IndirimUygulayıcı gibi bir bağlam sınıfı oluşturalım.

html


public class IndirimUygulayici
{
private IIndirimStratejisi _indirimStratejisi;

// Bağlam, çalışma zamanında stratejiyi değiştirebilir
public void IndirimStratejisiBelirle(IIndirimStratejisi indirimStratejisi)
{
_indirimStratejisi = indirimStratejisi ?? throw new ArgumentNullException(nameof(indirimStratejisi));
}

// Constructor ile başlangıç stratejisi tanımlama
public IndirimUygulayici(IIndirimStratejisi indirimStratejisi)
{
_indirimStratejisi = indirimStratejisi ?? throw new ArgumentNullException(nameof(indirimStratejisi));
}

public decimal UygulanacakIndirimiHesapla(decimal siparisTutari)
{
if (_indirimStratejisi == null)
{
// Hiçbir strateji belirlenmemişse, indirim yok veya varsayılan bir strateji kullanılabilir
Console.WriteLine("Uyarı: İndirim stratejisi belirlenmedi. Varsayılan olarak 0 indirim uygulanacaktır.");
return 0m;
}
return _indirimStratejisi.IndirimHesapla(siparisTutari);
}
}

IndirimUygulayici sınıfı, IIndirimStratejisi türünden bir referans tutar. Bu, hangi somut stratejiyi kullanacağını bilmek zorunda olmadığı anlamına gelir. Strateji, yapıcı (constructor) aracılığıyla veya IndirimStratejisiBelirle metodu aracılığıyla çalışma zamanında enjekte edilebilir. Bu, Bağlam sınıfını esnek ve tekrar kullanılabilir kılar.

Adım 4: Kullanım Örneği ve Test

Son olarak, farklı stratejileri nasıl kullanacağımızı gösterelim.

html


public class Program
{
public static void Main(string[] args)
{
decimal siparisTutari = 250m; // Örnek sipariş tutarı

Console.WriteLine($"Sipariş Tutarı: {siparisTutari:C}");

// Yüzde indirim stratejisi (%15 indirim)
IIndirimStratejisi yuzdeIndirim = new YuzdeIndirimStratejisi(15);
IndirimUygulayici uygulayici1 = new IndirimUygulayici(yuzdeIndirim);
decimal indirim1 = uygulayici1.UygulanacakIndirimiHesapla(siparisTutari);
Console.WriteLine($"%15 Yüzde İndirimi: {indirim1:C} (Kalan: {siparisTutari - indirim1:C})");

// Sabit tutar indirim stratejisi (50 TL indirim)
IIndirimStratejisi sabitTutarIndirim = new SabitTutarIndirimStratejisi(50);
IndirimUygulayici uygulayici2 = new IndirimUygulayici(sabitTutarIndirim);
decimal indirim2 = uygulayici2.UygulanacakIndirimiHesapla(siparisTutari);
Console.WriteLine($"50 TL Sabit Tutar İndirimi: {indirim2:C} (Kalan: {siparisTutari - indirim2:C})");

// Sadık müşteri indirim stratejisi (ekstra %5 indirim)
IIndirimStratejisi sadikMusteriIndirim = new SadikMusteriIndirimStratejisi(5);
IndirimUygulayici uygulayici3 = new IndirimUygulayici(sadikMusteriIndirim);
decimal indirim3 = uygulayici3.UygulanacakIndirimiHesapla(siparisTutari);
Console.WriteLine($"Sadık Müşteri İndirimi (Ekstra %5): {indirim3:C} (Kalan: {siparisTutari - indirim3:C})");

// Stratejiyi çalışma zamanında değiştirme
Console.WriteLine("\nStrateji çalışma zamanında değiştiriliyor...");
IndirimUygulayici dinamikUygulayici = new IndirimUygulayici(new YuzdeIndirimStratejisi(10));
Console.WriteLine($"Başlangıç %10 İndirimi: {dinamikUygulayici.UygulanacakIndirimiHesapla(siparisTutari):C}");

dinamikUygulayici.IndirimStratejisiBelirle(new SabitTutarIndirimStratejisi(25));
Console.WriteLine($"Değiştirildi -> 25 TL Sabit Tutar İndirimi: {dinamikUygulayici.UygulanacakIndirimiHesapla(siparisTutari):C}");
}
}

Bu örnekte görüldüğü gibi, IndirimUygulayici sınıfının UygulanacakIndirimiHesapla metodu, kullandığı strateji değişse bile aynı kalır. Bu sayede, yeni bir indirim stratejisi eklediğimizde IndirimUygulayici sınıfında herhangi bir değişiklik yapmamıza gerek kalmaz. Sadece yeni bir IIndirimStratejisi uygulaması oluşturur ve onu IndirimUygulayici'ya enjekte ederiz. Bu, uygulamanın esnekliğini, genişletilebilirliğini ve sürdürülebilirliğini çarpıcı bir şekilde artırır.

Gerçek Dünya Vakası: Ödeme Yöntemleri ve Strateji Deseni

İş kurallarının dinamik olduğu bir başka gerçek dünya senaryosu, ödeme işlemleri olabilir. Bir e-ticaret veya hizmet platformunda, farklı ödeme yöntemleri (kredi kartı, banka havalesi, mobil ödeme, dijital cüzdanlar vb.) genellikle kendilerine özgü validasyon, işlem akışı ve hata yönetimi kurallarına sahiptir. Bu çeşitlilik, Strateji Deseni'nin gücünü ortaya koymak için harika bir fırsattır. Bir müşterinin bir ödeme yöntemi seçtiğini düşünelim. Her ödeme yönteminin kendi benzersiz adımları, harici servis entegrasyonları, doğrulama kontrolleri (örneğin, kredi kartı numarası formatı, banka hesap bilgileri kontrolü) ve hata işleme mantığı vardır. Eğer tüm bu mantığı tek bir if-else veya switch-case bloğu içinde yönetmeye çalışırsak, kod hızla okunaksız, bakımı zor ve yeni ödeme yöntemleri eklemeye karşı dirençli hale gelir. Örneğin, OdemeIslemcisi adında merkezi bir sınıfımız olsaydı ve bu sınıf tüm ödeme yöntemlerinin detaylarını bilmek zorunda kalsaydı, her yeni ödeme yöntemi eklendiğinde bu sınıfı değiştirmemiz gerekirdi. Bu, Tek Sorumluluk Prensibi'ne (Single Responsibility Principle) aykırıdır ve kod tekrarına yol açar. Strateji Deseni, bu problemi zarif bir şekilde çözer. Her bir ödeme yöntemini ayrı bir "ödeme stratejisi" olarak tanımlarız. 1. Strateji Arayüzü: IOdemeStratejisi gibi bir arayüz tanımlarız. Bu arayüz, OdemeYap(Siparis siparis, OdemeBilgileri odemeBilgileri) gibi temel bir metodu içerebilir. Her somut ödeme stratejisi, bu metodu kendi benzersiz mantığıyla uygulayacaktır. 2. Somut Stratejiler: KrediKartiOdemeStratejisi, BankaHavalasiOdemeStratejisi, MobilOdemeStratejisi gibi sınıflar oluştururuz. Her biri IOdemeStratejisi arayüzünü uygular ve kendi ödeme akışını yönetir. Örneğin: * KrediKartiOdemeStratejisi: Kart bilgilerini doğrular, ödeme ağ geçidi API'si ile iletişim kurar, işlemi tamamlar. * BankaHavalasiOdemeStratejisi: Müşteriye banka hesap bilgilerini gösterir, ödemenin belirli bir süre içinde yapılmasını bekler, ödeme alındığında siparişi günceller. * MobilOdemeStratejisi: Mobil ödeme sağlayıcısının SDK'sını veya API'sini kullanarak işlemi başlatır, kullanıcının onayını bekler. 3. Bağlam: OdemeIslemcisi veya Siparis sınıfı, bir IOdemeStratejisi referansı tutar. Müşteri bir ödeme yöntemi seçtiğinde, OdemeIslemcisi bu referansı seçilen stratejinin bir örneğiyle günceller ve ardından OdemeYap metodunu çağırır. OdemeIslemcisi, hangi ödeme yönteminin kullanıldığını bilmez; sadece ödeme stratejisinin arayüzünü çağırır. İşte bu gerçek dünya vakasının basit bir kod örneği:

html


public interface IOdemeStratejisi
{
bool OdemeYap(decimal tutar);
}

public class KrediKartiOdemeStratejisi : IOdemeStratejisi
{
private readonly string _kartNumarasi;
private readonly string _sonKullanmaTarihi;

public KrediKartiOdemeStratejisi(string kartNumarasi, string sonKullanmaTarihi)
{
_kartNumarasi = kartNumarasi; // Gerçekte daha fazla bilgi ve validasyon
_sonKullanmaTarihi = sonKullanmaTarihi;
}

public bool OdemeYap(decimal tutar)
{
Console.WriteLine($"Kredi kartı ile {tutar:C} tutarında ödeme yapılıyor. Kart: {(_kartNumarasi.Length > 4 ? _kartNumarasi.Substring(_kartNumarasi.Length - 4) : _kartNumarasi)}");
// Harici ödeme ağ geçidi (Stripe, Braintree, Iyzico vb.) ile entegrasyon burada yapılır
if (tutar > 0 && !string.IsNullOrEmpty(_kartNumarasi) && _kartNumarasi.Length == 16) // Basit validasyon
{
Console.WriteLine("Ödeme başarılı: Kredi Kartı.");
return true;
}
Console.WriteLine("Ödeme başarısız: Kredi Kartı validasyon hatası veya harici servis reddi.");
return false;
}
}

public class BankaHavalasiOdemeStratejisi : IOdemeStratejisi
{
private readonly string _iban;
private readonly string _hesapSahibi;

public BankaHavalasiOdemeStratejisi(string iban, string hesapSahibi)
{
_iban = iban;
_hesapSahibi = hesapSahibi;
}

public bool OdemeYap(decimal tutar)
{
Console.WriteLine($"Banka havalesi ile {tutar:C} tutarında ödeme bekleniyor.");
Console.WriteLine($"Lütfen şu hesaba ödeme yapınız: IBAN: {_iban}, Hesap Sahibi: {_hesapSahibi}");
Console.WriteLine("Ödeme, banka onayı alındığında tamamlanacaktır.");
// Gerçek dünyada bu, ödemenin bir süre sonra kontrol edilmesi gereken asenkron bir süreçtir.
return true; // İşlem başlatıldı, henüz tamamlanmadı
}
}

public class OdemeIslemcisi
{
private IOdemeStratejisi _odemeStratejisi;

public OdemeIslemcisi(IOdemeStratejisi odemeStratejisi)
{
_odemeStratejisi = odemeStratejisi;
}

public void StratejiyiBelirle(IOdemeStratejisi odemeStratejisi)
{
_odemeStratejisi = odemeStratejisi;
}

public bool IslemYap(decimal tutar)
{
Console.WriteLine("\nÖdeme işlemi başlatılıyor...");
return _odemeStratejisi.OdemeYap(tutar);
}
}

// Kullanım Örneği:
// public static void Main(string[] args)
// {
// decimal siparisTutari = 199.99m;
//
// // Kredi kartı ile ödeme
// OdemeIslemcisi islemci1 = new OdemeIslemcisi(new KrediKartiOdemeStratejisi("1234567890123456", "12/25"));
// islemci1.IslemYap(siparisTutari);
//
// // Banka havalesi ile ödeme
// OdemeIslemcisi islemci2 = new OdemeIslemcisi(new BankaHavalasiOdemeStratejisi("TR12345...", "Şirket Adı A.Ş."));
// islemci2.IslemYap(siparisTutari);
//
// // Strateji çalışma zamanında değiştirilebilir
// islemci1.StratejiyiBelirle(new BankaHavalasiOdemeStratejisi("TR98765...", "Diğer Şirket"));
// islemci1.IslemYap(250m);
// }

Bu yapı, yeni bir ödeme yöntemi eklendiğinde (örneğin, PayPal, Bitcoin), sadece IOdemeStratejisi arayüzünü uygulayan yeni bir sınıf oluşturmamız gerektiği anlamına gelir. OdemeIslemcisi sınıfında herhangi bir değişiklik yapmaya gerek kalmaz, bu da kodu oldukça esnek ve bakımı kolay hale getirir. Her bir stratejinin kendine ait kuralları ve entegrasyon detayları izole edilmiş olur.

Ödeme Yöntemi Stratejisi Uyguladığı İş Kuralları/Adımlar Özellikleri
KrediKartiOdemeStratejisi Kart numarası, son kullanma tarihi, CVV doğrulama. Ödeme ağ geçidi API entegrasyonu. Anında işlem, yüksek güvenlik gereksinimi, PCI DSS uyumluluğu.
BankaHavalasiOdemeStratejisi IBAN ve hesap sahibi bilgisi. Manuel veya otomatik (API ile) ödeme kontrolü. Daha uzun işlem süresi, düşük komisyon, manuel hata izleme.
MobilOdemeStratejisi Telefon numarası doğrulama, mobil uygulama ile onay. Mobil operatör API entegrasyonu. Kullanıcı dostu, mikro ödemeler için uygun, mobil ağ bağımlılığı.

Strateji Desenini Gelişmiş Tekniklerle Birleştirmek

Strateji Deseni kendi başına güçlü olsa da, diğer tasarım desenleri ve tekniklerle birleştirildiğinde gerçek potansiyelini ortaya çıkarır. Bu kombinasyonlar, sisteminizi daha da esnek, yönetilebilir ve ölçeklenebilir hale getirebilir.

Fabrika Deseni (Factory Pattern) ile Strateji Seçimi Nasıl Otomatize Edilir?

Önceki örneklerde stratejileri doğrudan new anahtar kelimesiyle oluşturduk. Ancak gerçek bir uygulamada, hangi stratejinin kullanılacağına dair karar mantığı karmaşık olabilir. Müşteri tipi, siparişin büyüklüğü, ürün kategorisi veya günün saati gibi faktörlere bağlı olarak farklı stratejiler seçmemiz gerekebilir. Bu karar mantığını doğrudan Bağlam (Context) sınıfının içine yerleştirmek, Bağlam sınıfının Tek Sorumluluk Prensibi'ni ihlal etmesine neden olur. İşte bu noktada Fabrika Deseni (Factory Pattern) devreye girer. Fabrika Deseni, nesnelerin oluşturulmasından sorumlu olan bir sınıftır. Strateji Deseni ile birleştirildiğinde, Fabrika sınıfı, belirli koşullara göre uygun IIndirimStratejisi veya IOdemeStratejisi örneğini oluşturmaktan sorumlu olur.

html


public class IndirimStratejisiFabrikasi
{
public IIndirimStratejisi StratejiOlustur(string musteriTipi, decimal siparisTutari)
{
// Gerçek bir uygulamada, bu bilgiler bir veritabanından,
// bir yapılandırma dosyasından veya harici bir servisden gelebilir.
if (musteriTipi == "Sadık")
{
return new SadikMusteriIndirimStratejisi(5); // Sadık müşterilere ekstra %5
}
else if (siparisTutari > 500)
{
return new YuzdeIndirimStratejisi(20); // Büyük siparişlere %20
}
else if (siparisTutari > 200)
{
return new SabitTutarIndirimStratejisi(25); // Orta büyüklükteki siparişlere 25 TL
}
else
{
return new YuzdeIndirimStratejisi(5); // Varsayılan %5 indirim
}
}
}

// Kullanım örneği:
// public static void Main(string[] args)
// {
// IndirimStratejisiFabrikasi fabrika = new IndirimStratejisiFabrikasi();
//
// // Müşteri 1: Normal müşteri, 300 TL sipariş
// IIndirimStratejisi strateji1 = fabrika.StratejiOlustur("Normal", 300m);
// IndirimUygulayici uygulayici1 = new IndirimUygulayici(strateji1);
// Console.WriteLine($"Normal müşteri (300 TL) indirimi: {uygulayici1.UygulanacakIndirimiHesapla(300m):C}");
//
// // Müşteri 2: Sadık müşteri, 100 TL sipariş
// IIndirimStratejisi strateji2 = fabrika.StratejiOlustur("Sadık", 100m);
// IndirimUygulayici uygulayici2 = new IndirimUygulayici(strateji2);
// Console.WriteLine($"Sadık müşteri (100 TL) indirimi: {uygulayici2.UygulanacakIndirimiHesapla(100m):C}");
// }

Bu yaklaşım, strateji seçimi mantığını Fabrika sınıfına devrederek Bağlam sınıfını daha temiz tutar. Yeni bir strateji eklediğinizde veya seçim mantığını değiştirdiğinizde, sadece Fabrika sınıfında değişiklik yapmanız yeterli olur.

Bağımlılık Enjeksiyonu (Dependency Injection) ile Yönetim ve Test Edilebilirlik

Bağımlılık Enjeksiyonu (DI), bileşenler arasındaki bağımlılıkları yönetmeyi kolaylaştıran bir tekniktir. Strateji Deseni ile birlikte kullanıldığında, Bağlam sınıfının kendi stratejisini oluşturmak yerine, stratejinin dışarıdan (bir DI kabı tarafından) kendisine sağlanmasını sağlar. Bu, gevşek bağlantıyı daha da artırır ve özellikle birim testlerini (unit tests) kolaylaştırır.

html


// Bağlam sınıfının constructor'ı aracılığıyla bağımlılık enjeksiyonu
public class IndirimUygulayiciDI
{
private readonly IIndirimStratejisi _indirimStratejisi;

// Bağımlılık Enjeksiyonu (Constructor Injection)
public IndirimUygulayiciDI(IIndirimStratejisi indirimStratejisi)
{
_indirimStratejisi = indirimStratejisi ?? throw new ArgumentNullException(nameof(indirimStratejisi));
}

public decimal UygulanacakIndirimiHesapla(decimal siparisTutari)
{
return _indirimStratejisi.IndirimHesapla(siparisTutari);
}
}

// Bir DI kabı (örneğin, Microsoft.Extensions.DependencyInjection) ile kullanım:
// public static void Main(string[] args)
// {
// // Bir hizmet koleksiyonu oluşturun
// var serviceCollection = new ServiceCollection();
//
// // Farklı senaryolara göre farklı stratejileri kaydedebiliriz
// // Örneğin, belirli bir HTTP isteği için veya belirli bir kullanıcı türü için.
// // Bu örnekte, sadece bir stratejiyi kaydettik.
// serviceCollection.AddTransient(
// sp => new YuzdeIndirimStratejisi(10)); // Varsayılan %10 indirim
//
// // Veya Fabrika ile birlikte kullanmak için:
// // serviceCollection.AddTransient(sp =>
// // {
// // var fabrika = sp.GetService();
// // return fabrika.StratejiOlustur("Normal", 0); // Varsayılan strateji seçimi
// // });
//
// serviceCollection.AddTransient();
//
// // Bir hizmet sağlayıcı oluşturun
// var serviceProvider = serviceCollection.BuildServiceProvider();
//
// // Uygulayıcıyı DI kabından alın
// var uygulayici = serviceProvider.GetService();
//
// decimal siparisTutari = 150m;
// decimal indirim = uygulayici.UygulanacakIndirimiHesapla(siparisTutari);
// Console.WriteLine($"DI ile hesaplanan indirim ({siparisTutari:C}): {indirim:C}");
// }

DI, özellikle büyük ve karmaşık uygulamalarda bileşenlerin yaşam döngüsünü ve bağımlılıklarını yönetmek için vazgeçilmezdir. Test sırasında, gerçek stratejiler yerine kolayca taklit (mock) veya sahte (stub) stratejiler enjekte ederek birim testlerini basitleştirebilirsiniz.

Kompozit Stratejiler ve Zincirleme Sorumluluk (Chain of Responsibility) Deseni

Bazı durumlarda, bir işlemi tamamlamak için birden fazla iş kuralının veya stratejinin art arda uygulanması gerekebilir. Örneğin, bir siparişe hem kampanya indirimi hem de sadık müşteri indirimi uygulamak isteyebilirsiniz. Bu tür senaryolar için "Kompozit Stratejiler" veya "Zincirleme Sorumluluk Deseni" (Chain of Responsibility Pattern) ile Strateji Deseni'ni birleştirebilirsiniz. * Kompozit Stratejiler: Birden fazla stratejiyi bir araya getiren bir IIndirimStratejisi uygulaması oluşturursunuz. Bu "Kompozit Strateji" sınıfı, içindeki tüm stratejileri çağırır ve sonuçlarını birleştirir.

html


public class KompozitIndirimStratejisi : IIndirimStratejisi
{
private readonly List _stratejiler;

public KompozitIndirimStratejisi(params IIndirimStratejisi[] stratejiler)
{
_stratejiler = new List(stratejiler);
}

public decimal IndirimHesapla(decimal siparisTutari)
{
decimal toplamIndirim = 0;
foreach (var strateji in _stratejiler)
{
// Her strateji mevcut sipariş tutarı üzerinden indirim uygular
// Veya her stratejinin indirimini mevcut tutardan düşerek ilerleyebiliriz (zincirleme)
toplamIndirim += strateji.IndirimHesapla(siparisTutari - toplamIndirim);
}
return toplamIndirim;
}
}
// Kullanım:
// var kompozit = new KompozitIndirimStratejisi(
// new YuzdeIndirimStratejisi(10),
// new SabitTutarIndirimStratejisi(5)
// );
// decimal toplamIndirim = kompozit.IndirimHesapla(100); // %10 + 5 TL indirim

*   Zincirleme Sorumluluk Deseni: Her bir stratejinin bir sonraki stratejiye referans tuttuğu ve bir kuralı uygulayıp uygulayamayacağına karar verdiği bir yapı kurarsınız. Eğer uygulayabiliyorsa uygular, aksi takdirde sorumluluğu bir sonraki stratejiye devreder. Bu, bir dizi iş kuralının sıralı olarak kontrol edildiği ve koşula uyan ilk kuralın uygulandığı senaryolar için idealdir.

Bu kombinasyonlar, iş kurallarınızın sadece takılabilir değil, aynı zamanda son derece yapılandırılabilir ve genişletilebilir olmasını sağlar.

Performans, Sürdürülebilirlik ve Mobil Uyumluluk İpuçları

Strateji Deseni ile iş kurallarımızı esnek hale getirmek harika, ancak bu esnekliği performans, sürdürülebilirlik ve modern kullanıcı deneyimi beklentileriyle birleştirmek önemlidir. Performans İpuçları: * Strateji Örneklerini Yeniden Kullanma (Caching): Eğer strateji sınıflarınızın durumu (state) yoksa (stateless), her çağrıda yeni bir örnek oluşturmak yerine önceden oluşturulmuş strateji örneklerini bir önbellekte (cache) tutarak yeniden kullanabilirsiniz. Bu, özellikle sıkça kullanılan stratejiler için nesne oluşturma maliyetini azaltır. * Tembel Yükleme (Lazy Loading): Nadiren kullanılan veya karmaşık stratejileri, gerçekten ihtiyaç duyulana kadar oluşturmaktan kaçının. Bu, uygulama başlangıç süresini veya belirli bir işlemin ilk çağrı süresini iyileştirebilir. * Strateji Seçimini Optimize Etme: Fabrika deseni kullanırken, strateji seçimi mantığının mümkün olduğunca hızlı çalışmasını sağlayın. Karmaşık koşullar veya veri tabanı sorguları gerektiren seçimleri önbelleğe almayı veya optimize etmeyi düşünün. Örneğin, Dictionary kullanarak stratejileri anahtarla hızlıca bulabilirsiniz. Sürdürülebilirlik ve Kod Kalitesi İpuçları: * SOLID Prensipleri: Strateji Deseni, doğası gereği Single Responsibility (Tek Sorumluluk) ve Open/Closed (Açık/Kapalı) prensiplerini destekler. Bu prensiplere sıkıca bağlı kalarak her stratejinin sadece tek bir işi yapmasını sağlayın ve yeni davranış eklemek için mevcut kodu değiştirmek yerine yeni stratejiler ekleyin. * Kapsamlı Testler: Her bir strateji, bağımsız bir birim olarak test edilebilir olduğu için, her strateji için kapsamlı birim testleri yazmak kod kalitesini garanti eder. Bu testler, gelecekteki değişikliklerin mevcut kuralları bozmadığından emin olmanızı sağlar. * Dokümantasyon: Her stratejinin ne işe yaradığını, hangi koşullarda kullanılması gerektiğini ve varsa özel gereksinimlerini açıkça belgeleyin. Özellikle Fabrika deseni kullanılıyorsa, strateji seçim mantığını iyi açıklayın. * Adlandırma Konvansiyonları: Strateji arayüzlerini ve somut strateji sınıflarını tutarlı ve açıklayıcı isimlerle adlandırın (örneğin, IIndirimStratejisi, YuzdeIndirimStratejisi). Mobil Uyumluluk ve Kullanıcı Deneyimi (UI/UX) İpuçları: Strateji Deseni bir arka uç (backend) tasarım deseni olsa da, uygulamanın ön yüzü (frontend) ile dolaylı yoldan ilişkilidir. Özellikle iş kurallarının kullanıcılara sunulduğu veya kullanıcıların kuralları etkilediği senaryolarda mobil uyumluluk önemlidir. * Kural Görselleştirme: Eğer kullanıcıların hangi kuralların uygulandığını veya uygulanabileceğini görmesi gerekiyorsa, bu bilgiyi mobil cihazlarda okunabilir ve anlaşılır bir şekilde sunmak önemlidir. Stratejilerinizin yapılandırılmış doğası, bu bilgiyi programatik olarak çekmenizi ve kullanıcı arayüzünde dinamik olarak görüntülemenizi sağlar. * Duyarlı Tasarım (Responsive Design): İş kurallarının sonuçlarını veya seçim arayüzlerini sunarken, mobil cihazlar için optimize edilmiş duyarlı tasarımlar kullanın. CSS media query'ler, farklı ekran boyutlarına ve yönlendirmelerine göre layout'u (düzeni) ve stilleri ayarlamanıza olanak tanır.

html


/* Genel stiller */
.rule-info-card {
padding: 20px;
margin: 15px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
border-radius: 8px;
background-color: #fff;
}

/* Mobil cihazlar için özelleştirilmiş kurallar (768px'den küçük ekranlar) */
@media (max-width: 768px) {
.rule-info-card {
margin: 10px;
padding: 15px;
}
h3.rule-title {
font-size: 1.2em;
}
p.rule-description {
font-size: 0.9em;
}
}

/* Küçük mobil cihazlar için (480px'den küçük ekranlar) */
@media (max-width: 480px) {
.rule-info-card {
margin: 5px;
padding: 10px;
box-shadow: none; /* Daha sade görünüm */
border: 1px solid #eee;
}
h3.rule-title {
font-size: 1em;
}
}

Bu tür media query'ler, iş kurallarının sonuçlarının veya seçim mekanizmalarının görsel sunumunun her cihazda optimal olmasını sağlar. Örneğin, bir kural listesini mobil cihazda tek sütunlu bir kart görünümünde, masaüstünde ise çok sütunlu bir tablo olarak gösterebilirsiniz.

Bu ipuçları, Strateji Deseni'nin sağladığı mimari avantajları pekiştirerek, uygulamanızın genel kalitesini ve kullanıcı deneyimini yükseltmenize yardımcı olacaktır.

Sonuç: Çekiç Sanatında Ustalaşmak ve Geleceğe Hazırlık

"Çekiç Sanatı" metaforuyla başladığımız bu yolculukta, Strateji Deseni'nin iş kurallarını esnek, takılabilir ve yönetilebilir hale getirme konusundaki gücünü derinlemesine inceledik. Yazılım sistemlerinin sürekli değişen iş gereksinimlerine ayak uydurabilmesi, artık bir lüks değil, bir zorunluluktur. Monolitik yapılarda sıkça karşılaşılan bakım kabusları, yüksek maliyetli değişiklikler ve uzun teslimat süreleri gibi sorunlara karşı Strateji Deseni, zarif ve etkili bir çözüm sunar.

Öğrendiklerimizi kısaca özetlersek:

* Esneklik Temeldir: İş kuralları dinamiktir ve yazılımımızın bu dinamizme ayak uydurması gerekir.
* Strateji Deseni Kurtarıcıdır: Davranışları bir arayüz arkasına soyutlayarak, farklı algoritmaları çalışma zamanında değiştirmemize olanak tanır.
* Temel Bileşenler: Strateji Arayüzü, Somut Stratejiler ve Bağlam olmak üzere üç ana bileşenle çalışır.
* Gerçek Dünya Uygulamaları: İndirim hesaplamalarından ödeme yöntemlerine kadar birçok senaryoda başarıyla kullanılabilir.
* Gelişmiş Kombinasyonlar: Fabrika Deseni ile strateji seçimini otomatize edebilir, Bağımlılık Enjeksiyonu ile yönetimi ve test edilebilirliği artırabilir, Kompozit veya Zincirleme Sorumluluk ile karmaşık kural akışlarını yönetebiliriz.
* Performans ve Sürdürülebilirlik: Caching, lazy loading, SOLID prensipleri ve kapsamlı testler ile desenin avantajlarını pekiştirebiliriz.
* Mobil Uyumluluk: Kural görselleştirmesi ve responsive tasarım ile kullanıcı deneyimini iyileştirebiliriz.

Bir yazılım geliştiricisi olarak, çekiç ve iskarpelanızı ustaca kullanarak, iş kurallarınızı sadece "işleyen" değil, aynı zamanda "güzel", "esnek" ve "geleceğe hazır" sistemler inşa etmenizi sağlayacak güçlü bir araçla donatmış olduk. Bu teknikleri pratiğe dökerek, kodunuzun kalitesini, geliştirme sürecinizin verimliliğini ve dolayısıyla işinize kattığınız değeri artırabilirsiniz. Unutmayın, iyi tasarlanmış bir mimari, değişimin getirdiği rüzgarlara karşı ayakta durabilen bir gemi gibidir.

Sıkça Sorulan Sorular (SSS)

1. Strateji deseni ne zaman kullanılmamalıdır?
Strateji deseni, yalnızca farklı algoritmalar veya iş kuralları arasında önemli davranışsal farklılıklar olduğunda kullanılmalıdır. Eğer fark sadece bir veya iki parametre ile yönetilebilecek basit bir varyasyonsa, daha basit bir if-else yapısı veya yapılandırma tabanlı bir çözüm daha uygun olabilir. Ayrıca, çok fazla strateji sınıfı oluşturmak kod tabanını karmaşıklaştırabilir; bu durumda Fabrika veya Bağımlılık Enjeksiyonu gibi desenlerle birleştirme düşünülmelidir.

2. Strateji deseni ile diğer davranışsal desenler arasındaki fark nedir?
Strateji deseni, bir görev için alternatif algoritmaları kapsüllerken, diğer desenlerin farklı odak noktaları vardır. Örneğin, "Şablon Metot (Template Method)" deseni, bir algoritmanın iskeletini tanımlar ve adımlarını alt sınıfların implemente etmesine bırakır; Strateji ise tüm algoritmayı değiştirir. "Komut (Command)" deseni, bir işlemi nesne olarak kapsüller, ancak çalışma zamanında davranış değiştirmekten çok, işlemleri kuyruklamak veya geri almak için kullanılır. "Durum (State)" deseni, bir nesnenin iç durumuna bağlı olarak davranışını değiştirmesine olanak tanır; Strateji ise istemcinin seçtiği stratejiye göre davranışı değiştirir.

3. Her iş kuralı için ayrı bir strateji mi yazmalıyız?
Hayır, her iş kuralı için ayrı bir strateji yazmak zorunda değilsiniz. Mantıklı bir şekilde gruplandırılabilecek veya aynı soyutlama seviyesinde ele alınabilecek benzer işlevlere sahip kurallar için tek bir strateji arayüzü ve birden fazla somut strateji oluşturabilirsiniz. Önemli olan, her stratejinin tek bir sorumluluğu olması ve bir birim olarak anlamlı olmasıdır. Örneğin, "İndirim Hesaplama" gibi genel bir kategori altında farklı indirim stratejileri gruplandırılabilir.

4. Stratejileri harici kaynaklardan (veri tabanı/dosya) yüklemek mümkün müdür?
Evet, kesinlikle mümkündür ve hatta çoğu zaman tercih edilen bir yaklaşımdır. Gelişmiş senaryolarda, strateji sınıflarını doğrudan kodda tanımlamak yerine, çalışma zamanında dinamik olarak yüklemek veya oluşturmak isteyebilirsiniz. Bu, bir "Kural Motoru" (Rule Engine) kullanmak veya .NET (veya Java) için Reflection ve Assembly yükleme yeteneklerini kullanarak derlenmiş strateji DLL'lerini (veya JAR'larını) yüklemek yoluyla yapılabilir. Bu, iş kullanıcılarının bile arayüzler aracılığıyla yeni kurallar tanımlamasına ve bu kuralların uygulamaya canlı olarak entegre edilmesine olanak tanır.

Yorumlar
İçeriği beğendiniz mi? Bir tartışma başlatın veya görüşlerinizi paylaşın.
Yorum Yaz

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

E-posta Bülteni
Yazılım Topluluğuna Katılın
En son güncellemeleri, yaratıcı ipuçlarını ve özel kaynakları doğrudan e-posta kutunuza alın. Tasarım ve inovasyonun geleceğini birlikte keşfedelim.