Strategy Tasarım Deseni Nedir?

Sayfayı kopyala
Bu yazı Design Patterns/Tasarım Desenleri Nedir? başlıklı yazı dizisinin bir parçasıdır.
Bu içerik ağırlıklı olarak refactoring.guru sitesindeki içeriğin tercümesi ve derlenmesinden oluşturulmuştur.
Tüm tasarım desenleri ya da diğer adıyla tasarım kalıplarına yönelik ayrıntılı içeriklere yazının sonundaki bağlantılardan ulaşabilirsiniz.
💡 Özet (TL;DR):
- Amaç: Bir algoritma ailesi tanımlayıp her birini ayrı sınıfa koyarak çalışma zamanında birbirlerinin yerine kullanılabilmelerini sağlar.
- Tasarım İlkesi: Composition over Inheritance (Kalıtım yerine Kompozisyon) prensibine dayanır. Davranışlar alt sınıflarla değil, nesne kompozisyonu ile dinamik olarak değiştirilir.
- Kilit Yapılar: Bağlam (
Context), Strateji Arayüzü (Strategy) ve Somut Stratejiler (ConcreteStrategy).
Strategy Tasarım Deseninin Amacı
Strategy (Strateji), bir algoritma ailesi oluşturup her birini farklı bir sınıfa yerleştirerek nesnelerini birbiri arasında değişebilir (interchangeable) hale getirmeyi sağlayan davranışsal (behavioral) bir tasarım desenidir.
Sorun
Gezginler için bir navigasyon uygulaması oluşturmaya karar verdiniz. Uygulamayı herhangi bir şehirde, güzel bir harita üzerinde, kullanıcıların yönlerini hızlıca belirlemelerini sağlayacak şekilde inşa ettiniz.
Uygulama için en çok istenen özelliklerden biri de otomatik rota oluşturabilmesi oldu. Bunun için kullanıcı varmak istediği adresi girmeli ve o adrese gitmek için en hızlı rotayı görebilmeli.
Uygulamanın ilk sürümü, yalnızca yollar üzerinde rotalar oluşturabiliyordu. Arabayla seyahat edenler sevinçten havalara uçtu. Ama görünüşe göre, herkes tatilde araba kullanmayı tercih etmiyor. Bir sonraki güncelleme ile yürüyüş rotaları oluşturmak için bir seçenek eklediniz. Hemen ardından insanların güzergahlarında toplu taşımayı kullanmalarına izin veren bir seçenek daha eklediniz.
Ancak, bu sadece başlangıçtı. Daha sonra bisikletçiler için rota oluşturmayı eklemeyi planladınız. Hatta daha sonra, şehrin tüm turistik mekanları arasında rota oluşturmak için başka bir seçenek eklemeyi düşündünüz.

Uygulama ticari olarak başarılı olsa da teknik kısım birçok baş ağrısına neden oldu. Eklenen her yeni rota algoritması ile ana navigasyon kodunun büyüklüğü iki katına çıktı. Bir yerden sonra bu canavarla baş etmek zor olmaya başladı.
Algoritmalardan birinde yapılacak en küçük bir değişiklik (basit bir bug düzeltmesi gibi), tüm sınıfı etkileyerek halihazırda çalışan diğer kodlarda bir hata oluşturma riskini artırdı.
Ayrıca, ekip çalışması verimsiz hale geldi. Yeni bir özelliğin eklenmesi, diğer geliştiriciler tarafından üretilen kodlarla çelişecek şekilde aynı büyük sınıf üzerinde çalışılmasını gerektirdiğinden sürekli birleştirme çatışmaları (merge conflict) yaşanmaya başladı.
Çözüm
Strategy deseni, spesifik bir işi birçok farklı yolla yapan bir sınıfı alıp, bütün bu alternatif algoritmaları "Strateji" (Strategy) adı verilen ayrı sınıflara taşımayı önerir.
Bu modelde, "Bağlam" (Context) adını vereceğimiz orijinal sınıf, doğrudan bu stratejilerden birini kendi içinde çalıştırmak yerine seçilen stratejinin referansını bir değişken olarak saklar. Bağlam, işi doğrudan kendisi yapmak yerine seçilen strateji nesnesine delege eder.
Yapılacak iş için uygun algoritmayı seçmek bağlamın sorumluluğu değildir. İstemci kod bağlama tercih ettiği stratejiyi gönderir. Aslına bakarsanız bağlam nesnesinin stratejilerin ne yaptığı ile ilgili bir fikri yoktur. Tüm stratejilerle ortak bir arayüz üzerinden tek bir metot ile etkileşime girer ve algoritmanın içeriğini sadece seçilen strateji bilir.

