App: text editor undo với stack, Command và Memento
Xây text editor có undo redo: Command đóng gói thao tác, Memento snapshot, stack lịch sử, macro và benchmark bộ nhớ.
KhóDesign pattern ~120 phút
Ôn lại trước khi học
Giới thiệu
Bài này xây editor có undo redo. Gõ sai bấm undo là quay lại ngay.
Kiến thức cần nhớ
Command gói thao tác
- Mỗi thao tác là 1 lệnh có execute và unexecute.
- Insert lưu chữ đã chèn, Delete lưu chữ đã xóa.
- Ví dụ đời thường: mỗi bước đi ghi vào sổ để xóa được.
- Lệnh mới sau undo sẽ xóa sạch redo.
2 stack undo redo
- Execute đẩy vào stack undo và xóa stack redo.
- Undo pop ra đảo ngược rồi đẩy sang redo.
- Redo pop ra làm lại rồi đẩy về undo.
- Giới hạn 500 entry để RAM không phình.
Memento và macro
- Memento chụp ảnh toàn văn cho thao tác lớn như paste 5000 chữ.
- Undo việc lớn chỉ cần khôi phục ảnh 1 bước.
- Macro gom 5 thao tác thành 1 lệnh undo 1 lần.
- Delta nhỏ tiết kiệm RAM, snapshot lớn tiện 1 bước.
Ví dụ
- Input: gõ hello rồi xóa 2 chữ. Output: còn hel, undo về hello, redo về hel.
- Giải thích: mỗi lệnh nhớ đủ delta để đi tới lui.
- Input: paste 5000 chữ rồi undo. Output: về đúng văn bản cũ chỉ 1 bước.
- Giải thích: snapshot lưu ảnh trước paste nên khôi phục 1 lần.
💻 Bài tập
Ngôn ngữ: C++17 (hoặc Python). Dùng Command ngày 81, Memento ngày 95. Nộp vào day-148-editor/.
Bài 1: Văn bản và lệnh
class Document {
public:
void insert(size_t pos, const std::string &s);
std::string erase(size_t pos, size_t len); // trả phần đã xóa
const std::string &text() const; size_t cursor() const; void setCursor(size_t);
};
struct EditCommand { virtual void execute(Document&) = 0; virtual void undo(Document&) = 0;
virtual bool mergeWith(const EditCommand &next) { return false; } };
| Lệnh | Lưu gì để undo |
|---|---|
InsertCommand(pos, text) | Vị trí và chuỗi đã chèn |
DeleteCommand(pos, len) | Chuỗi đã xóa (lấy lúc execute) |
ReplaceCommand(pos, len, text) | Chuỗi cũ và chuỗi mới |
Gộp gõ phím: 10 lần gõ từng ký tự liền nhau (cùng vị trí nối tiếp, cách nhau dưới 1 giây) gộp thành 1 InsertCommand. Dùng mergeWith.
Bài 2: Lịch sử undo/redo
type "Hello"; type " World"; undo -> "Hello"; redo -> "Hello World"
undo; type "!"; redo -> không làm gì, văn bản "Hello!"
Lịch sử giới hạn 500 mục, mục cũ nhất bị bỏ
Undo khi lịch sử rỗng -> không làm gì
MacroCommand: "Tìm và thay tất cả" là 1 macro, undo 1 lần là hoàn tác toàn bộ.- Paste đoạn trên 5000 ký tự: lưu Memento (ảnh toàn văn bản) thay vì lệnh. Undo khôi phục ảnh.
- In bộ nhớ lịch sử ước lượng sau 10.000 thao tác.
Bài 3: Giao diện dòng lệnh (nâng cao)
- REPL với lệnh:
i <pos> <text>,d <pos> <len>,r <find> <replace>,u,y(redo),p(in),h(in lịch sử, đánh dấu vị trí hiện tại). - Lưu và mở file. Đánh dấu "đã sửa" khi lịch sử khác điểm lưu gần nhất (undo về đúng điểm lưu thì hết dấu).
- Test tính chất: chuỗi 1000 thao tác ngẫu nhiên, rồi undo hết phải về văn bản ban đầu, rồi redo hết phải về văn bản cuối.
✅ Tiêu chí hoàn thành
- Test mẫu undo/redo pass.
- Gõ phím liền nhau gộp thành 1 bước undo.
- Test tính chất 1000 thao tác ngẫu nhiên pass.
- Dấu "đã sửa" đúng khi undo về điểm lưu.
❓ Câu hỏi ôn tập
- Command cần lưu gì để đảo ngược được?
- 2 stack undo redo chuyển lệnh cho nhau thế nào?
- Memento dùng cho thao tác nào?
- Macro gom nhiều lệnh để làm gì?
🌕 Bạn đã hoàn thành ngày 148. Làm hết bài tập phía trên rồi sang ngày tiếp theo.