State: máy bán kẹo và ủy thác trạng thái
Định nghĩa State, máy kẹo bốn trạng thái với rẽ nhánh khổng lồ, và cách mỗi trạng thái tự lo hành vi của mình.
KhóDesign pattern ~60 phút
Ôn lại trước khi học
Giới thiệu
Bài này học State để hành vi đổi theo trạng thái. Bạn dùng nó thay cho bảng rẽ nhánh khổng lồ.
Kiến thức cần nhớ
Định nghĩa
- Cho phép đổi hành vi khi trạng thái nội tại đổi.
- Nhìn như thể đối tượng đổi cả lớp của mình.
- Từ khóa là hành vi theo trạng thái và ủy thác.
- Ví dụ đời thường: như đèn giao thông, cùng nút bấm nhưng mỗi màu phản ứng khác.
Máy kẹo 4 trạng thái
- Bốn trạng thái: chờ xu (NoQuarter), đã nhận xu (HasQuarter), đang nhả kẹo (Sold), hết kẹo (SoldOut).
- Bốn hành động: bỏ xu, trả xu, quay tay, nhả kẹo.
- Cách cũ rẽ nhánh 4 nhân 4 ở mọi nơi, thêm trạng thái là sửa khắp.
- Ví dụ mua thành công: chờ nhận xu, quay chuyển sang bán, nhả rồi về chờ.
Thiết kế ủy thác
- Mỗi trạng thái là 1 lớp riêng cùng giao diện 4 hành động.
- Máy chỉ giữ trạng thái hiện tại và chuyển lời cho nó.
- Trạng thái xử lý xong tự chuyển máy sang trạng thái khác.
- Mỗi nhịp chỉ đúng 1 lớp làm việc, máy chỉ giữ số kẹo.
| Tiêu chí | Rẽ nhánh | Ủy thác State |
|---|---|---|
| Logic 1 trạng thái | Rải 4 bảng | Gom 1 lớp |
| Thêm trạng thái | Sửa 4 bảng | Thêm 1 lớp |
| Sót nhánh | Dễ sót | Khó sót |
Ví dụ
- Input: quay tay khi chưa bỏ xu. Output: nhắc bỏ xu, không nhả, vẫn chờ xu.
- Giải thích: trạng thái chờ chỉ nhắc chứ không chuyển.
- Input: mua viên cuối cùng. Output: nhả viên cuối rồi sang hết kẹo.
- Giải thích: tồn kho hết thì lượt sau từ chối bỏ xu.
💻 Bài tập
Ngôn ngữ: C++17 (hoặc Java). Nộp vào day-085/. Mọi hành động trả std::string thông báo để test.
Bài 1: Máy bán kẹo bằng switch
Viết GumballMachineV1 theo cách cũ: 1 biến state kiểu enum, 4 hàm insertQuarter, ejectQuarter, turnCrank, dispense, mỗi hàm là 1 switch 4 nhánh.
Máy 2 viên:
turnCrank() -> "Bạn chưa bỏ xu"
insertQuarter(); turnCrank()-> "Bạn quay tay... Một viên kẹo lăn ra", còn 1
insertQuarter(); insertQuarter() -> lần 2: "Đã có xu rồi"
ejectQuarter() -> "Trả xu"
insertQuarter(); turnCrank()-> nhả viên cuối, sang SOLD_OUT
insertQuarter() -> "Hết kẹo, không nhận xu"
Viết đủ test cho 16 tổ hợp (4 trạng thái x 4 hành động).
Bài 2: Refactor sang State
struct State {
virtual std::string insertQuarter() = 0; virtual std::string ejectQuarter() = 0;
virtual std::string turnCrank() = 0; virtual std::string dispense() = 0;
virtual ~State() = default;
};
class GumballMachine {
public:
explicit GumballMachine(int count);
std::string insertQuarter(); std::string ejectQuarter(); std::string turnCrank();
void setState(State *s); int count() const; void releaseBall();
State *noQuarterState(); State *hasQuarterState(); State *soldState(); State *soldOutState();
};
Ràng buộc
GumballMachinekhông cóswitch/iftheo trạng thái.- Cùng 16 test của Bài 1 phải pass với bản mới.
- Thêm
refill(int n): từSoldOutsangNoQuarter.
Bài 3: Máy trạng thái cho đơn hàng (nâng cao)
Đơn hàng có trạng thái: Created, Paid, Shipped, Delivered, Cancelled, Refunded. Hành động: pay, ship, deliver, cancel, refund.
- Vẽ sơ đồ chuyển trạng thái (Mermaid
stateDiagram-v2) trongREADME.md. - Cài bằng State pattern. Hành động không hợp lệ ném
std::logic_errorkèm tên trạng thái. - Ghi lịch sử chuyển trạng thái
(từ, hành động, tới).
Created -pay-> Paid -ship-> Shipped -deliver-> Delivered
Delivered.cancel() -> logic_error "Không thể hủy đơn đã giao"
Paid -cancel-> Cancelled -refund-> Refunded
✅ Tiêu chí hoàn thành
- 16 test pass cho cả V1 và bản State.
-
GumballMachinebản mới không cóswitchtheo trạng thái. - Bài 3 có sơ đồ Mermaid và test mọi chuyển hợp lệ và không hợp lệ.
- Sanitizer sạch.
❓ Câu hỏi ôn tập
- State định nghĩa trong 1 câu là gì?
- Máy kẹo có mấy trạng thái và hành động nào?
- Ủy thác trạng thái khác rẽ nhánh ở đâu?
- Một lượt mua thành công đi qua những trạng thái nào?
🌕 Bạn đã hoàn thành ngày 85. Làm hết bài tập phía trên rồi sang ngày tiếp theo.