Wzorzec State pozwala obiektowi zmieniać swoje zachowanie w czasie działania w zależności od jego wewnętrznego stanu. Obiekt wygląda wtedy tak, jakby zmienił swoją klasę. W tym poście zaprezentuję jak go użyć w C# i kiedy jest on pomocny.
Struktura UML wzorca State może wyglądać tak:

- Context Obiekt, którego zachowanie zależy od stanu. Przechowuje aktualny obiekt stanu i deleguje do niego zachowanie.
- State Interfejs bazowy dla wszystkich stanów. Deklaruje metodę, którą stany muszą implementować.
- ConcreteStateA/B Konkretne implementacje stanu. Zawierają logikę zależną od danego stanu oraz mogą zmieniać stan kontekstu.
Dla mnie najważniejsze zastosowania tego wzorca to:
- Kiedy zachowanie obiektu zależy od jego stanu wewnętrznego.
- Gdy wiele metod w klasie zawiera
switch
/if
w zależności od stanu. - Gdy chcesz unikać rozrastających się klas z instrukcjami warunkowymi.
Kontynuując do zaler zaliczyłbym to:
- Eliminuje rozbudowane bloki
if/else
lubswitch
. - Każdy stan jest samodzielną klasą – SRP (Single Responsibility Principle).
- Łatwo dodać nowe stany bez modyfikowania istniejącego kodu (Open/Closed Principle).
- Stan może być dynamicznie zmieniany w czasie działania programu.
Ale moim zdaniem ma też wady (ale tylko jeśli mówimy o prostym i małoskomplikowanym projekcie):
- Może zwiększyć liczbę klas.
- Potrzeba przemyślanej struktury – niepotrzebne zastosowanie może prowadzić do „overengineeringu”.
Oczywiście można nie używać tego wzorca i zastąpić wszystko kombinacjami if
i switch
. Ale w skomplikowanym projekcie po pewnym czasie dojdziemy do jeszce bardziej skompilkowanych warunków 😀 A napiszę więcej – skomplikowanych zagnieżdżonych warunków 🙂 Więc jeśli zależy Ci na tym, żeby Twój projekt był łatwy w utrzymaniu to rozważ zastosowanie tego wzorca.
Przykład
Implementację tego wzorca pokażę na prostym przykładzie stanu zamówienia. Stan zamówienia będzie rezprezentował interfejs IOrderState
public interface IOrderState { void Proceed(Order context); void Cancel(Order context); string Name { get; } }
Teraz czas na context, czyli klasę która będzie „trzymała” stan:
public class Order { private IOrderState _state; public Order() { _state = new CreatedState(); // stan początkowy } public void SetState(IOrderState state) { Console.WriteLine($"State changed: {_state.Name} → {state.Name}"); _state = state; } public void Proceed() => _state.Proceed(this); public void Cancel() => _state.Cancel(this); public string CurrentState => _state.Name; }
No to teraz najważniejsze, czyli konkretne stany:
public class CreatedState : IOrderState { public string Name => "Created"; public void Proceed(Order context) { context.SetState(new PaidState()); } public void Cancel(Order context) { context.SetState(new CancelledState()); } } public class PaidState : IOrderState { public string Name => "Paid"; public void Proceed(Order context) { context.SetState(new ShippedState()); } public void Cancel(Order context) { Console.WriteLine("Can't cancel. Already paid."); } } public class ShippedState : IOrderState { public string Name => "Shipped"; public void Proceed(Order context) { Console.WriteLine("Order is already shipped."); } public void Cancel(Order context) { Console.WriteLine("Can't cancel. Already shipped."); } } public class CancelledState : IOrderState { public string Name => "Cancelled"; public void Proceed(Order context) { Console.WriteLine("Can't proceed. Order is cancelled."); } public void Cancel(Order context) { Console.WriteLine("Already cancelled."); } }
No i tyle 🙂 A teraz krótki przykład działania:
public static class Program { public static void Main() { var order = new Order(); Console.WriteLine($"Current state: {order.CurrentState}"); order.Proceed(); // Created → Paid Console.WriteLine($"Current state: {order.CurrentState}"); order.Proceed(); // Paid → Shipped Console.WriteLine($"Current state: {order.CurrentState}"); order.Cancel(); // Too late to cancel } }
Podsumowanie
Jak widać mamy trochę więcej kodu/klas ale to jest prosty przykład. Pomyśl ile miałbyś if
ów bez tych klas a teraz pomyśl o tym samym gdybyś miał 20 stanów. Moim zdaniem kod z if
ami byłby szybko zrozumiały tylko dla autora a dla kogoś kto pierwszy raz widziałby ten kod będzie on nie za bardzo zrozumiały 🙂