Iterator: sáp nhập quán ăn và single responsibility

Định nghĩa Iterator, hai quán ăn hai cách lưu thực đơn, và cách tách trách nhiệm duyệt khỏi trách nhiệm phục vụ.
Trung bìnhDesign pattern ~60 phút

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ệtQua bộ duyệt
Biết cách lưuBiết hếtKhông cần biết
Đổi cách lưuSửa phục vụSửa đúng bộ duyệt
Thêm quán mớiMở 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ớpCách lưuIterator
PancakeHouseMenustd::vector<MenuItem>VectorIterator
DinerMenuMenuItem items[6] + int countArrayIterator, dừng ở count
CafeMenustd::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 DinerMenu sang std::list mà Waitress không sửa.
  • Range dù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.

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.