Iterator: sáp nhập quán ăn và single responsibility
Ôn lại trước khi học
Giới thiệu
Bài này học Iterator duyệt nhiều cấu trúc bằng một cách chung. Mẫu này giúp giấu cách lưu để đổi tự do.
Kiến thức cần nhớ
Iterator là gì
- Iterator cho cách duyệt tuần tự mà giấu cách lưu.
- Mỗi thực đơn tự tạo bộ duyệt hiểu cấu trúc của nó.
- Phục vụ chỉ dùng giao diện chung: còn món, lấy tiếp.
- Ví dụ đời thường: một remote dùng chung nhiều đầu máy.
Hai quán hai cách lưu
- Quán bánh lưu danh sách động vì món đổi theo mùa.
- Quán tối lưu mảng cố định vì món ổn định.
- Không Iterator: phục vụ phải học cả hai cách duyệt.
- Có Iterator: phục vụ xin bộ duyệt rồi duyệt như nhau.
# Phục vụ duyệt chung không cần biết cách lưu
def in_thuc_don(duyet_a, duyet_b):
for duyet in (duyet_a, duyet_b): # 2 quán
while duyet.con_mon(): # hỏi còn món không
print(duyet.lay_tiep()) # lấy món tiếp
Đơn trách nhiệm
- Thực đơn chỉ lo giữ món ăn.
- Bộ duyệt chỉ lo đi từng món.
- Phục vụ chỉ lo in và tiếp khách.
- Ví dụ đời thường: bếp nấu, bồi bàn bưng, thu ngân tính.
| Tiêu chí | Tự duyệt | Qua bộ duyệt |
|---|---|---|
| Biết cách lưu | Biết hết | Không cần biết |
| Đổi cách lưu | Sửa phục vụ | Sửa đúng bộ duyệt |
| Thêm quán mới | Mở phục vụ | Thêm bộ duyệt mới |
Ví dụ
- Input: in thực đơn chung 2 quán -> Output: đủ món, đúng thứ tự, không lặp.
- Câu này cho thấy một vòng lặp duyệt cả hai quán.
- Input: đổi cách lưu quán tối -> Output: thực đơn in ra y hệt, phục vụ không sửa.
- Câu này cho thấy giấu cách lưu rất lợi.
💻 Bài tập
Ngôn ngữ: C++17 (hoặc Java). Nộp vào day-037/.
Bài 1: Hai quán, một phục vụ
struct MenuItem { std::string name; double price; bool vegetarian; };
template <class T> struct Iterator { virtual bool hasNext() const = 0; virtual T next() = 0; virtual ~Iterator() = default; };
struct Menu { virtual std::unique_ptr<Iterator<MenuItem>> createIterator() const = 0; virtual ~Menu() = default; };
| Lớp | Cách lưu | Iterator |
|---|---|---|
PancakeHouseMenu | std::vector<MenuItem> | VectorIterator |
DinerMenu | MenuItem items[6] + int count | ArrayIterator, dừng ở count |
CafeMenu | std::unordered_map<std::string, MenuItem> | MapIterator |
Waitress nhận std::vector<const Menu*>. Có printMenu() và printVegetarianMenu().
printMenu() -> in đủ món 3 quán, mỗi quán theo thứ tự lưu của nó
DinerMenu thêm món thứ 7 -> ném length_error
next() khi hết món -> ném out_of_range
Ràng buộc: Waitress không biết std::vector, mảng hay map nào.
Bài 2: Iterator theo chuẩn C++
Viết lớp Range(int start, int stop, int step) dùng được trong for (int x : Range(0, 10, 3)). Cần begin(), end(), iterator có operator*, operator++, operator!=.
Range(0, 10, 3) -> 0 3 6 9
Range(10, 0, -4) -> 10 6 2
Range(0, 0, 1) -> không in gì
step = 0 -> ném invalid_argument
Bài 3: Iterator lười và lọc (nâng cao)
Viết FilterIterator<T> bọc một iterator khác và một hàm pred, chỉ trả phần tử thỏa pred. Viết TakeIterator<T> chỉ trả n phần tử đầu.
Dùng chúng để in 3 món chay đầu tiên của cả 3 quán mà không tạo vector trung gian.
Viết 3 câu: Iterator giúp giữ nguyên lý đơn trách nhiệm như thế nào trong bài này.
✅ Tiêu chí hoàn thành
- Test mẫu pass.
- Đổi cách lưu
DinerMenusangstd::listmàWaitresskhông sửa. -
Rangedùng được với range-based for. - Bài 3 không cấp phát vector trung gian.
❓ Câu hỏi ôn tập
- Iterator giấu cách lưu thế nào?
- Hai quán lưu thực đơn khác nhau ra sao?
- Ai tạo bộ duyệt, ai dùng bộ duyệt?
- Khi nào không cần dùng Iterator?
🌕 Bạn đã hoàn thành ngày 37. Làm hết bài tập phía trên rồi sang ngày tiếp theo.