State (Tillstånd)

Introduktion

State är ett beteendemönster som låter ett objekt ändra sitt beteende när dess interna tillstånd förändras. Det ser ut som om objektet byter klass.


Problem

Du bygger en orderhantering. En order kan vara PENDING, PAID, SHIPPED eller DELIVERED. Beroende på tillstånd är olika operationer tillåtna — du kan inte skicka en obetalad order, du kan inte avbryta en levererad order.

Med if/else och enums växer varje metod till en lång kedja av villkorsblock. Varje nytt tillstånd kräver att du rör vid alla befintliga metoder.

Lösning

State extraherar varje tillstånd till en egen klass. Kontextobjektet delegerar alla tillståndsspecifika operationer till sitt nuvarande tillståndsobjekt. Att byta tillstånd innebär att byta ut objektet.

När ska State användas?

  • När ett objekt beter sig olika beroende på sitt nuvarande tillstånd.
  • När du har stora villkorsblock som beror på objektets tillstånd.
  • När tillståndsövergångarna är komplexa och behöver dokumenteras tydligt.

Struktur

RollAnsvar
ContextHåller en referens till det nuvarande tillståndsobjektet; exponerar tillståndsväxling.
State InterfaceDeklarerar tillståndsspecifika metoder.
Concrete StateImplementerar beteendet för ett specifikt tillstånd.

Exempel — Orderhantering (Java)

Scenariot: En order rör sig genom tillstånd och varje tillstånd tillåter olika operationer. Ogiltiga operationer blockeras med tydliga felmeddelanden.

// ── State Interface ───────────────────────────────────────

public interface OrderState {
    void pay(Order order);
    void ship(Order order);
    void deliver(Order order);
    void cancel(Order order);
    String getStateName();
}

// ── Context ───────────────────────────────────────────────

public class Order {
    private OrderState state;
    private final String orderId;

    public Order(String orderId) {
        this.orderId = orderId;
        this.state   = new PendingState(); // Starttillstånd
        System.out.println("📦 Order " + orderId + " skapad [" + state.getStateName() + "]");
    }

    public void setState(OrderState state) {
        System.out.println("  🔄 " + this.state.getStateName() + " → " + state.getStateName());
        this.state = state;
    }

    public String getOrderId() { return orderId; }

    // Delegerar till nuvarande tillstånd
    public void pay()     { state.pay(this); }
    public void ship()    { state.ship(this); }
    public void deliver() { state.deliver(this); }
    public void cancel()  { state.cancel(this); }

    public String getStateName() { return state.getStateName(); }
}

// ── Concrete States ───────────────────────────────────────

public class PendingState implements OrderState {
    @Override
    public void pay(Order order) {
        System.out.println("✅ Order " + order.getOrderId() + " betald.");
        order.setState(new PaidState());
    }

    @Override public void ship(Order order) {
        System.out.println("❌ Kan inte skicka obetalad order.");
    }

    @Override public void deliver(Order order) {
        System.out.println("❌ Kan inte leverera obetalad order.");
    }

    @Override public void cancel(Order order) {
        System.out.println("🚫 Order " + order.getOrderId() + " avbruten.");
        order.setState(new CancelledState());
    }

    @Override public String getStateName() { return "VÄNTANDE"; }
}

public class PaidState implements OrderState {
    @Override public void pay(Order order) {
        System.out.println("❌ Order är redan betald.");
    }

    @Override
    public void ship(Order order) {
        System.out.println("🚚 Order " + order.getOrderId() + " skickad.");
        order.setState(new ShippedState());
    }

    @Override public void deliver(Order order) {
        System.out.println("❌ Order måste skickas innan den kan levereras.");
    }

    @Override
    public void cancel(Order order) {
        System.out.println("🚫 Order " + order.getOrderId() + " avbruten — återbetalning initierad.");
        order.setState(new CancelledState());
    }

    @Override public String getStateName() { return "BETALD"; }
}

public class ShippedState implements OrderState {
    @Override public void pay(Order order) {
        System.out.println("❌ Order är redan betald och skickad.");
    }

    @Override public void ship(Order order) {
        System.out.println("❌ Order är redan skickad.");
    }

    @Override
    public void deliver(Order order) {
        System.out.println("🏠 Order " + order.getOrderId() + " levererad!");
        order.setState(new DeliveredState());
    }

    @Override public void cancel(Order order) {
        System.out.println("❌ Kan inte avbryta skickad order — kontakta support.");
    }

    @Override public String getStateName() { return "SKICKAD"; }
}

public class DeliveredState implements OrderState {
    @Override public void pay(Order order)     { System.out.println("❌ Order är redan avslutad."); }
    @Override public void ship(Order order)    { System.out.println("❌ Order är redan levererad."); }
    @Override public void deliver(Order order) { System.out.println("❌ Order är redan levererad."); }
    @Override public void cancel(Order order)  { System.out.println("❌ Kan inte avbryta levererad order."); }
    @Override public String getStateName()     { return "LEVERERAD"; }
}

public class CancelledState implements OrderState {
    @Override public void pay(Order order)     { System.out.println("❌ Order är avbruten."); }
    @Override public void ship(Order order)    { System.out.println("❌ Order är avbruten."); }
    @Override public void deliver(Order order) { System.out.println("❌ Order är avbruten."); }
    @Override public void cancel(Order order)  { System.out.println("❌ Order är redan avbruten."); }
    @Override public String getStateName()     { return "AVBRUTEN"; }
}

// ── Klientkod ─────────────────────────────────────────────

public class Application {
    public static void main(String[] args) {
        Order order = new Order("ORD-2024-001");

        System.out.println("\n--- Normal orderflöde ---");
        order.ship();    // Ogiltigt — inte betald
        order.pay();
        order.pay();     // Ogiltigt — redan betald
        order.ship();
        order.deliver();
        order.cancel();  // Ogiltigt — redan levererad

        System.out.println("\n--- Avbruten order ---");
        Order order2 = new Order("ORD-2024-002");
        order2.pay();
        order2.cancel();
        order2.pay();    // Ogiltigt — avbruten
    }
}

Output:

📦 Order ORD-2024-001 skapad [VÄNTANDE]

--- Normal orderflöde ---
❌ Kan inte skicka obetalad order.
✅ Order ORD-2024-001 betald.
  🔄 VÄNTANDE → BETALD
❌ Order är redan betald.
🚚 Order ORD-2024-001 skickad.
  🔄 BETALD → SKICKAD
🏠 Order ORD-2024-001 levererad!
  🔄 SKICKAD → LEVERERAD
❌ Kan inte avbryta levererad order.

--- Avbruten order ---
📦 Order ORD-2024-002 skapad [VÄNTANDE]
✅ Order ORD-2024-002 betald.
  🔄 VÄNTANDE → BETALD
🚫 Order ORD-2024-002 avbruten — återbetalning initierad.
  🔄 BETALD → AVBRUTEN
❌ Order är avbruten.

Fördelar

  • Single Responsibility Principle — Varje tillstånd har sin egna klass.
  • Open/Closed Principle — Lägg till nya tillstånd utan att ändra befintliga.
  • Eliminerar stora villkorsblock — tillståndslogiken är isolerad och tydlig.

Nackdelar

  • Kan vara överkurs om ett objekt bara har ett fåtal tillstånd.
  • Ökar antalet klasser i kodbasen avsevärt.

Av Victor Hernandez från Bytebase.se