Yazılım geliştirme sürecinde, bir nesnenin iç durumuna (state) göre farklı davranışlar sergilemesi gereken yapılarla sıkça karşılaşılır. Örneğin bir sipariş nesnesi; beklemede, işlemde, kargoda veya teslim edilmiş olabilir ve bu durumların her birinde "iptal et", "onay ver" gibi işlemler farklı sonuçlar doğurur ya da hiç izin vermez. Tüm bu mantığı tek bir sınıf içinde if-else veya switch bloklarıyla yönetmek, yeni bir durum eklendiğinde ilgili sınıfı baştan sona taramayı zorunlu kılar ve kodun okunabilirliğini ciddi ölçüde düşürür.
Bu durumda State Pattern, duruma bağlı davranışları ayrı nesnelere taşıyarak bir Context nesnesinin içinde bulunduğu durumu temsil eden nesneye iş eder. Durum geçişleri (state transition) ya Context tarafından ya da mevcut durum nesnesinin kendisi tarafından tetiklenebilir. Böylece her durum kendi sınıfında kapsüllenir; yeni bir durum eklemek mevcut sınıflara dokunmayı gerektirmez.
State Pattern implementasyonu Strategy Pattern'e oldukça benzer görünür; her ikisi de bir Context ve birbirinin yerine geçebilen nesnelerden oluşur. Ancak amaçları farklıdır. Strategy "algoritma ailesini" kapsüller ve genellikle dışarıdan (client) belirlenir; stratejiler birbirinden habersizdir. State ise nesnenin mevcut durumuna bağlı olarak davranışı değiştirir ve durumlar arası geçişler çoğu zaman durum nesnelerinin kendi içinde yönetilir; durumlar birbirinden haberdar olabilir.
GoF, durum geçişlerinin nerede yönetileceği konusunda iki yaklaşımdan söz eder:
- İçeriden geçiş: Durum nesneleri
_context.TransitionTo(...)çağrısıyla bir sonraki durumu kendileri belirler. Bu yaklaşımda geçiş mantığı ilgili durumun içinde kapsüllenir; ancakConcreteStateA'nınConcreteStateB'yi doğrudan örneklemesi (ya da en azından adını bilmesi) gerekir. Durumlar birbirine bağımlı hale gelir. - Dışarıdan geçiş: Geçiş kararını
Contextveya client verir; durum nesneleri yalnızca kendi işlerini yapar, birbirinden tamamen habersizdir. Strategy Pattern'deki algoritma değiştirmeye yapısal olarak çok benzer; fark amacında veContext'in delegate etme biçimindedir.
Aşağıdaki iskelet dışarıdan geçiş yaklaşımını göstermektedir. State nesneleri Context'e geri referans taşımaz; ConcreteStateA ve ConcreteStateB birbirini tanımaz.
public class Context
{
private State _state;
public Context(State initialState)
{
_state = initialState;
}
public void SetState(State state)
{
Console.WriteLine($"Context: {_state.GetType().Name} → {state.GetType().Name}");
_state = state;
}
public void Request()
{
_state.Handle();
}
}
public abstract class State
{
public abstract void Handle();
}
public class ConcreteStateA : State
{
public override void Handle()
{
Console.WriteLine("ConcreteStateA: isteği işledi.");
}
}
public class ConcreteStateB : State
{
public override void Handle()
{
Console.WriteLine("ConcreteStateB: isteği işledi.");
}
}Context yalnızca aktif durumu saklar ve Request() çağrısını ona delege eder. Hangi durumun ne zaman devreye gireceğine dair hiçbir bilgi Context içinde yoktur; bu karar tamamen dışarıya bırakılmıştır. SetState çağrısı olmadan Context durumunu değiştiremez.
program.cs
internal class Program
{
static void Main(string[] args)
{
var context = new Context(new ConcreteStateA());
context.Request(); // A işler
context.SetState(new ConcreteStateB()); // dışarıdan B'ye geç
context.Request(); // B işler
context.SetState(new ConcreteStateA()); // dışarıdan A'ya geri dön
context.Request(); // A işler
}
}çıktı
ConcreteStateA: isteği işledi.
Context: ConcreteStateA → ConcreteStateB
ConcreteStateB: isteği işledi.
Context: ConcreteStateB → ConcreteStateA
ConcreteStateA: isteği işledi.
Her Request() çağrısında yalnızca aktif durumun Handle() metodu çalışır. Geçiş kararı tamamen dışarıda olduğundan ConcreteStateA ve ConcreteStateB birbirini bilmek zorunda kalmaz; her biri bağımsız olarak test edilip değiştirilebilir.
Aşağıdaki örnekte bir sipariş (order) yönetim sistemi modellenmiştir. Sipariş dört durumdan geçer: Pending (beklemede) → Processing (işlemde) → Shipped (kargoda) → Delivered (teslim edildi). Her durum, Confirm ve Cancel işlemlerinin ne anlama geldiğini kendi içinde tanımlar; geçersiz bir işlem denendiğinde uyarı mesajı üretir.
public class OrderContext
{
private OrderState _state;
public string OrderId { get; }
public OrderContext(string orderId)
{
OrderId = orderId;
_state = new PendingState();
_state.SetContext(this);
Console.WriteLine($"[{OrderId}] Sipariş oluşturuldu → {_state.GetType().Name}");
}
public void TransitionTo(OrderState state)
{
Console.WriteLine($"[{OrderId}] {_state.GetType().Name} → {state.GetType().Name}");
_state = state;
_state.SetContext(this);
}
public void Confirm() => _state.Confirm();
public void Cancel() => _state.Cancel();
}
public abstract class OrderState
{
protected OrderContext _context = null!;
public void SetContext(OrderContext context) => _context = context;
public abstract void Confirm();
public abstract void Cancel();
}Her durum sınıfı yalnızca kendi sorumluluğundaki geçişleri yönetir.
public class PendingState : OrderState
{
public override void Confirm()
{
Console.WriteLine(" ✓ Sipariş onaylandı, işleme alınıyor.");
_context.TransitionTo(new ProcessingState());
}
public override void Cancel()
{
Console.WriteLine(" ✓ Beklemedeki sipariş iptal edildi.");
}
}
public class ProcessingState : OrderState
{
public override void Confirm()
{
Console.WriteLine(" ✓ Sipariş hazırlandı, kargoya verildi.");
_context.TransitionTo(new ShippedState());
}
public override void Cancel()
{
Console.WriteLine(" ✓ İşlemdeki sipariş iptal edildi, iade süreci başlatıldı.");
}
}
public class ShippedState : OrderState
{
public override void Confirm()
{
Console.WriteLine(" ✓ Teslimat onaylandı.");
_context.TransitionTo(new DeliveredState());
}
public override void Cancel()
{
Console.WriteLine(" ✗ Kargodaki sipariş iptal edilemez, kargo firması ile iletişime geçin.");
}
}
public class DeliveredState : OrderState
{
public override void Confirm()
{
Console.WriteLine(" ✗ Sipariş zaten teslim edildi, tekrar onaylanamaz.");
}
public override void Cancel()
{
Console.WriteLine(" ✗ Teslim edilen sipariş iptal edilemez, iade talebi açın.");
}
}program.cs
internal class Program
{
static void Main(string[] args)
{
var order = new OrderContext("ORD-001");
Console.WriteLine("\n-- Normal akış --");
order.Confirm(); // Pending → Processing
order.Confirm(); // Processing → Shipped
order.Confirm(); // Shipped → Delivered
order.Confirm(); // Delivered → geçersiz
Console.WriteLine("\n-- İptal senaryoları --");
var order2 = new OrderContext("ORD-002");
order2.Confirm(); // Pending → Processing
order2.Cancel(); // Processing → iptal
var order3 = new OrderContext("ORD-003");
order3.Confirm(); // Pending → Processing
order3.Confirm(); // Processing → Shipped
order3.Cancel(); // Shipped → iptal edilemez
}
}çıktı
[ORD-001] Sipariş oluşturuldu → PendingState
-- Normal akış --
✓ Sipariş onaylandı, işleme alınıyor.
[ORD-001] PendingState → ProcessingState
✓ Sipariş hazırlandı, kargoya verildi.
[ORD-001] ProcessingState → ShippedState
✓ Teslimat onaylandı.
[ORD-001] ShippedState → DeliveredState
✗ Sipariş zaten teslim edildi, tekrar onaylanamaz.
-- İptal senaryoları --
[ORD-002] Sipariş oluşturuldu → PendingState
✓ Sipariş onaylandı, işleme alınıyor.
[ORD-002] PendingState → ProcessingState
✓ İşlemdeki sipariş iptal edildi, iade süreci başlatıldı.
[ORD-003] Sipariş oluşturuldu → PendingState
✓ Sipariş onaylandı, işleme alınıyor.
[ORD-003] PendingState → ProcessingState
✓ Sipariş hazırlandı, kargoya verildi.
[ORD-003] ProcessingState → ShippedState
✗ Kargodaki sipariş iptal edilemez, kargo firması ile iletişime geçin.
State tasarım deseni, bir nesnenin duruma bağlı davranışlarını 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 durum yönetmek yerine her durumu ayrı bir sınıfa taşımak —ve geçiş mantığını bu sınıflara devretmek— hem Open-Closed Principle'a uyum sağlar hem de her durum sınıfının yalnızca kendi sorumluluğunu üstlenmesini garanti eder.
Pratikte State Pattern, sipariş yönetimi, belge onay akışları, oyun karakteri davranışları ve protokol implementasyonları gibi net ve belirli durum geçişlerinin söz konusu olduğu yerlerde oldukça işe yarar. Uygularken durum geçişlerinin Context'te mi yoksa durum nesnelerinde mi yönetileceğinin baştan netleştirilmesi, ilerleyen süreçte geçiş mantığının dağılmasının önüne geçer.