Yazılım geliştirme sürecinde, bir işlemin birden fazla yöntemle (algoritmayla) gerçekleştirilmesi gereken durumlarla sıkça karşılaşılmaktadır. Örneğin bir verinin sıralanmasında, verinin boyutuna veya türüne göre farklı sıralama algoritmaları kullanılması gerekebilir. Tüm bu mantığı tek bir sınıfın içine if-else veya switch bloklarıyla gömmek, kodun okunabilirliğini azaltır ve yönetilmesini zorlaştırır.
Bu durumda Strategy Pattern, bir dizi davranışı (algoritmayı) ayrı nesnelere dönüştüren ve bunları orijinal bir bağlam (context) nesnesi içinde birbirinin yerine kullanılabilir (interchangeable) hale getiren davranışsal bir tasarım desenidir. Bununla birlikte Open-Closed prensibine uyumluluğu ile yeni stratejiler eklenmesi durumunda yazılmış herhangi bir kodu değiştirmeden implementasyon yapılabilmektedir.
Strategy Pattern implementasyonu farklı patternlere benzediği (State) gözlemlenmektedir ancak diğer yazılarda da bahsedildiği üzere, önemli olan implementasyon değil amaçtır. Strategy “algoritma ailesini” kapsüller; State ise nesnenin durumuna bağlı olarak davranışı değiştirir ve genelde durum geçişleri içerir.
Aşağıdaki görselden görüldüğü üzere bir Context class'ı ve bunun içerisinde Strategy base class'ından türetilmiş ConcreteStrategyler bulunmaktadır. Context içinde hangi stratejini kullanılacağının kararı verilip ilgili stratejinin execute edilmesine olanak sağlar. Strategybase class'ı ise altından üretilecek ConcreteStrategy ailesinin ana hatlarını taşır.
public class Context
{
private Strategy _strategy;
public void SetStrategy(Strategy strategy)
{
_strategy = strategy;
}
public void Execute()
{
_strategy.AlgorithmInterface();
}
public void Execute(string input)
{
_strategy.AlgorithmInterface(input);
}
}
public abstract class Strategy
{
public abstract void AlgorithmInterface();
public virtual void AlgorithmInterface(string algorithm)
{
Console.WriteLine("Strategy.AlgorithmInterface(string algorithm)");
}
}
public class ConcreteStrategyA : Strategy
{
public override void AlgorithmInterface()
{
Console.WriteLine("ConcreteStrategyA.AlgorithmInterface()");
}
public override void AlgorithmInterface(string input) {
Console.WriteLine($"ConcreteStrategyA.AlgorithmInterface(string {input})");
base.AlgorithmInterface(input);
}
}
public class ConcreteStrategyB : Strategy
{
public override void AlgorithmInterface()
{
Console.WriteLine("ConcreteStrategyB.AlgorithmInterface()");
}
public override void AlgorithmInterface(string input)
{
Console.WriteLine($"ConcreteStrategyB.AlgorithmInterface(string {input})");
base.AlgorithmInterface(input);
}
}program.cs
internal class Program
{
static void Main(string[] args)
{
Context context = new Context();
Console.WriteLine("Client: Strategy is A");
context.SetStrategy(new ConcreteStrategyA());
context.Execute();
context.Execute("input for A");
Console.WriteLine("************************");
Console.WriteLine("Client: Strategy is B");
context.SetStrategy(new ConcreteStrategyB());
context.Execute();
context.Execute("input for B");
}
}çıktı
Client: Strategy is A
ConcreteStrategyA.AlgorithmInterface()
ConcreteStrategyA.AlgorithmInterface(string input for A)
Strategy.AlgorithmInterface(string algorithm)
************************
Client: Strategy is B
ConcreteStrategyB.AlgorithmInterface()
ConcreteStrategyB.AlgorithmInterface(string input for B)
Strategy.AlgorithmInterface(string algorithm)
Strategy tasarım deseni, farklı algoritma veya davranışları birbirinden ayrıştırarak kodun okunabilirliğini, test edilebilirliğini ve genişletilebilirliğini artırır. Tek bir sınıf içinde if-else ya da switch ile çok sayıda algoritma yönetmek yerine, her bir algoritmayı ayrı bir strateji nesnesine dönüştürmek —ve bu nesneleri bağlama (context) nesnesi içinde değiştirilebilir hâle getirmek— hem Open–Closed Principle'a (yeni stratejiler eklerken önceki koda dokunmama) uyum sağlar hem de sorumlulukları netleştirir.
Pratikte Strategy Pattern, sıralama, doğrulama, ödeme işleme, loglama gibi alternatif uygulanışların gerektiği yerlerde çok işe yarar. Uygularken stratejileri mümkün olduğunca basit ve stateless tutmak, bağımlılıkları constructor ile vermek, ve Context tarafında null/exception kontrolleri yapmak iyi uygulamalardır. Bu sayede uygulama esnek, bakımı kolay ve birim testleri (unit tests) yazılması basit bir mimari kazanır.