Navigasyon uygulamamızda, her rota algoritması sadece buildRoute(起点, 终点) metodunu içeren bağımsız bir sınıfa dönüştürülebilir.
Aynı parametreler ile çağrılsalar bile her rota sınıfı başka bir rota döndürebilir. Ana navigasyon uygulaması hangi algoritmanın seçildiği ile ilgilenmez, onun tek görevi döndürülen rotayı harita üzerinde çizmektir. Bağlam sınıfı, aktif rota stratejisini değiştirmek için bir setter metoduna (setStrategy) sahiptir, böylece çalışma zamanında kullanıcının seçimine göre rota oluşturma davranışı dinamik olarak değiştirilebilir.
Gerçek Hayat Senaryosu: Ödeme Sistemi Entegrasyonu
E-ticaret sistemlerinde en yaygın kullanılan Strategy kalıbı Ödeme Yöntemleri entegrasyonudur:
// 1. Strateji Arayüzü (Strategy Interface)
interface PaymentStrategy {
public function pay(int $amount): void;
}
// 2. Somut Stratejiler (Concrete Strategies)
class CreditCardPayment implements PaymentStrategy {
private string $cardNumber;
public function __construct(string $cardNumber) {
$this->cardNumber = $cardNumber;
}
public function pay(int $amount): void {
echo "Kredi kartı ($this->cardNumber) ile $amount TL ödeme yapıldı.\n";
}
}
class PayPalPayment implements PaymentStrategy {
private string $email;
public function __construct(string $email) {
$this->email = $email;
}
public function pay(int $amount): void {
echo "PayPal ($this->email) ile $amount TL ödeme yapıldı.\n";
}
}
// 3. Bağlam Sınıfı (Context)
class ShoppingCart {
private int $totalAmount = 0;
private PaymentStrategy $paymentStrategy;
public function setPaymentStrategy(PaymentStrategy $strategy): void {
$this->paymentStrategy = $strategy;
}
public function addAmount(int $amount): void {
$this->totalAmount += $amount;
}
public function checkout(): void {
// Ödeme işlemini seçili stratejiye delege et
$this->paymentStrategy->pay($this->totalAmount);
}
}
Strategy vs State
| Kriter | Strategy | State |
|---|---|---|
| Bağımsızlık | Stratejiler birbirinden tamamen bağımsızdır ve birbirlerinden haberdar değildir. | Durumlar (States) genellikle birbirine bağımlıdır ve durum geçişlerini kendileri tetikleyebilir. |
| Dışarıdan Müdahale | Uygun strateji genellikle istemci tarafından seçilir ve Context'e dışarıdan enjekte edilir. | Durum geçişleri Context içindeki olaylara göre otomatik olarak gerçekleşebilir. |
| Amaç | Aynı işi yapmanın farklı yollarını (algoritmaları) sunmak. | Nesnenin iç durumuna göre davranışını tamamen değiştirmek. |
Uygulanabilirlik
- Çalışma Zamanında Algoritma Değiştirme: Bir nesne içinde bir algoritmanın farklı türevlerini kullanmak ve çalışma esnasında dinamik olarak birinden diğerine geçebilmek istediğinizde kullanın.
- Benzer Sınıfların Sadeleştirilmesi: Yalnızca bazı davranışları yürütme biçimleri bakımından farklılık gösteren çok sayıda benzer sınıfınız olduğunda, bu davranışları ayrı sınıflara çıkarıp kod tekrarını azaltmak için kullanın.
- İş Mantığından Algoritmayı Soyutlama: Bir sınıfın iş mantığı için kritik olmayan detayları ve bağımlılıkları ana sınıftan yalıtmak istediğinizde kullanın.
- Büyük Koşullu İfadelerin Temizlenmesi: Sınıfınız aynı algoritmanın farklı varyasyonları arasında geçiş yapan devasa
if-elseveyaswitchbloklarına sahip olduğunda kullanın.
Diğer Tasarım Desenleri ile İlişkisi
- Bridge, State, Strategy ve Adapter desenleri benzer bir kompozisyon yapısına sahiptir; hepsi işi başka nesnelere delege eder. Ancak her biri farklı bir yazılımsal probleme odaklanır.
- Decorator nesnenin dış görünüşünü/kabuğunu değiştirirken, Strategy nesnenin özünü ve çalışma biçimini değiştirir.
- Template Method kalıtım (inheritance) kullanarak algoritmanın parçalarını alt sınıflarda değiştirir (statiktir). Strategy ise kompozisyon kullanarak tüm davranışı çalışma zamanında değiştirmeye izin verir (dinamiktir).
Strategy Deseni Kod Örnekleri
Örnek PHP Kodu
<?php
namespace RefactoringGuru\Strategy\Conceptual;
class Context
{
private Strategy $strategy;
public function __construct(Strategy $strategy)
{
$this->strategy = $strategy;
}
public function setStrategy(Strategy $strategy)
{
$this->strategy = $strategy;
}
public function doSomeBusinessLogic(): void
{
echo "Context: Verileri strateji kullanarak sıralıyorum (nasıl yapıldığını bilmiyorum):\n";
$result = $this->strategy->doAlgorithm(["a", "b", "c", "d", "e"]);
echo implode(",", $result) . "\n";
}
}
interface Strategy
{
public function doAlgorithm(array $data): array;
}
class ConcreteStrategyA implements Strategy
{
public function doAlgorithm(array $data): array
{
sort($data);
return $data;
}
}
class ConcreteStrategyB implements Strategy
{
public function doAlgorithm(array $data): array
{
rsort($data);
return $data;
}
}
$context = new Context(new ConcreteStrategyA());
echo "Client: Normal sıralama stratejisi seçildi.\n";
$context->doSomeBusinessLogic();
echo "\n";
echo "Client: Ters sıralama stratejisi seçildi.\n";
$context->setStrategy(new ConcreteStrategyB());
$context->doSomeBusinessLogic();
Örnek Python Kodu
from __future__ import annotations
from abc import ABC, abstractmethod
from typing import List
class Context():
def __init__(self, strategy: Strategy) -> None:
self._strategy = strategy
@property
def strategy(self) -> Strategy:
return self._strategy
@strategy.setter
def strategy(self, strategy: Strategy) -> None:
self._strategy = strategy
def do_some_business_logic(self) -> None:
print("Context: Verileri strateji kullanarak sıralıyorum (nasıl yapıldığını bilmiyorum):")
result = self._strategy.do_algorithm(["a", "b", "c", "d", "e"])
print(",".join(result))
class Strategy(ABC):
@abstractmethod
def do_algorithm(self, data: List):
pass
class ConcreteStrategyA(Strategy):
def do_algorithm(self, data: List) -> List:
return sorted(data)
class ConcreteStrategyB(Strategy):
def do_algorithm(self, data: List) -> List:
return list(reversed(sorted(data)))
if __name__ == "__main__":
context = Context(ConcreteStrategyA())
print("Client: Normal sıralama stratejisi seçildi.")
context.do_some_business_logic()
print()
print("Client: Ters sıralama stratejisi seçildi.")
context.strategy = ConcreteStrategyB()
context.do_some_business_logic()
Sıkça Sorulan Sorular (FAQ)
Neden kalıtım (inheritance) yerine kompozisyon (composition) tercih edilir?
Kalıtım statiktir; sınıf yapısı kod yazılırken belirlenir ve çalışma zamanında değiştirilemez. Ayrıca alt sınıflar üst sınıfın tüm detaylarına bağımlı hale gelir (White-box reuse). Kompozisyon ise dinamiktir. Bağlam nesnesine çalışma zamanında farklı strateji nesneleri enjekte edilebilir ve bu sayede nesnenin davranışı esnek bir şekilde değiştirilir (Black-box reuse).
Strategy deseni ile if-else / switch kontrol blokları nasıl azaltılır?
Strateji deseni kullanılmadığında, farklı durum veya algoritma alternatifleri için kodun içinde if ($type == 'credit_card') gibi koşullu ifadeler yazılır. Strateji deseni ile her koşulun içi ayrı bir sınıf haline getirilir. İstemci, koşula uygun strateji nesnesini bir kez bağlama verir ve bağlam herhangi bir kontrol yapısı işletmeden doğrudan strateji metodunu çağırır.
Modern dillerde arayüz (Interface) tanımlamadan strateji uygulayabilir miyiz?
Evet. PHP 8+ veya Python 3 gibi modern dillerde, her strateji için ayrı bir sınıf oluşturmak yerine closure (fonksiyon) veya callback yapısı kullanabilirsiniz. Bağlam sınıfı bir interface yerine direkt bir callable parametre kabul edebilir. Küçük ve basit algoritmalar için bu yöntem ek dosya/sınıf kalabalığını önler.
Diğer Tasarım Kalıpları/Design Patterns
Oluşumsal Kalıplar (Creational Patterns)
Factory Method, Abstract Factory, Builder, Prototype, Singleton
Yapısal Kalıplar (Structural Patterns)
Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy
Davranışsal Kalıplar (Behavioral Patterns)
Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor
