Behavioral — Davranışsal Desenler
Strategy
Strateji Deseni
Amaç: Bir işi yapmanın farklı yollarını (algoritma ailesini) ayrı sınıflara taşıyıp çalışma zamanında birbirinin yerine kullanılabilir hale getirmek.
Basit Anlatım
Teknik terim yok — günlük hayattan bir benzetme
Evden havalimanına gitmeniz gerekiyor. Birkaç seçeneğiniz var: taksi (hızlı ama pahalı), otobüs (ucuz ama yavaş), bisiklet (bedava ama yorucu). Hedef aynı: havalimanına ulaşmak. Değişen tek şey nasıl gittiğiniz.
Seçiminizi o anki koşullara göre yaparsınız: acele varsa taksi, bütçe kısıtlıysa otobüs, hava güzelse bisiklet. Ve bu seçim size kalmıştır; yol size "sadece taksiyle gelebilirsin" demez.
Navigasyon uygulaması
Haritada rota oluştururken "araba", "yürüyüş" ve "toplu taşıma" seçenekleri vardır. Başlangıç ve bitiş noktası aynıdır, ama her seçenek rotayı farklı bir yöntemle hesaplar.
Teknik Anlamı
Bir yazılımcının bakış açısıyla
Strategy, birbirinin yerine geçebilen algoritmaları ortak bir arayüzün arkasına koyar. Algoritmayı kullanan nesne (Context), somut stratejiyi bilmez; yalnızca arayüzü bilir. Strateji constructor üzerinden ya da çalışma zamanında değiştirilerek verilir.
Katılımcılar
- Strategy: Tüm algoritmaların ortak arayüzü (
IShippingStrategy). - ConcreteStrategy: Algoritmanın farklı uygulamaları.
- Context: Stratejiyi kullanan sınıf (
ShoppingCart).
Problem: koşul cehennemi
if (type == "standart") {...} else if (type == "hızlı") {...} else if .... Her yeni seçenek bu bloğu büyütür, sınıfı değiştirmeyi gerektirir ve test etmeyi zorlaştırır.
.NET'te Strategy
IComparer<T>(sıralama stratejisi),IEqualityComparer<T>List.Sort(Comparison<T>)veya LINQ'e verilen lambda'lar: C#'ta basit stratejiler için ayrı sınıf yerineFunc<>delegate'i yeterlidir.- Dependency Injection ile arayüze farklı implementasyonların enjekte edilmesi.
Avantajlar
- Algoritmalar çalışma zamanında değiştirilebilir.
- Her algoritma izole şekilde test edilebilir.
- Yeni strateji eklemek context'i değiştirmez (Open/Closed).
- Kalıtım yerine composition kullanılır.
Dezavantajlar
- İstemcinin stratejiler arasındaki farkı bilmesi gerekir.
- Sadece 2 basit seçenek varsa ek sınıflar gereksiz olabilir.
C# ile Uygulama
Adım adım, çalışan kod örneği
Senaryo: Bir alışveriş sepetinde kargo ücreti hesaplama. Standart, hızlı ve "belirli tutarın üstü ücretsiz" seçenekleri var.
Adım 1 Strategy arayüzü ve somut stratejiler
public interface IShippingStrategy
{
string Name { get; }
decimal Calculate(decimal cartTotal, double weightKg);
}
public class StandardShipping : IShippingStrategy
{
public string Name => "Standart (3-5 gün)";
public decimal Calculate(decimal total, double kg) => 30m + (decimal)kg * 5m;
}
public class ExpressShipping : IShippingStrategy
{
public string Name => "Hızlı (ertesi gün)";
public decimal Calculate(decimal total, double kg) => 75m + (decimal)kg * 10m;
}
public class FreeOverLimitShipping : IShippingStrategy
{
private readonly decimal _limit;
private readonly IShippingStrategy _fallback;
public FreeOverLimitShipping(decimal limit, IShippingStrategy fallback)
{ _limit = limit; _fallback = fallback; }
public string Name => $"{_limit} TL üstü ücretsiz";
public decimal Calculate(decimal total, double kg) =>
total >= _limit ? 0 : _fallback.Calculate(total, kg);
}Adım 2 Context: stratejiyi kullanan sepet
Sepet hangi kargo yönteminin seçildiğini bilmez ve umursamaz; sadece Calculate çağırır.
public class ShoppingCart
{
private readonly List<(string Name, decimal Price, double Kg)> _items = new();
private IShippingStrategy _shipping = new StandardShipping();
public void Add(string name, decimal price, double kg) => _items.Add((name, price, kg));
public void SetShipping(IShippingStrategy strategy) => _shipping = strategy;
public void Checkout()
{
decimal total = _items.Sum(i => i.Price);
double weight = _items.Sum(i => i.Kg);
decimal cargo = _shipping.Calculate(total, weight);
Console.WriteLine($" {_shipping.Name,-22} Ürünler: {total,7:0.00} " +
$"Kargo: {cargo,6:0.00} Toplam: {total + cargo,7:0.00} TL");
}
}Adım 3 Kullanım
Tüm parçaları bir araya getiren istemci (client) kodu:
var cart = new ShoppingCart();
cart.Add("Kitap", 180m, 0.5);
cart.Add("Kahve Makinesi", 950m, 3.0);
var options = new IShippingStrategy[]
{
new StandardShipping(),
new ExpressShipping(),
new FreeOverLimitShipping(1000m, new StandardShipping()),
};
Console.WriteLine("Aynı sepet, farklı kargo stratejileri:");
foreach (var option in options)
{
cart.SetShipping(option); // stratejiyi çalışma zamanında değiştir
cart.Checkout();
}
// Basit stratejiler için C#'ta lambda yeterlidir:
var names = new List<string> { "Zeynep", "Ali", "Mehmet" };
names.Sort((x, y) => x.Length.CompareTo(y.Length)); // sıralama stratejisi
Console.WriteLine($"\nUzunluğa göre sıralı: {string.Join(", ", names)}");Konsol çıktısı
Aynı sepet, farklı kargo stratejileri: Standart (3-5 gün) Ürünler: 1130,00 Kargo: 47,50 Toplam: 1177,50 TL Hızlı (ertesi gün) Ürünler: 1130,00 Kargo: 110,00 Toplam: 1240,00 TL 1000 TL üstü ücretsiz Ürünler: 1130,00 Kargo: 0,00 Toplam: 1130,00 TL Uzunluğa göre sıralı: Ali, Zeynep, Mehmet
Kodda dikkat edilmesi gerekenler
ShoppingCartiçinde hiçbirif (kargoTipi == ...)yok.FreeOverLimitShippingbaşka bir stratejiyi sarmalıyor; stratejiler birbirleriyle birleştirilebilir.- Son örnekte
Sortmetoduna bir lambda verdik: bu da bir strateji. Tek metotlu stratejiler için ayrı sınıf yazmak şart değil.