İçeriğe geç

Strategy Design Pattern: Koşullu Mantığı Zarif Kodla Değiştirmenin Yolu

T
Tolgahan
· · 15 dk okuma · 57 görüntülenme

Strategy Design Pattern: Koşullu Mantığı Zarif Kodla Değiştirmenin Yolu

Bir ödeme sistemi geliştirdiğini düşün. Kredi kartı, havale, kripto para, mobil ödeme… Her biri farklı çalışıyor, farklı doğrulama kuralları var, farklı API'lere bağlanıyor. İlk instinct'in ne olurdu? Muhtemelen büyük bir if-else ya da switch bloğu yazmak. İlk iki ödeme yöntemi için bu çalışır, sorun yok. Ama üçüncü, dördüncü, beşinci yöntem geldiğinde o kod bloğu bir canavara dönüşür. Yeni bir ödeme yöntemi eklemek için mevcut kodu açıp değiştirmen gerekir, test etmen gereken yer sayısı katlanır, bir yöntemi düzeltirken diğerini bozma riski artar.

İşte Strategy Design Pattern tam olarak bu sorunu çözmek için var. GoF'un (Gang of Four) davranışsal (behavioral) tasarım kalıplarından biri olan Strategy, bir algoritma ailesini tanımlar, her birini ayrı bir sınıfa kapsüller ve bunları birbirleriyle değiştirilebilir (interchangeable) hale getirir. Yani çalışma zamanında "hangi algoritmayı kullanacağım" kararını esnek, temiz ve genişletilebilir şekilde vermen sağlanır.

Bu yazıda Strategy Pattern'ı gerçekten anlayacaksın. Sadece UML diyagramı gösterip geçmeyeceğiz — gerçek dünya senaryolarıyla, çalışan kod örnekleriyle ve profesyonellerin bildiği ince noktalarla derinlemesine dalacağız.

Strategy Pattern'ın Anatomisi

Strategy Pattern üç temel bileşenden oluşur. Bunu bir restoran analojisiyle düşünelim:

Strategy (Strateji Arayüzü): Restorandaki "yemek pişirme" konsepti. Ne pişirileceğinden bağımsız olarak, her yemek bir şekilde pişirilir. Bu, ortak bir kontrat (interface) tanımlar.

Concrete Strategy (Somut Stratejiler): Farklı pişirme yöntemleri — ızgara, fırın, tava, buhar. Her biri aynı işi (pişirme) farklı şekilde yapar. Bunlar arayüzü uygulayan (implement eden) somut sınıflardır.

Context (Bağlam): Şef. Hangi pişirme yöntemini kullanacağına karar verir ve o yöntemi uygular. Ama şef, her pişirme yönteminin iç detayını bilmek zorunda değil — sadece "pişir" der.

Bu üçlü yapı sayesinde yeni bir pişirme yöntemi eklemek istediğinde şefin koduna dokunmazsın. Sadece yeni bir pişirme sınıfı yazarsın. Bu, SOLID prensiplerinden Open/Closed Principle'ın (Açık/Kapalı Prensibi) tam karşılığıdır: genişletmeye açık, değişikliğe kapalı.

İlk Adım: if-else Cehenneminden Çıkış

Önce sorunu net görelim. Aşağıdaki kod, Strategy Pattern olmadan yazılmış bir indirim hesaplama sistemi:

// ❌ Kötü yaklaşım: if-else cehennemi
public class DiscountCalculator {

    public double calculateDiscount(String customerType, double amount) {
        if (customerType.equals("REGULAR")) {
            // Normal müşteri: %5 indirim
            return amount * 0.05;
        } else if (customerType.equals("PREMIUM")) {
            // Premium müşteri: %15 indirim, min 50 TL
            double discount = amount * 0.15;
            return Math.max(discount, 50.0);
        } else if (customerType.equals("VIP")) {
            // VIP müşteri: %25 indirim + ekstra 100 TL
            return (amount * 0.25) + 100.0;
        } else if (customerType.equals("EMPLOYEE")) {
            // Çalışan: %40 indirim, max 500 TL
            double discount = amount * 0.40;
            return Math.min(discount, 500.0);
        } else {
            return 0;
        }
    }
}

