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
Ôn lại trước khi học
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ạnh | Lệnh đơn | Macro |
|---|---|---|
| Bên trong | 1 thiết bị | Danh sách lệnh |
| Thực thi | 1 hành động | Chạy xuôi cả danh sách |
| Hoàn tác | Trả 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 động | Hiệ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ạn | history 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.
- 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 độ. MacropartyOffngược lại. - Undo
partyOnđưa mọi thiết bị về đúng trạng thái trước đó (không chỉ "tắt"). - 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.