Gereksinimler ve Ön Hazırlık
Bu rehberdeki örnekleri uygulamak için sisteminizde Java Development Kit (JDK) 17 veya daha güncel bir sürümün kurulu olması önerilir. Modern Java özellikleri, özellikle arayüzler (interface) ve lambda ifadeleri ile Strategy kalıbını çok daha temiz bir şekilde uygulamanıza olanak tanır.
- JDK 17+: Modern sözdizimi ve performans iyileştirmeleri için.
- IDE: IntelliJ IDEA, Eclipse veya VS Code (Java eklentisi ile).
- Build Aracı: Maven veya Gradle (bağımlılık yönetimi için).
Projenizi oluştururken herhangi bir dış kütüphaneye ihtiyaç duymayacaksınız; tamamen Java'nın standart kütüphanelerini (Core Java) kullanacağız.
Adım 1: Strategy Arayüzünü Tanımlama
Strategy tasarım kalıbının ilk adımı, tüm algoritmaların uyması gereken ortak bir sözleşme (interface) oluşturmaktır. Bu arayüz, stratejilerin hangi metodu uygulayacağını belirler.
public interface OdemeStratejisi {
void ode(int miktar);
}
Burada OdemeStratejisi arayüzü, ödeme işlemini gerçekleştirecek tüm sınıflar için bir şablon görevi görür. Bu sayede, gelecekte yeni ödeme yöntemleri eklemek istediğimizde mevcut kod yapısını bozmamıza gerek kalmayacaktır.
Adım 2: Somut Strateji Sınıflarını Oluşturma
Şimdi, arayüzümüzü uygulayan somut sınıflarımızı yazalım. Örneğin, Kredi Kartı ve PayPal ile ödeme yöntemlerini ayrı ayrı tanımlayalım.
public class KrediKartiOdeme implements OdemeStratejisi {
@Override
public void ode(int miktar) {
System.out.println("Kredi kartı ile " + miktar + " TL ödendi.");
}
}
public class PayPalOdeme implements OdemeStratejisi {
@Override
public void ode(int miktar) {
System.out.println("PayPal ile " + miktar + " TL ödendi.");
}
}
Bu sınıflar, kendi içlerinde özelleşmiş mantıklarını barındırırlar. KrediKartiOdeme sınıfı kart doğrulama işlemlerini, PayPalOdeme ise API entegrasyonlarını içerebilir. Bu ayrım, kodun okunabilirliğini artırır.
Adım 3: Bağlam (Context) Sınıfını Hazırlama
Bağlam sınıfı, stratejiyi kullanan veya ona erişimi yöneten sınıftır. Bu sınıf, hangi stratejinin seçileceğini belirler ve işlemi o stratejiye delege eder.
public class OdemeIslemcisi {
private OdemeStratejisi strateji;
public void setStrateji(OdemeStratejisi strateji) {
this.strateji = strateji;
}
public void odemeYap(int miktar) {
if (strateji == null) {
throw new IllegalStateException("Ödeme stratejisi seçilmedi!");
}
strateji.ode(miktar);
}
}
OdemeIslemcisi sınıfı, stratejiyi çalışma zamanında değiştirebilmemize olanak tanır. setStrateji metodu sayesinde kullanıcı, dinamik olarak ödeme yöntemini değiştirebilir.
Adım 4: Dinamik Algoritma Seçimi ve Test Etme
Şimdi tüm parçaları bir araya getirerek dinamik seçimin nasıl çalıştığını görelim. Kullanıcıdan gelen girdiye göre strateji ataması yapacağız.
public class Main {
public static void main(String[] args) {
OdemeIslemcisi islemci = new OdemeIslemcisi();
// Senaryo 1: Kredi Kartı
islemci.setStrateji(new KrediKartiOdeme());
islemci.odemeYap(100);
// Senaryo 2: PayPal
islemci.setStrateji(new PayPalOdeme());
islemci.odemeYap(250);
}
}
Bu örnekte, setStrateji metodunu kullanarak çalışma zamanında algoritmayı değiştirdik. Uygulama, hangi stratejinin aktif olduğunu bilmeden sadece ode metodunu çağırır.
Adım 5: Strategy Kalıbı Karşılaştırma Tablosu
Strategy kalıbının geleneksel if-else yapısı ile karşılaştırması, neden bu kalıbı tercih etmeniz gerektiğini daha iyi açıklar.
| Özellik | If-Else Yapısı | Strategy Kalıbı |
|---|---|---|
| Bakım | Zor (Kod büyüdükçe karmaşıklaşır) | Kolay (Her strateji ayrı sınıf) |
| Genişletilebilirlik | Düşük (Yeni durum eklemek için kod değişir) | Yüksek (Yeni sınıf eklemek yeterli) |
| Test Edilebilirlik | Zor | Çok Kolay (Birim testleri bağımsız yapılır) |
Adım 6: İleri Seviye: Lambda İfadeleri ile Kullanım
Java 8 ve sonrası ile birlikte, her strateji için ayrı bir sınıf oluşturmak zorunda değilsiniz. Eğer strateji mantığınız çok basitse, lambda ifadelerini kullanabilirsiniz.
public class LambdaMain {
public static void main(String[] args) {
OdemeIslemcisi islemci = new OdemeIslemcisi();
// Lambda kullanarak sınıf oluşturmadan strateji tanımlama
islemci.setStrateji(miktar -> System.out.println("Kripto ile " + miktar + " ödendi."));
islemci.odemeYap(500);
}
}
Bu yaklaşım, kodunuzu daha kompakt hale getirir ve gereksiz sınıf dosyası kalabalığını önler. Özellikle küçük, tek seferlik stratejiler için idealdir.
Güvenlik Uyarısı: Strateji sınıflarınızı tasarlarken dışarıdan gelen verilerin (örneğin kullanıcıdan alınan ödeme miktarı) her zaman doğrulanmış (validation) olduğundan emin olun. Strateji kalıbı iş mantığını ayırsa da, veri girişi güvenliği (SQL injection veya XSS önlemleri) yine de uygulamanızın giriş noktalarında ele alınmalıdır.
Sıkça Sorulan Sorular
Strategy Kalıbı performans kaybına neden olur mu?
Modern JVM'ler, arayüz kullanımı ve nesne oluşturma konusunda oldukça optimize edilmiştir. Strategy kalıbının getirdiği performans farkı, genellikle ihmal edilebilir düzeydedir; kodun sağladığı esneklik bu küçük farktan çok daha değerlidir.
Çok fazla strateji sınıfı oluşturmak projeyi karmaşıklaştırır mı?
Eğer stratejileriniz çok küçükse, yukarıda gösterdiğimiz lambda ifadelerini veya anonim sınıfları kullanarak sınıf sayısını azaltabilirsiniz. Ancak her strateji kendi içinde karmaşık mantıklar barındırıyorsa, ayrı sınıflar en temiz yaklaşımdır.
Strategy kalıbı ile State kalıbı arasındaki fark nedir?
Strategy kalıbında kullanıcı, hangi algoritmanın kullanılacağını genellikle dışarıdan belirler. State (Durum) kalıbında ise nesne, kendi iç durumuna göre stratejisini otomatik olarak değiştirir.
Bu kalıp sadece ödeme sistemlerinde mi kullanılır?
Hayır; dosya sıkıştırma algoritmaları, sıralama yöntemleri, veritabanı bağlantı tipleri veya farklı indirim hesaplama motorları gibi her türlü "alternatif işlem" senaryosunda kullanılabilir.
Uygulama sırasında yaygın yapılan hata nedir?
En yaygın hata, bağlam (context) sınıfının strateji detaylarını bilmesidir. Bağlam sınıfı asla somut strateji sınıflarını (örneğin KrediKartiOdeme) doğrudan çağırmamalı, her zaman arayüz (OdemeStratejisi) üzerinden işlem yapmalıdır.
Strateji Nesneleri İçin Fabrika (Factory) Entegrasyonu
Strategy tasarım kalıbını bir adım ileriye taşıyarak, nesne oluşturma sürecini merkezi bir noktaya toplamak, kodun sürdürülebilirliğini artırır. Bu yaklaşımda, PaymentFactory sınıfı, hangi ödeme stratejisinin kullanılacağına çalışma zamanında karar verir ve ilgili nesneyi döner.
public class PaymentFactory {
public static PaymentStrategy getStrategy(String type) {
switch (type.toLowerCase()) {
case "kredi":
return new KrediKartiOdeme();
case "paypal":
return new PayPalOdeme();
case "bitcoin":
return new BitcoinOdeme();
default:
throw new IllegalArgumentException("Geçersiz ödeme yöntemi: " + type);
}
}
}
Bu yapı sayesinde, Main sınıfınızda yeni bir strateji eklemek istediğinizde mevcut iş mantığını değiştirmek yerine sadece fabrikayı güncellemeniz yeterli olacaktır. Bu, Open/Closed Principle (Açık/Kapalı Prensibi) ile tam uyumludur.
Strateji Kalıbında Birim Test (Unit Testing) Stratejileri
Strategy kalıbı, kodunuzun test edilebilirliğini (testability) ciddi oranda artırır. Her bir strateji sınıfı bağımsız olduğu için, bu sınıfları izole bir şekilde test edebilirsiniz. JUnit ve Mockito kullanarak, bağlam (context) sınıfınızın doğru stratejiyi tetikleyip tetiklemediğini şu şekilde doğrulayabilirsiniz:
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
public class PaymentContextTest {
@Test
public void testPaymentExecution() {
PaymentStrategy mockStrategy = mock(PaymentStrategy.class);
PaymentContext context = new PaymentContext(mockStrategy);
context.executePayment(100.0);
verify(mockStrategy, times(1)).pay(100.0);
}
}
Test Sürecinde Dikkat Edilmesi Gerekenler
- Sınır Değer Analizi: Ödeme tutarının 0 veya negatif olduğu durumları her strateji sınıfı için ayrı ayrı test edin.
- Mocking: Strateji sınıflarınız veritabanı veya dış API bağlantısı içeriyorsa, bu bağımlılıkları mutlaka mock nesneleri ile değiştirin.
- Hata Durumları: Strateji sınıfının fırlattığı özel exception'ların bağlam sınıfı tarafından doğru yakalanıp yakalanmadığını kontrol edin.
Dağıtık Sistemlerde Strateji Kalıbı ve Deployment
Büyük ölçekli projelerde, strateji sınıflarınızın farklı modüllerde veya mikroservislerde yer alması gerekebilir. Bu durumda, strateji sınıflarını bir Service Provider Interface (SPI) yapısı ile yüklemek, uygulamanın yeniden derlenmesine gerek kalmadan yeni stratejiler eklemenize olanak tanır.
İpucu: Eğer uygulamanız Spring Framework kullanıyorsa,@Serviceanotasyonuna sahip tüm strateji sınıflarını birListiçerisinde enjekte edebilir ve çalışma zamanında bu liste üzerinden dinamik seçim yapabilirsiniz. Bu, manuel fabrika sınıfı yazma ihtiyacını tamamen ortadan kaldırır.
Sonuç
Java ile Strategy tasarım kalıbını kullanmak, kod tabanınızı daha modüler, test edilebilir ve sürdürülebilir hale getirmenin en etkili yollarından biridir. Bu rehberde, bir arayüz tanımlayarak başladık, somut stratejilerimizi oluşturduk ve bunları dinamik olarak yöneten bir bağlam sınıfı kurduk. Son olarak, modern Java'nın sunduğu lambda avantajlarını inceledik.
Bir sonraki adım olarak, bu yapıyı bir "Factory" (Fabrika) tasarım kalıbı ile birleştirerek, strateji nesnelerinin oluşturulmasını tamamen otomatize etmeyi deneyebilirsiniz. Böylece Main sınıfınızda new KrediKartiOdeme() gibi ifadeler yerine, strateji tipini bir parametre olarak verip nesneyi fabrikadan alabilirsiniz.
Sorumluluk Reddi: Bu makalede paylaşılan kod örnekleri eğitim amaçlıdır. Üretim ortamında (production) kullanacağınız yazılımlarda, özellikle finansal işlemler ve kullanıcı verisi işleme süreçlerinde, Java'nın güvenlik standartlarına (OWASP vb.) uyduğunuzdan ve gerekli tüm hata yönetimi (try-catch) mekanizmalarını kurduğunuzdan emin olun.
Yorumlar (0)
Yorum Yaz