Bu kodun sorunları:

  1. Yeni müşteri tipi eklemek mevcut metodu değiştirmeyi gerektirir (Open/Closed ihlali).

  2. Test etmesi zor — tek bir metotta birden fazla davranış var, her dalı ayrı test etmen gerekiyor ama hepsi iç içe.

  3. String karşılaştırma hata kaynağı — bir yerde "PREMUM" yazarsan derleyici uyarmaz.

  4. Büyüme problemi — 10 müşteri tipinde bu metot 100+ satıra çıkar.

Şimdi aynı işi Strategy Pattern ile yapalım.

Strategy Pattern ile Temiz Çözüm

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

// Strateji arayüzü — tüm indirim stratejilerinin uyması gereken kontrat
public interface DiscountStrategy {

    double calculateDiscount(double amount);

    // Bu strateji hangi müşteri tipi için geçerli?
    String getCustomerType();
}

Adım 2: Somut Stratejileri Yaz

// Normal müşteri stratejisi
public class RegularDiscount implements DiscountStrategy {

    @Override
    public double calculateDiscount(double amount) {
        return amount * 0.05;
    }

    @Override
    public String getCustomerType() {
        return "REGULAR";
    }
}

// Premium müşteri stratejisi
public class PremiumDiscount implements DiscountStrategy {

    @Override
    public double calculateDiscount(double amount) {
        double discount = amount * 0.15;
        return Math.max(discount, 50.0);
    }

    @Override
    public String getCustomerType() {
        return "PREMIUM";
    }
}

// VIP müşteri stratejisi
public class VipDiscount implements DiscountStrategy {

    @Override
    public double calculateDiscount(double amount) {
        return (amount * 0.25) + 100.0;
    }

    @Override
    public String getCustomerType() {
        return "VIP";
    }
}

// Çalışan stratejisi
public class EmployeeDiscount implements DiscountStrategy {

    @Override
    public double calculateDiscount(double amount) {
        double discount = amount * 0.40;
        return Math.min(discount, 500.0);
    }

    @Override
    public String getCustomerType() {
        return "EMPLOYEE";
    }
}

Adım 3: Context Sınıfını Yaz

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DiscountCalculator {

    private final Map<String, DiscountStrategy> strategies = new HashMap<>();

    // Stratejileri dışarıdan al — bağımlılık enjeksiyonu (Dependency Injection)
    public DiscountCalculator(List<DiscountStrategy> strategyList) {
        for (DiscountStrategy strategy : strategyList) {
            strategies.put(strategy.getCustomerType(), strategy);
        }
    }

    public double calculateDiscount(String customerType, double amount) {
        DiscountStrategy strategy = strategies.get(customerType);

        if (strategy == null) {
            throw new IllegalArgumentException(
                "Bilinmeyen müşteri tipi: " + customerType
            );
        }

        return strategy.calculateDiscount(amount);
    }
}

Fark ettiysen, DiscountCalculator artık hiçbir indirim mantığı bilmiyor. Sadece doğru stratejiyi buluyor ve işi ona devrediyor. Yeni bir müşteri tipi eklemek için tek yapman gereken: yeni bir sınıf yazıp listeye eklemek. Mevcut hiçbir koda dokunmuyorsun.

Kullanım

public class Main {

    public static void main(String[] args) {
        // Stratejileri oluştur
        List<DiscountStrategy> strategies = List.of(
            new RegularDiscount(),
            new PremiumDiscount(),
            new VipDiscount(),
            new EmployeeDiscount()
        );

        DiscountCalculator calculator = new DiscountCalculator(strategies);

        // Kullanım
        double amount = 1000.0;

        System.out.println("Regular: " + calculator.calculateDiscount("REGULAR", amount));
        // Çıktı: Regular: 50.0

        System.out.println("Premium: " + calculator.calculateDiscount("PREMIUM", amount));
        // Çıktı: Premium: 150.0

        System.out.println("VIP: " + calculator.calculateDiscount("VIP", amount));
        // Çıktı: VIP: 350.0

        System.out.println("Employee: " + calculator.calculateDiscount("EMPLOYEE", amount));
        // Çıktı: Employee: 400.0
    }
}

Spring Boot ile Strategy Pattern: Gerçek Dünya Uygulaması

