State
"Allow an object to alter its behavior when its internal state changes. The object will appear to change its class." — GoF, Design Patterns (1994)
Bạn đã bao giờ cảm thấy một object như có "tính cách thay đổi" — lúc thì thế này, lúc thì thế khác? Đó chính là State pattern. Nó giống như một người lúc vui lúc buồn, và hành xử khác nhau trong mỗi trạng thái.
State là một behavioral pattern cho phép một đối tượng thay đổi hành vi của nó khi trạng thái nội tại thay đổi. Pattern này đóng gói mỗi trạng thái thành một class riêng, và ủy quyền (delegate) hành vi cho class trạng thái hiện tại. Nói đơn giản — object context sẽ "đổi class" khi trạng thái thay đổi.
Bài toán chi tiết
Tôi từng làm cho một công ty bảo hiểm, và tôi biết — quy trình phê duyệt là một cơn ác mộng. Hãy tưởng tượng bạn đang xây dựng hệ thống quản lý quy trình phê duyệt tài liệu (Document Approval Workflow) cho một công ty bảo hiểm. Mỗi tài liệu yêu cầu bồi thường (claim document) đi qua nhiều trạng thái:
- Draft: Người dùng tạo mới, có thể chỉnh sửa
- Pending Review: Đã gửi lên quản lý, chờ xem xét
- Under Review: Quản lý đang xem xét, không thể sửa
- Approved: Đã duyệt, chờ thanh toán
- Rejected: Từ chối, có thể sửa lại và gửi lại
- Paid: Đã thanh toán — terminal state
- Archived: Lưu trữ — terminal state
Mỗi trạng thái có các hành vi (method) khác nhau:
| Hành vi | Draft | Pending Review | Under Review | Approved | Rejected | Paid | Archived |
|---|---|---|---|---|---|---|---|
edit() | ✅ | ❌ Chờ duyệt | ❌ Đang review | ❌ Đã duyệt | ✅ | ❌ Đã thanh toán | ❌ Đã lưu trữ |
submit() | ✅ | ❌ Đã gửi | ❌ Đang review | ❌ Đã duyệt | ✅ | ❌ Đã thanh toán | ❌ Đã lưu trữ |
approve() | ❌ Chưa gửi | ✅ | ❌ Đang review | ❌ Đã duyệt | ❌ Đã từ chối | ❌ Đã thanh toán | ❌ Đã lưu trữ |
reject() | ❌ Chưa gửi | ✅ | ❌ Đang review | ❌ Đã duyệt | ❌ Đã từ chối | ❌ Đã thanh toán | ❌ Đã lưu trữ |
pay() | ❌ Chưa duyệt | ❌ Chưa duyệt | ❌ Chưa duyệt | ✅ | ❌ Đã từ chối | ❌ Đã thanh toán | ❌ Đã lưu trữ |
archive() | ❌ Đang xử lý | ❌ Đang xử lý | ❌ Đang xử lý | ✅ | ✅ | ✅ | ❌ Đã lưu trữ |
Và rồi — như bao người mới khác — cách tiếp cận đầu tiên của bạn sẽ là dùng if-else hoặc match-case. Tin tôi đi, tôi đã từng ở đó:
class NaiveDocument:
def edit(self):
if self.status == "DRAFT" or self.status == "REJECTED":
print("Đã sửa tài liệu")
elif self.status == "PENDING_REVIEW":
raise Exception("Không thể sửa — đang chờ duyệt")
elif self.status == "UNDER_REVIEW":
raise Exception("Không thể sửa — đang được review")
# ... còn nhiều nữa
Vấn đề của cách này — ôi trời, nhiều vô kể:
- Violates Open/Closed Principle: Mỗi lần thêm trạng thái mới (ví dụ:
PendingPayment), bạn phải sửa tất cả method củaDocument. - Code trùng lặp: Các điều kiện giống nhau lặp lại ở mọi method. Ví dụ, kiểm tra
status == "PAID" or status == "ARCHIVED"xuất hiện khắp nơi. - Khả năng sai cao: Dễ quên cập nhật một method khi thêm/xóa state.
- Không thể mở rộng: Nếu muốn thêm hành vi
escalate()(chuyển lên cấp trên), phải thêmelifvào tất cả các state. - Khó kiểm thử: Phải test tất cả tổ hợp (state × method) trong một class.
Giải pháp với Pattern
State pattern giải quyết vấn đề này một cách rất đơn giản — tách mỗi trạng thái thành một class riêng biệt, implement cùng interface:
- Context (
Document): Duy trì reference đến state hiện tại, rồi delegate các method cho state đó. Nó không cần biết state nào đang xử lý. - State interface (
DocumentState): Định nghĩa contract — tất cả concrete state phải implement những method này - Concrete States (
DraftState,PendingReviewState, ...): Implement hành vi cụ thể cho từng trạng thái. Mỗi class chỉ lo chuyện của nó.
Khi trạng thái thay đổi — chỉ cần context đổi reference state sang một state object khác. Lần gọi method tiếp theo sẽ được chuyển hướng đến state mới. Nhẹ nhàng, thanh lịch.
Phân tích thiết kế
Nguyên lý OOP được áp dụng
Nhìn vào State, tôi thấy nó chính là hiện thân của Single Responsibility Principle:
- Single Responsibility: Mỗi class state chỉ chịu trách nhiệm cho hành vi của một trạng thái
- Open/Closed Principle: Thêm state mới = thêm class mới — không sửa code cũ
- Strategy Pattern resemblance: State và Strategy có cấu trúc giống nhau, nhưng khác về intent. State cho phép context tự động chuyển đổi; Strategy do client chọn.
- Polymorphism: Hành vi thay đổi dựa trên runtime type của state
Trade-offs
Nhưng không có bữa trưa nào miễn phí:
- Class explosion: Mỗi state là một class mới. Với hệ thống có 10-15 state, số class tăng đáng kể.
- Context-state coupling: Context phải expose dữ liệu cho state (thường qua parameter). Có thể vi phạm encapsulation.
- State transition logic phân tán: Ai quyết định chuyển state? Có thể để state tự quyết định, hoặc context quyết định, hoặc có state machine riêng.