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

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áiRải 4 bảngGom 1 lớp
Thêm trạng tháiSửa 4 bảngThêm 1 lớp
Sót nhánhDễ sótKhó 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

  • GumballMachine không có switch/if theo 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ừ SoldOut sang NoQuarter.

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.

  1. Vẽ sơ đồ chuyển trạng thái (Mermaid stateDiagram-v2) trong README.md.
  2. Cài bằng State pattern. Hành động không hợp lệ ném std::logic_error kèm tên trạng thái.
  3. 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.
  • GumballMachine bản mới không có switch theo 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.

Bớt paste mù, hiểu sâu từng dòng mình viết.

180 Days Challenge. 180 ngày cai nghiện vibe code.