Spring Boot'un Dependency Injection (bağımlılık enjeksiyonu) mekanizması, Strategy Pattern ile mükemmel uyum sağlar. Spring, aynı arayüzü implement eden tüm bean'leri otomatik olarak bir listeye toplayabilir. Bu, strateji yönetimini neredeyse sıfır eforla yapmanı sağlar.

Gerçek bir senaryo üzerinden gidelim: bir bildirim (notification) sistemi. E-posta, SMS, push notification ve Slack — her biri farklı servislerle entegre çalışıyor.

// Strateji arayüzü
public interface NotificationStrategy {

    void send(String recipient, String message);

    // Bu strateji hangi kanal için geçerli?
    String getChannel();
}
import org.springframework.stereotype.Component;

// E-posta bildirimi
@Component
public class EmailNotification implements NotificationStrategy {

    @Override
    public void send(String recipient, String message) {
        // Gerçek projede JavaMailSender kullanılır
        System.out.println("📧 E-posta gönderildi → " + recipient);
        System.out.println("   İçerik: " + message);
    }

    @Override
    public String getChannel() {
        return "EMAIL";
    }
}

// SMS bildirimi
@Component
public class SmsNotification implements NotificationStrategy {

    @Override
    public void send(String recipient, String message) {
        // Gerçek projede Twilio, Netgsm vb. kullanılır
        System.out.println("📱 SMS gönderildi → " + recipient);
        System.out.println("   İçerik: " + message);
    }

    @Override
    public String getChannel() {
        return "SMS";
    }
}

// Push notification
@Component
public class PushNotification implements NotificationStrategy {

    @Override
    public void send(String recipient, String message) {
        // Gerçek projede Firebase Cloud Messaging kullanılır
        System.out.println("🔔 Push gönderildi → " + recipient);
        System.out.println("   İçerik: " + message);
    }

    @Override
    public String getChannel() {
        return "PUSH";
    }
}
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class NotificationService {

    private final Map<String, NotificationStrategy> strategies;

    // Spring, NotificationStrategy implement eden TÜM bean'leri otomatik enjekte eder
    public NotificationService(List<NotificationStrategy> strategyList) {
        this.strategies = strategyList.stream()
            .collect(Collectors.toMap(
                NotificationStrategy::getChannel,
                Function.identity()
            ));
    }

    public void sendNotification(String channel, String recipient, String message) {
        NotificationStrategy strategy = strategies.get(channel.toUpperCase());

        if (strategy == null) {
            throw new IllegalArgumentException(
                "Desteklenmeyen bildirim kanalı: " + channel
                + ". Mevcut kanallar: " + strategies.keySet()
            );
        }

        strategy.send(recipient, message);
    }

    // Tüm kanallara aynı anda gönder
    public void broadcast(String recipient, String message) {
        strategies.values().forEach(s -> s.send(recipient, message));
    }
}

Bu yaklaşımın güzelliğine bak: yeni bir bildirim kanalı eklemek istediğinde (örneğin Telegram), sadece yeni bir @Component sınıfı yazıyorsun. NotificationService'e dokunmuyorsun. Spring otomatik olarak yeni bean'i listeye ekliyor. Sıfır konfigürasyon, sıfır if-else.

Java Enum ile Hafif Strategy Pattern

Her zaman tam sınıf hiyerarşisine ihtiyacın olmayabilir. Stratejiler basit ve sayıca az ise, Java enum'ları şık bir alternatif sunar:

public enum SortStrategy {

    BUBBLE_SORT {
        @Override
        public void sort(int[] array) {
            // Bubble sort implementasyonu
            for (int i = 0; i < array.length - 1; i++) {
                for (int j = 0; j < array.length - i - 1; j++) {
                    if (array[j] > array[j + 1]) {
                        int temp = array[j];
                        array[j] = array[j + 1];
                        array[j + 1] = temp;
                    }
                }
            }
            System.out.println("Bubble Sort ile sıralandı");
        }
    },

    SELECTION_SORT {
        @Override
        public void sort(int[] array) {
            // Selection sort implementasyonu
            for (int i = 0; i < array.length - 1; i++) {
                int minIdx = i;
                for (int j = i + 1; j < array.length; j++) {
                    if (array[j] < array[minIdx]) {
                        minIdx = j;
                    }
                }
                int temp = array[minIdx];
                array[minIdx] = array[i];
                array[i] = temp;
            }
            System.out.println("Selection Sort ile sıralandı");
        }
    },

