Takip et

Java’da Strategy Tasarım Deseni – Kapsamlı Bir Örnek ve Uygulama Rehberi

Java’da Strategy Tasarım Deseni – Kapsamlı Bir Örnek ve Uygulama Rehberi Java programlama dünyasında, yazılımın esnekliğini, genişletilebil

Java’da Strategy Tasarım Deseni – Kapsamlı Bir Örnek ve Uygulama Rehberi

Java programlama dünyasında, yazılımın esnekliğini, genişletilebilirliğini ve bakımını kolaylaştırmak için çeşitli tasarım desenleri kullanılır. Bu desenler, yazılım geliştiricilere karşılaşılan yaygın sorunlara kanıtlanmış çözümler sunar. Bu desenlerden biri de davranışsal desenler kategorisinde yer alan Strategy Tasarım Deseni‘dir. Strategy deseni, bir dizi algoritmayı tanımlamanın, her birini kapsüllemenin ve birbirleriyle değiştirilebilir hale getirmenin bir yolunu sunar. Bu sayede, istemci kodu algoritmanın kendisinden bağımsız olarak değişebilir.

Strategy Tasarım Deseni Nedir?

Strategy deseni, bir algoritma ailesini tanımlar, her algoritmayı ayrı bir sınıf içinde kapsüller ve bu algoritmaların birbirlerinin yerine kullanılabilmesini sağlar. Bu desen, algoritmanın kendisinin, onu kullanan istemciden bağımsız olarak değişmesine olanak tanır. Başka bir deyişle, bir nesnenin farklı davranışlara sahip olması gerektiğinde ve bu davranışların çalışma zamanında değiştirilebilmesi istendiğinde Strategy deseni devreye girer.

Bu desenin temel amacı, bir sınıfın farklı işlevsellikleri (algoritmaları) doğrudan kendi içinde uygulamak yerine, bu işlevsellikleri ayrı nesnelere devretmesini sağlamaktır. Bu, özellikle bir sınıfın davranışının birçok farklı varyantı olduğunda ve bu varyantların sık sık değişebileceği durumlarda çok faydalıdır. Geleneksel olarak, bu tür durumlar if-else veya switch-case yapılarıyla ele alınmaya çalışılır, ancak bu yaklaşımlar kodun karmaşıklığını artırır, bakımı zorlaştırır ve Açık/Kapalı Prensibi’ni (Open/Closed Principle) ihlal eder. Strategy deseni, bu sorunlara zarif ve esnek bir çözüm sunar.

Yaygın Bir Problem: Koşullu Mantık Karmaşası

Strategy deseninin neden bu kadar önemli olduğunu anlamak için, sıkça karşılaşılan bir senaryoyu ele alalım. Bir e-ticaret uygulamasında, müşterilerin farklı ödeme yöntemlerini (kredi kartı, PayPal, banka havalesi vb.) kullanarak ödeme yapabilmesi gerekmektedir. Geliştiriciler genellikle bu durumu aşağıdaki gibi bir yaklaşımla ele alabilirler:

public class PaymentProcessor {
    public void processPayment(String paymentType, double amount) {
        if (paymentType.equals("CreditCard")) {
            // Kredi kartı ödeme mantığı
            System.out.println(amount + " TL kredi kartı ile ödendi.");
        } else if (paymentType.equals("PayPal")) {
            // PayPal ödeme mantığı
            System.out.println(amount + " TL PayPal ile ödendi.");
        } else if (paymentType.equals("BankTransfer")) {
            // Banka havalesi ödeme mantığı
            System.out.println(amount + " TL banka havalesi ile ödendi.");
        } else {
            System.out.println("Geçersiz ödeme türü.");
        }
    }
}

// Kullanım örneği
public class Main {
    public static void main(String[] args) {
        PaymentProcessor processor = new PaymentProcessor();
        processor.processPayment("CreditCard", 100.0);
        processor.processPayment("PayPal", 50.0);
    }
}

Bu yaklaşımın ciddi dezavantajları vardır:

* Genişletilebilirlik Sorunu: Yeni bir ödeme yöntemi eklendiğinde (örneğin, Bitcoin), PaymentProcessor sınıfının processPayment metodu değiştirilmek zorunda kalır. Bu, mevcut ve çalışan kodu değiştirmek anlamına gelir ki bu, Açık/Kapalı Prensibi’ni ihlal eder.
* Bakım Zorluğu: if-else veya switch-case blokları büyüdükçe, kodun okunabilirliği ve bakımı zorlaşır. Her bir ödeme yönteminin mantığı, tek bir metot içinde iç içe geçmiş durumdadır.
* Test Edilebilirlik: Her bir ödeme yöntemini ayrı ayrı test etmek zordur, çünkü hepsi aynı metot içinde birleşmiştir.
* Sıkı Bağlantı (Tight Coupling): PaymentProcessor sınıfı, tüm ödeme yöntemlerinin detaylarını bilir ve onlarla sıkı bir şekilde bağlıdır.

Strategy deseni, bu tür sorunları çözmek için tasarlanmıştır.

Strategy Deseni’nin Temel Kavramları ve Bileşenleri

Strategy deseni, üç ana bileşenden oluşur:

1. Strategy (Arayüz veya Soyut Sınıf)

Bu, tüm somut stratejiler için ortak bir arayüz tanımlar. İstemciler, belirli bir algoritmayı uygulamak için bu arayüzü kullanır. Bu arayüz, genellikle tek bir metot içerir ve bu metot, stratejinin ana davranışını temsil eder. Örneğin, ödeme sistemi senaryosunda, bu arayüz PaymentStrategy olabilir ve pay(int amount) metodunu içerebilir.

2. Concrete Strategy (Somut Stratejiler)

Bunlar, Strategy arayüzünü uygulayan sınıflardır. Her somut strateji, Strategy arayüzünde tanımlanan metodun belirli bir uygulamasını (yani, belirli bir algoritmayı) sağlar. Ödeme sistemi örneğinde, CreditCardPaymentStrategy, PayPalPaymentStrategy ve BankTransferPaymentStrategy gibi sınıflar somut stratejiler olacaktır. Her biri kendi ödeme mantığını uygulayacaktır.

3. Context (Bağlam)

Bağlam, bir Strategy nesnesine referans tutan sınıftır. Bu sınıf, istemcinin etkileşimde bulunduğu ana sınıftır. Bağlam, işi doğrudan yapmak yerine, referansını tuttuğu Strategy nesnesine devrederek görevi yerine getirir. Bağlamın kendisi hangi stratejinin kullanılacağını bilmez, sadece kendisine verilen stratejiyi kullanır. İstemci, çalışma zamanında bağlama hangi somut stratejinin kullanılacağını söyler veya bağlam, kendi iç mantığına göre bir strateji seçebilir. Ödeme sistemi örneğinde, PaymentContext veya Order sınıfı bir bağlam olabilir.

Örnek Senaryo: Ödeme Sistemi Uygulaması

Yukarıda bahsettiğimiz ödeme sistemi sorununu Strategy deseni ile nasıl çözeceğimize bakalım. Amacımız, yeni ödeme yöntemleri eklendiğinde PaymentContext sınıfını değiştirmek zorunda kalmamak ve her ödeme yöntemini kendi içinde izole etmektir.

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

Öncelikle, tüm ödeme stratejilerinin uygulayacağı bir arayüz tanımlarız. Bu arayüz, ödeme işlemini gerçekleştirecek olan pay metodunu içerecektir.

// PaymentStrategy.java
public interface PaymentStrategy {
    void pay(int amount);
}

Bu arayüz, ödeme algoritmasının soyutlamasıdır. Herhangi bir somut ödeme yöntemi, bu arayüzü uygulayarak kendi ödeme mantığını sağlayacaktır. Bu, istemcinin (bağlamın) farklı ödeme yöntemlerini aynı şekilde ele alabilmesini sağlar.

Adım 2: Somut Stratejiler Oluşturma

Şimdi, PaymentStrategy arayüzünü uygulayan somut ödeme stratejilerini oluşturalım. Her biri belirli bir ödeme yönteminin mantığını içerecektir.

