Chuyển tới nội dung chính

State Pattern (Mẫu State)

Định nghĩa (Definition)

State cho phép object thay đổi hành vi khi internal state thay đổi. Object sẽ có vẻ như thay đổi class. Mỗi state được đóng gói trong class riêng, và context ủy quyền cho state object hiện tại.

Ví dụ Coffee Shop

Đơn cà phê có vòng đời: Created → Paid → Brewing → Ready → Collected. Ở mỗi state, đơn phản hồi khác nhau với cùng action — không thể pha đơn chưa thanh toán, không thể lấy đơn chưa sẵn sàng.

Cấu trúc (Structure)

State Interface

public interface IOrderState
{
void Pay(OrderContext order);
void StartBrewing(OrderContext order);
void FinishBrewing(OrderContext order);
void Collect(OrderContext order);
string GetName();
}

Context — Đơn hàng (The Order)

public class OrderContext
{
public string Customer { get; }
public string Coffee { get; }
public CupSize Size { get; }
private IOrderState _state;

public OrderContext(string customer, string coffee, CupSize size)
{
Customer = customer;
Coffee = coffee;
Size = size;
_state = new CreatedState();
}

public void TransitionTo(IOrderState state)
{
_state = state;
Console.WriteLine($" → State: {_state.GetName()}");
}

public void Pay() => _state.Pay(this);
public void StartBrewing() => _state.StartBrewing(this);
public void FinishBrewing() => _state.FinishBrewing(this);
public void Collect() => _state.Collect(this);
}

Concrete State

public class CreatedState : IOrderState
{
public void Pay(OrderContext order)
{
Console.WriteLine($" [Thanh toán] Thu tiền {order.Coffee}...");
order.TransitionTo(new PaidState());
}

public void StartBrewing(OrderContext order) =>
Console.WriteLine(" ⚠️ Không thể pha — đơn chưa thanh toán!");

public void FinishBrewing(OrderContext order) =>
Console.WriteLine(" ⚠️ Không thể hoàn thành — chưa bắt đầu pha!");

public void Collect(OrderContext order) =>
Console.WriteLine(" ⚠️ Không thể lấy — đơn chưa sẵn sàng!");

public string GetName() => "Created";
}

public class PaidState : IOrderState
{
public void Pay(OrderContext order) =>
Console.WriteLine(" ⚠️ Đã thanh toán rồi!");

public void StartBrewing(OrderContext order)
{
Console.WriteLine($" [Barista] Bắt đầu pha {order.Coffee}...");
order.TransitionTo(new BrewingState());
}

public void FinishBrewing(OrderContext order) =>
Console.WriteLine(" ⚠️ Không thể hoàn thành — chưa bắt đầu pha!");

public void Collect(OrderContext order) =>
Console.WriteLine(" ⚠️ Không thể lấy — đơn chưa sẵn sàng!");

public string GetName() => "Paid";
}

public class BrewingState : IOrderState
{
public void Pay(OrderContext order) =>
Console.WriteLine(" ⚠️ Đã thanh toán rồi!");

public void StartBrewing(OrderContext order) =>
Console.WriteLine(" ⚠️ Đang pha rồi!");

public void FinishBrewing(OrderContext order)
{
Console.WriteLine($" [Barista] {order.Coffee} xong!");
order.TransitionTo(new ReadyState());
}

public void Collect(OrderContext order) =>
Console.WriteLine(" ⚠️ Không thể lấy — đang pha!");

public string GetName() => "Brewing";
}

public class ReadyState : IOrderState
{
public void Pay(OrderContext order) =>
Console.WriteLine(" ⚠️ Đã thanh toán rồi!");

public void StartBrewing(OrderContext order) =>
Console.WriteLine(" ⚠️ Đã pha xong rồi!");

public void FinishBrewing(OrderContext order) =>
Console.WriteLine(" ⚠️ Đã hoàn thành rồi!");

public void Collect(OrderContext order)
{
Console.WriteLine($" [Lấy hàng] {order.Customer} đã lấy {order.Coffee}! 🎉");
order.TransitionTo(new CollectedState());
}

public string GetName() => "Ready";
}

public class CollectedState : IOrderState
{
public void Pay(OrderContext order) =>
Console.WriteLine(" ⚠️ Đơn đã hoàn thành.");

public void StartBrewing(OrderContext order) =>
Console.WriteLine(" ⚠️ Đơn đã hoàn thành.");

public void FinishBrewing(OrderContext order) =>
Console.WriteLine(" ⚠️ Đơn đã hoàn thành.");

public void Collect(OrderContext order) =>
Console.WriteLine(" ⚠️ Đơn đã được lấy rồi.");

public string GetName() => "Collected";
}

Client

var order = new OrderContext("Alice", "Latte", CupSize.Large);

order.StartBrewing(); // ⚠️ Không thể pha — đơn chưa thanh toán!
order.Pay(); // → State: Paid
order.StartBrewing(); // → State: Brewing
order.FinishBrewing(); // → State: Ready
order.Collect(); // → State: Collected
order.Pay(); // ⚠️ Đơn đã hoàn thành.
mẹo

Mỗi state class xử lý cùng method khác nhau. OrderContext ủy quyền cho state hiện tại. Transition không hợp lệ được xử lý gracefully — không cần if (state == "Brewing") check.

State vs Strategy

StateStrategy
Ai thay đổiObject tự thay đổi (internal transition)Client (external injection)
Kiến thứcClient không biết về stateClient chọn strategy
TransitionState biết về state khácStrategy không biết về nhau
Mục đíchHành vi thay đổi theo stateThuật toán thay đổi theo lựa chọn

Sử dụng thực tế trong .NET (.NET Real-World Usage)

  • ASP.NET Core IAuthenticationHandler — hành vi khác nhau per auth state
  • Task<T> lifecycle — Created → WaitingForActivation → Running → Completed/Faulted
  • Workflow engine (Elsa, Orleans grain) — xử lý theo state
  • Game development — character state (idle, running, jumping, attacking)

Khi nào sử dụng (When to Use)

  • Hành vi object phụ thuộc vào state và phải thay đổi lúc runtime
  • Có khối if/switch lớn kiểm tra state trong nhiều method
  • State transition phức tạp với nhiều rule

Khi nào KHÔNG sử dụng (When NOT to Use)

  • Chỉ có 2 state — boolean flag đơn giản là đủ
  • Logic state đơn giản — enum + switch đơn giản hơn
  • State không có sự khác biệt hành vi phức tạp

Điểm chính (Key Takeaways)

  • State đóng gói hành vi mỗi state trong class riêng
  • Context ủy quyền cho state object hiện tại
  • State transition diễn ra bên trong — context gọi TransitionTo()
  • Loại bỏ khối điều kiện lớn — mỗi state class xử lý logic riêng

Câu hỏi phỏng vấn (Interview Questions)

Q: State khác finite state machine (FSM) như thế nào? State pattern chính là implement FSM trong OOP. Mỗi state class đại diện một state, và transition là method call. FSM truyền thống dùng state table hoặc switch — State pattern dùng polymorphism.

Q: State nên là singleton hay instance mới? Nếu state không có state nội bộ (không instance field), có thể là singleton — chia sẻ cho tất cả context. Nếu state giữ data (như timer), tạo instance mới per context.

  • Strategy — cấu trúc tương tự, client-driven (không state-driven)
  • Template Method — thuật toán cố định với bước thay đổi
  • Command — có thể trigger state transition