    INSERTION_SORT {
        @Override
        public void sort(int[] array) {
            // Insertion sort implementasyonu
            for (int i = 1; i < array.length; i++) {
                int key = array[i];
                int j = i - 1;
                while (j >= 0 && array[j] > key) {
                    array[j + 1] = array[j];
                    j--;
                }
                array[j + 1] = key;
            }
            System.out.println("Insertion Sort ile sıralandı");
        }
    };

    // Her enum sabiti bu metodu kendine göre uygular
    public abstract void sort(int[] array);
}
public class Main {

    public static void main(String[] args) {
        int[] data = {64, 34, 25, 12, 22, 11, 90};

        // Çalışma zamanında strateji seçimi
        SortStrategy strategy = SortStrategy.INSERTION_SORT;
        strategy.sort(data);

        // Sonucu yazdır
        System.out.print("Sonuç: ");
        for (int num : data) {
            System.out.print(num + " ");
        }
        // Çıktı: Insertion Sort ile sıralandı
        // Sonuç: 11 12 22 25 34 64 90
    }
}

Enum yaklaşımı, strateji sayısı sabit ve derleme zamanında bilinen senaryolar için idealdir. Yeni strateji eklemeye çok uygun değil (enum'a değişiklik gerekir), ama basitliği ve tip güvenliği büyük avantaj.

Fonksiyonel Yaklaşım: Lambda ile Strategy

Java 8+ ile birlikte Strategy Pattern'ı lambda ifadeleriyle (lambda expressions) çok daha kısa yazabilirsin. Strateji arayüzün tek metotlu (functional interface) ise, ayrı sınıflar yazmana bile gerek kalmaz:

import java.util.Map;
import java.util.function.UnaryOperator;

public class TextFormatter {

    // Stratejiler lambda olarak tanımlanıyor
    private static final Map<String, UnaryOperator<String>> STRATEGIES = Map.of(
        "uppercase",  String::toUpperCase,
        "lowercase",  String::toLowerCase,
        "capitalize", text -> {
            if (text == null || text.isEmpty()) return text;
            return Character.toUpperCase(text.charAt(0)) + text.substring(1).toLowerCase();
        },
        "slug",       text -> text.toLowerCase()
                                  .replaceAll("[^a-z0-9\\s-]", "")
                                  .replaceAll("\\s+", "-")
                                  .replaceAll("-+", "-"),
        "reverse",    text -> new StringBuilder(text).reverse().toString()
    );

    public static String format(String text, String strategy) {
        UnaryOperator<String> formatter = STRATEGIES.get(strategy);

        if (formatter == null) {
            throw new IllegalArgumentException("Bilinmeyen format: " + strategy);
        }

        return formatter.apply(text);
    }

    public static void main(String[] args) {
        String input = "Merhaba Dünya Strategy Pattern";

        System.out.println(format(input, "uppercase"));
        // Çıktı: MERHABA DÜNYA STRATEGY PATTERN

        System.out.println(format(input, "slug"));
        // Çıktı: merhaba-dnya-strategy-pattern

        System.out.println(format(input, "reverse"));
        // Çıktı: nrettaP ygetartS aynüD abahreM
    }
}

Lambda yaklaşımı, strateji mantığı kısa olduğunda (1-3 satır) mükemmel çalışır. Ama iş mantığı karmaşıksa, ayrı sınıflar hâlâ daha okunabilir ve test edilebilir.

Python'da Strategy Pattern

Python'un first-class function (birinci sınıf fonksiyon) desteği sayesinde Strategy Pattern çok doğal bir şekilde uygulanır. Hem sınıf tabanlı hem de fonksiyonel yaklaşımı görelim:

from abc import ABC, abstractmethod
from typing import Protocol


# Yaklaşım 1: Protocol ile (duck typing dostu)
class CompressionStrategy(Protocol):
    def compress(self, data: bytes) -> bytes: ...
    def decompress(self, data: bytes) -> bytes: ...


class GzipCompression:
    """Gzip sıkıştırma stratejisi"""

    def compress(self, data: bytes) -> bytes:
        import gzip
        return gzip.compress(data)

    def decompress(self, data: bytes) -> bytes:
        import gzip
        return gzip.decompress(data)


class ZlibCompression:
    """Zlib sıkıştırma stratejisi"""

    def compress(self, data: bytes) -> bytes:
        import zlib
        return zlib.compress(data)

    def decompress(self, data: bytes) -> bytes:
        import zlib
        return zlib.decompress(data)


class NoCompression:
    """Sıkıştırma yok — test ve debug için"""

    def compress(self, data: bytes) -> bytes:
        return data

    def decompress(self, data: bytes) -> bytes:
        return data


class FileProcessor:
    """Context sınıfı — sıkıştırma stratejisini kullanır"""

    def __init__(self, strategy: CompressionStrategy):
        self._strategy = strategy

    @property
    def strategy(self) -> CompressionStrategy:
        return self._strategy

    @strategy.setter
    def strategy(self, strategy: CompressionStrategy):
        # Çalışma zamanında strateji değiştirme
        self._strategy = strategy

    def process(self, data: bytes) -> bytes:
        compressed = self._strategy.compress(data)
        ratio = len(compressed) / len(data) * 100
        print(f"Orijinal: {len(data)} bytes → Sıkıştırılmış: {len(compressed)} bytes ({ratio:.1f}%)")
        return compressed


# Kullanım
if __name__ == "__main__":
    data = b"Strategy Pattern " * 100  # tekrarlanan veri sıkıştırmaya uygun

    processor = FileProcessor(GzipCompression())
    processor.process(data)
    # Çıktı: Orijinal: 1800 bytes → Sıkıştırılmış: 34 bytes (1.9%)

    # Çalışma zamanında strateji değiştir
    processor.strategy = ZlibCompression()
    processor.process(data)
    # Çıktı: Orijinal: 1800 bytes → Sıkıştırılmış: 22 bytes (1.2%)

    processor.strategy = NoCompression()
    processor.process(data)
    # Çıktı: Orijinal: 1800 bytes → Sıkıştırılmış: 1800 bytes (100.0%)

Python'da dikkat çekilmesi gereken bir nokta: Protocol kullanmak, sınıfların açıkça arayüzü implement etmesini zorunlu kılmaz — duck typing ile çalışır. Yani compress ve decompress metodları olan herhangi bir nesne, CompressionStrategy olarak kabul edilir. Bu esneklik Python'un güçlü yanı, ama aynı zamanda dikkatli olunması gereken bir nokta.

Yaygın Hatalar ve Tuzaklar

1. Her if-else'i Strategy'ye Dönüştürme Hastalığı

En yaygın hata: her koşullu ifadeyi Strategy Pattern'a çevirmeye çalışmak. İki-üç seçenekli basit bir if-else için tam bir strateji hiyerarşisi oluşturmak over-engineering'dir (aşırı mühendislik).

Kural: Eğer seçenek sayısı 2-3 ise ve artma ihtimali düşükse, basit if-else yeterlidir. Strategy Pattern, seçenek sayısının artacağını bildiğin veya her seçeneğin karmaşık mantık içerdiği durumlarda değer katar.

2. Context'in Stratejiyi Bilmesi

// ❌ Yanlış: Context, somut stratejiyi biliyor
public class PaymentProcessor {

    public void process(Payment payment) {
        if (strategy instanceof CreditCardStrategy) {
            // Kredi kartına özel bir şey yap
            ((CreditCardStrategy) strategy).validateCvv();
        }
        strategy.process(payment);
    }
}

Context sınıfı asla somut stratejiyi bilmemeli. instanceof kontrolü görüyorsan, arayüzün eksik tasarlanmış demektir. Eğer CVV doğrulaması gerekiyorsa, bu process metodunun içinde olmalı — strateji kendi sorumluluğunu kendisi taşımalı.

3. Strateji Seçimini Sabit Kodlamak

// ❌ Yanlış: Strateji seçimi sabit kodlanmış
public class OrderService {

    private final DiscountStrategy strategy = new PremiumDiscount(); // Sabit!

    public double getDiscount(double amount) {
        return strategy.calculateDiscount(amount);
    }
}

Strategy Pattern'ın tüm amacı, stratejiyi çalışma zamanında değiştirebilmek. Stratejiyi sabit kodlarsan pattern'ın faydası sıfıra iner. Strateji ya constructor'dan enjekte edilmeli, ya da bir factory/registry üzerinden dinamik olarak seçilmeli.

4. Strateji Arayüzünü Şişirmek

// ❌ Yanlış: Arayüz çok şişkin — her strateji hepsini uygulamak zorunda
public interface PaymentStrategy {
    void authorize();
    void capture();
    void refund();
    void partialRefund(double amount);
    void recurring();
    void validateCard();
    void tokenize();
    void threeDSecure();
}

Bir strateji arayüzü, Interface Segregation Principle'a (Arayüz Ayrıştırma Prensibi) uymalı. Tüm stratejilerin ihtiyaç duymadığı metotları arayüze koymak, gereksiz boş implementasyonlara veya UnsupportedOperationException fırlatan metotlara yol açar. Arayüzü küçük ve odaklı tut.

5. Durum Taşıyan (Stateful) Stratejiler

// ❌ Tehlikeli: Strateji durum taşıyor — thread-safe değil
@Component
public class CumulativeDiscount implements DiscountStrategy {

    private int callCount = 0; // Tehlike! Spring bean'leri singleton'dır

    @Override
    public double calculateDiscount(double amount) {
        callCount++; // Eşzamanlı erişimde race condition
        return amount * (0.05 * callCount);
    }
}

Spring'de bean'ler varsayılan olarak singleton'dır — yani tüm istekler aynı nesneyi paylaşır. Strateji sınıfında durum (state) tutarsan, çoklu iş parçacığı (multi-thread) ortamında race condition oluşur. Stratejiler mümkün olduğunca stateless (durumsuz) olmalı. Eğer duruma ihtiyaç varsa, @Scope("prototype") kullan veya durumu parametre olarak geçir.

Best Practices: Profesyonel İpuçları

1. Strateji Seçimi İçin Registry Pattern Kullan

Stratejileri bir Map'te topla ve string bazlı erişim sağla. Bu, özellikle REST API'lerde kullanıcıdan gelen parametre ile strateji seçerken çok işe yarar.

2. Default Strateji (Null Object Pattern) Tanımla

Bilinmeyen bir strateji istendiğinde exception fırlatmak yerine, varsayılan davranış sunan bir "no-op" strateji tanımlayabilirsin. Bu, Null Object Pattern ile Strategy Pattern'ın birleşimidir.

3. Strateji Değiştirmeyi Log'la

Çalışma zamanında strateji değişiklikleri debug edilmesi zor sorunlara yol açabilir. Her strateji değişikliğini log'lamak, production'da hayat kurtarır.

4. Strateji Seçim Mantığını Ayrı Tut

Strateji seçim mantığı (hangi koşulda hangi strateji) ile strateji uygulama mantığı (algoritmanın kendisi) ayrı olmalı. Factory Method veya Registry bu ayrımı sağlar.

5. Stratejileri Test Etmek Kolaydır — Avantajını Kullan

Her strateji bağımsız bir sınıf olduğu için, birim testleri (unit tests) yazmak çok kolay. Her stratejiyi izole şekilde test edebilirsin, mock'lara gerek kalmadan:

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;

class DiscountStrategyTest {

    @Test
    void regularDiscount_shouldApply5Percent() {
        DiscountStrategy strategy = new RegularDiscount();
        assertEquals(50.0, strategy.calculateDiscount(1000.0));
    }

    @Test
    void premiumDiscount_shouldApplyMinimum50() {
        DiscountStrategy strategy = new PremiumDiscount();
        // 200 * 0.15 = 30, ama minimum 50 olmalı
        assertEquals(50.0, strategy.calculateDiscount(200.0));
    }

    @Test
    void vipDiscount_shouldAdd100Extra() {
        DiscountStrategy strategy = new VipDiscount();
        // 1000 * 0.25 + 100 = 350
        assertEquals(350.0, strategy.calculateDiscount(1000.0));
    }

    @Test
    void employeeDiscount_shouldCapAt500() {
        DiscountStrategy strategy = new EmployeeDiscount();
        // 2000 * 0.40 = 800, ama max 500
        assertEquals(500.0, strategy.calculateDiscount(2000.0));
    }
}

Gördüğün gibi, her test tam olarak bir stratejiyi test ediyor. Mock yok, karmaşık setup yok, saf birim testi. Bu, Strategy Pattern'ın en büyük pratik faydalarından biri.

Strategy vs. Diğer Patternlar: Ne Zaman Hangisi?

Tasarım kalıpları arasında kafa karışıklığı yaşanabilir. İşte en sık karıştırılan kalıplarla farkları:

Strategy vs. State: Her ikisi de davranışı değiştirir, ama amaçları farklıdır. Strategy, aynı işi farklı yollarla yapmaktır (sıralama algoritması seçimi). State, nesnenin durumuna göre davranışını değiştirmektir (sipariş durumu: beklemede → onaylandı → kargoda). State'de geçişler otomatik olabilir, Strategy'de geçiş dışarıdan yapılır.

Strategy vs. Template Method: Template Method, algoritmayı sabit bir iskeletle tanımlayıp bazı adımları alt sınıflara bırakır (kalıtım bazlı). Strategy, tüm algoritmayı dışarıdan enjekte eder (kompozisyon bazlı). Modern yaklaşımda Strategy genellikle tercih edilir çünkü kalıtım yerine kompozisyon daha esnek ve test edilebilir.

Strategy vs. Command: Command, bir işlemi nesne olarak kapsülleyip kuyruklamak, geri almak (undo) veya zamanlamak için kullanılır. Strategy, bir algoritma seçimi yapmak içindir. Odak farklıdır: Command "ne yapılacağını", Strategy "nasıl yapılacağını" kapsüller.

Ne Zaman Strategy Pattern Kullanmalısın?

Strategy Pattern'ı kullanman gereken durumları özetleyelim:

  • Bir işlemi birden fazla yolla yapabiliyorsan ve bunlar arasında çalışma zamanında seçim yapman gerekiyorsa.

  • Aynı türdeki algoritmaların sayısı artabilecekse (bugün 3, yarın 7).

  • if-else veya switch bloğun 4+ dala ulaştıysa ve her dal karmaşık mantık içeriyorsa.

  • Algoritma değişikliğinin mevcut kodu etkilememesini istiyorsan.

  • Her algoritmayı bağımsız olarak test etmek istiyorsan.

Kullanmaman gereken durumlar:

  • Seçenek sayısı 2-3 ve artması beklenmiyor.

  • Algoritmalar arası fark sadece bir parametre (bu durumda parametrik çözüm yeterli).

  • Uygulaman küçük ve basit — over-engineering yapma.

Sonuç

Strategy Design Pattern, yazılım geliştirmede en sık karşına çıkacak ve en çok kullanacağın kalıplardan biri. Özetleyelim:

  • Ne yapar: Bir algoritma ailesini tanımlar, her birini ayrı sınıfa kapsüller, çalışma zamanında birinin yerine diğerini kullanmanı sağlar.

  • Temel motivasyon: if-else/switch bloklarını ortadan kaldırıp kodu genişletilebilir yapmak.

  • SOLID bağlantısı: Open/Closed Principle'ı doğrudan uygular. Yeni strateji eklemek mevcut kodu değiştirmez.

  • Spring Boot ile birlikte: List<Interface> enjeksiyonu sayesinde neredeyse sıfır eforla uygulanır.

  • Fonksiyonel alternatif: Java'da lambda, Python'da first-class functions ile hafif stratejiler oluşturulabilir.

  • Dikkat noktaları: Her if-else'i dönüştürme, stratejileri stateless tut, arayüzü şişirme, strateji seçimini log'la.

Strategy Pattern'ı gerçekten içselleştirdiğinde, koddaki if-else cehennemlerini farklı gözle görmeye başlarsın. "Bu koşullu yapıyı stratejiyle çözebilir miyim?" diye düşünmek, daha temiz ve sürdürülebilir kod yazmanın ilk adımıdır.

Paylaş:
Son güncelleme: Jun 05, 2026

Yorumlar

Giriş yapın ve yorum bırakın.

Henüz yorum yok

Düşüncelerinizi paylaşan ilk siz olun!

Bu yazıyı beğendiniz mi?

Bültene abone olun ve yeni yazılardan ilk siz haberdar olun. Spam yok, söz.

İlgili Yazılar