// CreditCardPaymentStrategy.java
public class CreditCardPaymentStrategy implements PaymentStrategy {
    private String cardNumber;
    private String cardHolderName;
    private String cvv;
    private String expiryDate;

    public CreditCardPaymentStrategy(String cardNumber, String cardHolderName, String cvv, String expiryDate) {
        this.cardNumber = cardNumber;
        this.cardHolderName = cardHolderName;
        this.cvv = cvv;
        this.expiryDate = expiryDate;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " TL kredi kartı ile ödendi. Kart No: " + cardNumber);
        // Gerçek dünyada burada kredi kartı API'si ile etkileşim kurulur.
    }
}
// PayPalPaymentStrategy.java
public class PayPalPaymentStrategy implements PaymentStrategy {
    private String email;
    private String password;

    public PayPalPaymentStrategy(String email, String password) {
        this.email = email;
        this.password = password;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " TL PayPal ile ödendi. E-posta: " + email);
        // Gerçek dünyada burada PayPal API'si ile etkileşim kurulur.
    }
}
// BitcoinPaymentStrategy.java
public class BitcoinPaymentStrategy implements PaymentStrategy {
    private String bitcoinAddress;

    public BitcoinPaymentStrategy(String bitcoinAddress) {
        this.bitcoinAddress = bitcoinAddress;
    }

    @Override
    public void pay(int amount) {
        System.out.println(amount + " TL Bitcoin ile ödendi. Adres: " + bitcoinAddress);
        // Gerçek dünyada burada Bitcoin ağı ile etkileşim kurulur.
    }
}

Her bir somut strateji sınıfı, kendi ödeme yöntemine özgü verileri (örneğin, kart numarası, e-posta) tutabilir ve kendi pay metodunu uygulayarak belirli ödeme algoritmasını çalıştırır.

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

Şimdi, PaymentStrategy arayüzünü kullanarak ödeme işlemini yönetecek olan bağlam sınıfını (PaymentContext) oluşturalım. Bu sınıf, hangi ödeme stratejisinin kullanılacağını bilmeyecek, sadece kendisine verilen stratejiyi yürütecektir.

// PaymentContext.java
public class PaymentContext {
    private PaymentStrategy paymentStrategy;

    // Stratejiyi constructor aracılığıyla enjekte edebiliriz.
    public PaymentContext(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    // Veya setter metodu aracılığıyla çalışma zamanında değiştirebiliriz.
    public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
        this.paymentStrategy = paymentStrategy;
    }

    public void executePayment(int amount) {
        // İşi doğrudan kendisi yapmak yerine, stratejiye devreder.
        paymentStrategy.pay(amount);
    }
}

PaymentContext sınıfı, bir PaymentStrategy referansı tutar. executePayment metodu çağrıldığında, işi bu referansa devrederek pay metodunu çağırır. Bu sayede, PaymentContext sınıfı, hangi somut ödeme stratejisinin kullanıldığını bilmek zorunda kalmaz. Bu, sıkı bağlantıyı ortadan kaldırır.

Adım 4: Uygulamayı Test Etme (Client Kodu)

Son olarak, Main sınıfımızda bu yapıyı nasıl kullanacağımızı gösterelim. İstemci kodu, hangi stratejinin kullanılacağına karar verir ve bunu bağlama iletir.

// Main.java
public class Main {
    public static void main(String[] args) {
        // Kredi kartı ile ödeme
        PaymentContext context = new PaymentContext(new CreditCardPaymentStrategy("1234-5678-9012-3456", "John Doe", "123", "12/25"));
        context.executePayment(250);

        System.out.println("--------------------");

        // PayPal ile ödeme (stratejiyi çalışma zamanında değiştirme)
        context.setPaymentStrategy(new PayPalPaymentStrategy("john.doe@example.com", "mysecurepassword"));
        context.executePayment(150);

        System.out.println("--------------------");

        // Yeni bir ödeme yöntemi ekleyelim: Bitcoin
        // PaymentContext sınıfını değiştirmeye gerek kalmadan yeni bir strateji ekledik.
        context.setPaymentStrategy(new BitcoinPaymentStrategy("1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2"));
        context.executePayment(500);

        System.out.println("--------------------");

        // Başka bir kredi kartı ile ödeme
        context.setPaymentStrategy(new CreditCardPaymentStrategy("9876-5432-1098-7654", "Jane Smith", "456", "01/24"));
        context.executePayment(75);
    }
}

