Strateji pattern behavioral(davranışsal) bir pattern olup, bir nesnenin belirli bir işlevselliği farklı şekillerde gerçekleştirmesi gerektiği durumlarda davranışını değiştirerek runtimeda istenen işlevin yerine getirilmesini sağlar. Kısacası belirli bir görevi yerine getirmek için farklı stratejilerin kullanılmasına olanak sağlar diyebiliriz.
Temel fikri ve uygulaması, bir sınıfın davranışını çalışma zamanında değiştirmemizi sağlayacak bir arayüz sağlamaktır. Bu arayüz, sınıfın içindeki bir değişkenin değerine göre, farklı algoritmaların kullanılmasına izin verir ve buda aynı işi yapmak için farklı stratejilerin kullanılmasına olanak tanır aynı zamanda bu stratejiler birbirlerinin yerine kullanılabilir hale gelecektir. Bu sayede esnek, ölçeklenebilir ve bakımı kolay bir yapı oluşturabilmek için altyapı sağlamaktadır, yeni stratejiler kodu değiştirmeye gerek kalmadan eklenebileceğinden ve farklı stratejiler kendilerine özgü işlev/özellikleri içereceğinden SOLID prensiplerinden open/open ve single responsibility prensiplerine doğrudan uygundur diyebiliriz. Ayrıca, farklı algoritmaların kullanılabileceği durumlarda “if-else” veya “switch-case” yapılarının kullanılması yerine, daha iyi bir çözüm sunar. Örnek bir senaryo üzerinden gidecek olursak; bir e-ticaret sistemi geliştirdiğinizi düşünün başlangıçta ödeme yöntemi olarak, banka hesabı ile ödeme ve kredi kartı ile ödeme seçenekleri olsun, ödeme aşamasında seçilen ödeme yöntemine göre farklı davranışlar sergilenmeli ve ödeme işlemi gerçekleşmelidir. bu senaryoda strategy pattern kullanıldığında, ileride yeni entegre edilecek ödeme yöntemleri için (apple pay, paypal vs.) yeni stratejiler oluşturarak kolay şekilde geliştirmelerimize devam edebiliriz. Örnek senaryomuzu koda dökecek olursak;
from abc import ABC, abstractclassmethod
class PaymentStrategy(ABC):
@abstractclassmethod
def pay(self, amount):
pass
class CreditCardPayment(PaymentStrategy):
def __init__(self, card_number, expiry_date, cvv):
self.card_number = card_number
self.expiry_date = expiry_date
self.cvv = cvv
def pay(self, amount):
print(f"Amount {amount} is paid by credit card {self.card_number}")
class BankAccountPayment(PaymentStrategy):
def __init__(self, account_number, sort_code):
self.account_number = account_number
self.sort_code = sort_code
def pay(self, amount):
print(f"Amount {amount} is paid from bank account {self.account_number}")
class PaymentContext:
def __init__(self, payment_strategy):
self._payment_strategy = payment_strategy
@property
def strategy(self):
return self._payment_strategy
@strategy.setter
def strategy(self, payment_strategy):
self._payment_strategy = payment_strategy
def make_payment(self, amount):
self._payment_strategy.pay(amount)
# Create a payment context with credit card payment strategy
payment_context = PaymentContext(CreditCardPayment("1234-5678-9012-3456", "12/25", "123"))
payment_context.make_payment(100)
# Change the payment strategy to bank account payment strategy
payment_context.strategy = BankAccountPayment("1234567890", "11-22-33")
payment_context.make_payment(200)
--------------------------------------------------------------------
Output:
>>> Amount 100 is paid by credit card 1234-5678-9012-3456
>>> Amount 200 is paid from bank account 1234567890
Sonuç kısmına geçmeden önce strategy pattern ile kurduğumuz yapının uml diagramı:
Herhangi bir tasarım desenini uygulamaya karar vermeden, uygulamanın ihtiyaçlarına uygunluğu ve avantajları ile dezavantajları arasında bir denge kurmak önemlidir. Strategy pattern için bir kaç dezavantaj olabilecek durumu şöyle sıralayabiliriz:
- Karmaşıklık: Strategy pattern gereksiz karmaşıklığa neden olabilir. Farklı stratejiler için ayrı sınıflar oluşturmak ve bu sınıfların bir araya getirilmesi, kodun olması gerekenden daha karmaşık hale gelmesine neden olabilir.
- Hız: Strategy pattern ekstra bir seviyede soyutlama katmanı ekleyebilir ve bu da performansı etkileyebilir. ( PaymentContext ödemeyi gerçekleştirebilmek için bir ödeme stratejisine ihtiyaç duymakta, bu nedenle PaymentStrategy sınıfından türeyen CreditCardPayment veya BankAccountPayment nesneleri oluşturulur ve buda performansı negatif yönlü etkiler, çünkü CreditCardpayment veya BankAccountPayment’i direkt kullansaydık tüm bu aradaki işlemlerin yapılmasına gerek kalmayacaktı.)
- Dependency Inversion Principle (DIP, Tersine Dönük Bağımlılık İlkesi): Strategy pattern DIP ilkesine uymamaktadır. (Context içinde somut strateji sınıfları kullanılıyor.)
- Yeniden Kullanılabilirlik: Strateji sınıfları contextin beklediği işleri yerine getirecek şekilde oluşturuldan her durumda yeniden kullanımı mümkün olmayabilir.
Sources:
- https://refactoring.guru/design-patterns/strategy/
- https://www.dofactory.com/net/strategy-design-pattern
Be First to Comment