Command: undo bằng state và macro mở tiệc

Nút hoàn tác ghi nhớ trạng thái trước khi chạy, và macro một nút mở cả rạp hát cho buổi tiệc.
KhóDesign pattern ~60 phút

Giới thiệu

Bài này học hoàn tác và macro nhiều lệnh. Bạn dùng nó cho nút undo quạt và nút mở tiệc 1 chạm.

Kiến thức cần nhớ

Nút hoàn tác

  • Quy ước nút undo làm ngược việc của nút vừa bấm.
  • Đèn bật tắt đối xứng nên dễ, quạt 4 nấc thì khó.
  • Tắt quạt nấc nhanh phải trả đúng về nhanh, không bật chung chung.
  • Ví dụ đời thường: như Ctrl+Z phải trả đúng chữ vừa xóa.

Ghi nhớ trạng thái

  • Lệnh ghi lại nấc cũ của quạt trước khi đổi.
  • Thực thi thì đổi sang nấc mới, hoàn tác thì trả nấc cũ.
  • Remote chỉ nhớ lệnh gần nhất nên undo 1 bước.
  • Muốn nhiều bước thì dùng ngăn xếp lịch sử.
# Ý tưởng lệnh quạt có undo
# execute: self.cu = quat.nac; quat.doi(self.moi)  # nhớ cũ rồi đổi mới
# undo: quat.doi(self.cu)  # trả lại nấc cũ

Macro mở tiệc

  • Macro cũng là lệnh nên cắm vào khe như lệnh thường.
  • Bên trong giữ danh sách lệnh nhỏ: đèn, màn, máy chiếu, loa.
  • Thực thi chạy xuôi từng lệnh, hoàn tác chạy ngược thứ tự.
  • Ngược thứ tự mới an toàn như xếp bát đĩa.
Khía cạnhLệnh đơnMacro
Bên trong1 thiết bịDanh sách lệnh
Thực thi1 hành độngChạy xuôi cả danh sách
Hoàn tácTrả trạng thái cũChạy ngược từng lệnh

Ví dụ

  • Input: quạt nấc vừa, bấm sang nhanh rồi undo. Output: lên nhanh rồi về vừa.
  • Giải thích: lệnh đã nhớ nấc vừa trước khi đổi.
  • Input: chạy macro 4 thiết bị rồi undo. Output: bật xuôi, tắt ngược, về đúng trạng thái cũ.
  • Giải thích: hoàn tác ngược thứ tự mới dọn an toàn.

💻 Bài tập

Ngôn ngữ: C++17 (hoặc Java). Dùng remote ngày 28. Nộp vào day-081/.

Bài 1: Undo một bước và quạt 4 nấc

Thêm virtual void undo() = 0; vào Command. Remote giữ std::shared_ptr<Command> undoCommand và có undoButtonPressed().

class CeilingFan {
 public:
  enum Speed { OFF, LOW, MEDIUM, HIGH };
  void setSpeed(Speed s); Speed speed() const;
};
class CeilingFanHighCommand : public Command {
  CeilingFan &fan; CeilingFan::Speed prev;   // lưu nấc cũ trước khi đổi
 public:
  void execute() override;   // prev = fan.speed(); fan.setSpeed(HIGH);
  void undo() override;      // fan.setSpeed(prev);
};

Viết CeilingFanHighCommand, CeilingFanMediumCommand, CeilingFanLowCommand, CeilingFanOffCommand.

quạt MEDIUM; bấm HIGH; undo    -> MEDIUM
quạt LOW; bấm OFF; undo        -> LOW
LightOn; undo                   -> đèn tắt
undo khi chưa bấm gì            -> không làm gì (NoCommand)

Bài 2: Undo nhiều bước và redo

Thay undoCommand bằng 2 stack history và redoStack.

Hành độngHiệu ứng
Bấm nút bất kỳexecute, đẩy vào history, xóa redoStack
undo()Lấy đỉnh history, gọi undo, đẩy vào redoStack
redo()Lấy đỉnh redoStack, gọi execute, đẩy vào history
Giới hạnhistory giữ tối đa 50 lệnh, bỏ lệnh cũ nhất
HIGH, LOW, OFF; undo x3 -> quạt về nấc ban đầu
undo x2; redo           -> quạt ở LOW
undo; bấm MEDIUM; redo  -> redo không làm gì (redoStack đã bị xóa)

Bài 3: Macro "mở tiệc" (nâng cao)

MacroCommand(std::vector<std::shared_ptr<Command>>): execute chạy xuôi, undo chạy ngược.

  1. Tạo macro partyOn: đèn dim 30%, stereo bật âm lượng 11, TV bật, hot tub bật và nhiệt 40 độ. Macro partyOff ngược lại.
  2. Undo partyOn đưa mọi thiết bị về đúng trạng thái trước đó (không chỉ "tắt").
  3. Nếu một lệnh giữa macro ném lỗi, hoàn tác các lệnh đã chạy trước nó rồi ném lại lỗi. Viết test.

✅ Tiêu chí hoàn thành

  • Test mẫu 3 bài pass.
  • Undo quạt trả đúng nấc cũ, không phải "bật mặc định".
  • Macro hoàn tác theo thứ tự ngược, có test lỗi giữa chừng.
  • Vẽ trạng thái 2 stack sau mỗi bước của test Bài 2.

❓ Câu hỏi ôn tập

  • Lệnh ghi nhớ gì để undo được?
  • Vì sao quạt nhiều nấc khó hơn đèn?
  • Macro thực thi và undo thế nào?
  • Vì sao undo macro phải ngược thứ tự?

🌕 Bạn đã hoàn thành ngày 81. 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.