Bu Main sınıfı, Strategy deseninin gücünü gösterir. İstemci, ödeme bağlamına hangi stratejiyi kullanması gerektiğini dinamik olarak söyleyebilir. Yeni bir ödeme yöntemi (örneğin Bitcoin) eklendiğinde, sadece yeni bir PaymentStrategy uygulayıcı sınıfı oluşturmak yeterli olur; PaymentContext sınıfının kodunu değiştirmeye gerek kalmaz. Bu, sistemin genişletilebilirliğini büyük ölçüde artırır.

Strategy Deseni’nin Avantajları

Strategy deseni, yazılım tasarımında bir dizi önemli avantaj sunar:

* Esneklik ve Genişletilebilirlik: Yeni algoritmalar veya davranışlar eklemek çok kolaydır. Sadece Strategy arayüzünü uygulayan yeni bir sınıf oluşturmak yeterlidir. Mevcut kodda değişiklik yapmaya gerek kalmaz.
* Açık/Kapalı Prensibi (Open/Closed Principle): Bu prensip, bir yazılım varlığının (sınıf, modül, fonksiyon vb.) geliştirmeye açık, ancak değiştirmeye kapalı olması gerektiğini belirtir. Strategy deseni, yeni algoritmalar eklemek için mevcut kodu değiştirmemize gerek kalmadığı için bu prensibi destekler.
* Kod Tekrarını Azaltma: Ortak arayüz sayesinde, farklı algoritmalar benzer bir şekilde ele alınabilir, bu da kod tekrarını azaltır.
* Test Edilebilirlik: Her strateji kendi sınıfında kapsüllendiği için, her bir algoritma bağımsız olarak kolayca test edilebilir. Bu, birim testlerinin yazılmasını ve sürdürülmesini basitleştirir.
* Bakım Kolaylığı: Her algoritma kendi sınıfında izole edildiğinden, bir algoritmadaki değişiklikler diğerlerini etkileme olasılığı düşüktür. Bu, kodun bakımını kolaylaştırır.
* Koşullu İfadelerin Azalması: if-else veya switch-case bloklarının karmaşasını ortadan kaldırır. Bu, kodun daha temiz ve okunabilir olmasını sağlar.
* Çalışma Zamanında Davranış Değişikliği: Bir nesnenin davranışını çalışma zamanında dinamik olarak değiştirmeyi mümkün kılar. Örneğin, kullanıcı bir ödeme yöntemi seçtiğinde, ilgili strateji anında uygulanabilir.

Strategy Deseni’nin Dezavantajları ve Ne Zaman Kullanılmalı/Kullanılmamalı

Her tasarım deseni gibi, Strategy deseninin de bazı dezavantajları ve belirli kullanım senaryoları vardır:

Dezavantajlar:

* Artan Sınıf Sayısı: Çok sayıda stratejiniz varsa, bu durum sistemdeki sınıf sayısını artırabilir. Basit durumlar için bu, gereksiz bir karmaşıklık olarak algılanabilir.
* İstemcinin Stratejileri Bilmesi Gerekliliği: Genellikle istemci, hangi stratejiyi kullanacağına karar vermek ve bu stratejiyi bağlama sağlamak zorundadır. Bu, istemcinin stratejiler hakkında belirli bir bilgiye sahip olmasını gerektirir. Ancak, bu sorumluluk bazen bir Fabrika (Factory) deseni kullanılarak istemciden alınabilir.

Ne Zaman Kullanılmalı:

* Bir nesnenin farklı davranışlara sahip olması gerektiğinde ve bu davranışların çalışma zamanında değiştirilebilmesi istendiğinde.
* Bir sınıfın içinde çok sayıda koşullu ifade (if-else veya switch-case) bulunduğunda ve bu ifadelerin farklı algoritmaları seçmek için kullanıldığı durumlarda.
* Algoritmaların farklı varyantlarını izole etmek ve her birini bağımsız olarak yönetmek istediğinizde.
* Bir algoritmanın uygulamasını, onu kullanan istemciden ayırmak istediğinizde.
* Farklı algoritmaların ortak bir arayüze sahip olması gerektiğinde.

Ne Zaman Kullanılmamalı:

* Sadece bir veya iki algoritma varyantı varsa ve gelecekte yeni varyantlar eklenmesi beklenmiyorsa, basit if-else veya switch-case yapıları yeterli olabilir. Strategy deseni, bu tür basit durumlar için gereksiz bir karmaşıklık getirebilir.
* Ekstra sınıf ve arayüz oluşturmanın getirdiği maliyetin, sağladığı faydalardan daha ağır bastığı durumlarda.

Diğer Tasarım Desenleri ile İlişkisi

Strategy deseni, diğer bazı tasarım desenleriyle birlikte kullanılarak daha güçlü ve esnek çözümler oluşturulabilir:

* Factory Method / Abstract Factory: Strateji nesnelerinin oluşturulması, genellikle bir Fabrika (Factory) deseni kullanılarak soyutlanabilir. Bu, istemcinin somut strateji sınıflarının isimlerini bilme zorunluluğunu ortadan kaldırır ve strateji seçimini daha esnek hale getirir. Örneğin, PaymentStrategyFactory adında bir sınıf, paymentType parametresine göre uygun PaymentStrategy nesnesini döndürebilir.
* Template Method: Template Method deseni, bir algoritmanın iskeletini bir metotta tanımlar ve bazı adımları alt sınıflara bırakır. Strategy deseni ise tüm algoritmayı değiştirirken, Template Method deseni algoritmanın belirli adımlarını değiştirir. İki desen bazen birlikte kullanılabilir: bir Template Method, genel bir algoritma yapısını tanımlarken, bu algoritmanın bazı adımları Strategy deseni tarafından sağlanan farklı algoritmalar olabilir.
State Pattern: State deseni, yapısal olarak Strategy desenine çok benzer. Her ikisi de bir nesnenin davranışını çalışma zamanında değiştirmek için arayüzleri ve somut sınıfları kullanır. Temel fark, State deseninin bir nesnenin durumuna göre davranışını değiştirmesidir. Yani, bir nesnenin iç durumu değiştiğinde, davranışı da değişir. Strategy deseni ise, bir nesnenin nasıl* davrandığını değiştirmek için kullanılır, genellikle istemci tarafından açıkça seçilen bir algoritmayı uygular. State nesneleri, genellikle birbirleri arasındaki geçişleri tanımlar, oysa Strategy nesneleri bunu yapmaz.

Sonuç

Strategy tasarım deseni, Java’da ve diğer nesne yönelimli dillerde esnek, genişletilebilir ve bakımı kolay yazılım sistemleri oluşturmak için güçlü bir araçtır. Algoritma ailesini tanımlama, her birini kapsülleme ve çalışma zamanında değiştirilebilir hale getirme yeteneği sayesinde, kod tekrarını azaltır, koşullu ifadelerin karmaşasını giderir ve Açık/Kapalı Prensibi’ni destekler.

Ödeme sistemi gibi senaryolarda, farklı algoritmaların (ödeme yöntemlerinin) olduğu ve bunların gelecekte değişebileceği veya yenilerinin eklenebileceği durumlarda Strategy deseni vazgeçilmez bir çözüm sunar. Her ne kadar ekstra sınıf sayısıyla sonuçlansa da, sağladığı esneklik, modülerlik ve bakım kolaylığı genellikle bu küçük maliyeti fazlasıyla karşılar. Yazılım geliştiricilerin araç kutusunda mutlaka bulunması gereken temel davranışsal desenlerden biridir. Bu deseni doğru bir şekilde anlamak ve uygulamak, daha sağlam ve sürdürülebilir yazılım mimarileri tasarlamanıza yardımcı olacaktı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.