Composite: thực đơn cây, trong suốt vs an toàn

Định nghĩa Composite, thực đơn lồng thực đơn con, và đánh đổi giữa trong suốt dễ dùng với an toàn đúng kiểu.
KhóDesign pattern ~60 phút

Giới thiệu

Bài này học Composite để xử lý cây đồng nhất. Bạn dùng nó cho thực đơn lồng nhau nhiều tầng.

Kiến thức cần nhớ

Định nghĩa Composite

  • Gộp đối tượng thành cây để biểu diễn phân cấp phần tử.
  • Khách hàng đối xử cụm và lá như nhau.
  • Từ khóa là cây và đồng nhất.
  • Ví dụ đời thường: như thư mục chứa file và thư mục con, mở đều như nhau.

Thực đơn cây

  • Thực đơn tổng chứa thực đơn con, thực đơn con chứa món lá.
  • Cụm giữ danh sách con, gọi in thì gọi lan xuống từng con.
  • Lá chỉ in chính nó, không có con.
  • Ví dụ đời thường: như gọi cả bàn ăn, bếp tự lo từng món.

Trong suốt vs an toàn

  • Trong suốt: thêm bớt nằm ở giao diện chung, khách không rẽ nhánh.
  • An toàn: thêm bớt chỉ ở cụm, lá gọn sạch.
  • Trong suốt lỗi phát hiện lúc chạy, an toàn lỗi phát hiện lúc biên dịch.
  • Bài này chọn trong suốt vì đơn giản cho khách.
Khía cạnhTrong suốtAn toàn
Giao diện chungCó thêm bớtChỉ việc chung
Khách hàngKhông rẽ nhánhPhải kiểm tra cụm
LáMang phương thức thừaGọn sạch

Ví dụ

  • Input: gọi in trên thực đơn tổng. Output: mọi món mọi tầng in đúng 1 lần.
  • Giải thích: yêu cầu lan đệ quy từ cụm cha xuống lá.
  • Input: thêm con vào món lá ở bản trong suốt. Output: lá từ chối rõ ràng, cây không đổi.
  • Giải thích: lá không có con nên phải báo lỗi nhẹ nhàng.

💻 Bài tập

Ngôn ngữ: C++17 (hoặc Java). Nộp vào day-067/.

Bài 1: Thực đơn dạng cây (trong suốt)

class MenuComponent {
 public:
  virtual std::string name() const = 0;
  virtual void print(std::ostream &os, int indent = 0) const = 0;
  virtual double price() const { throw std::logic_error("không hỗ trợ"); }
  virtual bool vegetarian() const { throw std::logic_error("không hỗ trợ"); }
  virtual void add(std::shared_ptr<MenuComponent>) { throw std::logic_error("không hỗ trợ"); }
  virtual void remove(const MenuComponent *) { throw std::logic_error("không hỗ trợ"); }
  virtual ~MenuComponent() = default;
};
class MenuItem : public MenuComponent { ... };   // lá
class Menu : public MenuComponent { ... };       // cụm, giữ vector con

Dựng cây: ALL MENUS gồm PANCAKE HOUSE, DINER, CAFE. DINER chứa thêm thực đơn con DESSERT.

allMenus.print() -> in mọi món mọi tầng, thụt lề theo độ sâu, mỗi món đúng 1 lần
item.add(...)    -> ném logic_error, cây không đổi

Bài 2: Thao tác trên cả cây

Thêm các hàm đệ quy vào giao diện chung:

HàmLáCụm
double totalPrice()giá chính nótổng các con
int countItems()1tổng các con
int depth()01 + sâu nhất các con
std::shared_ptr<MenuComponent> find(name)so têntìm trong các con

Bài 3: Bản an toàn và hệ thống file (nâng cao)

  1. Viết lại theo kiểu an toàn: add/remove chỉ có ở Menu. Viết 1 dòng code gọi add trên lá: bản an toàn không biên dịch được. Viết bảng so sánh 2 cách.
  2. Mô hình thư mục: File(name, size) và Directory. Viết du (tổng dung lượng), tree (in cây), findLarger(n) (các file lớn hơn n byte). Đọc cây thư mục thật bằng std::filesystem để dựng Composite.

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

  • Test mẫu pass, in cây đúng thứ tự và thụt lề.
  • Các hàm Bài 2 đúng trên cây 4 tầng.
  • Bài 3 có bảng trong suốt vs an toàn và du khớp lệnh du -sb (sai số do metadata).
  • Client in cây mà không có dynamic_cast hay kiểm tra kiểu.

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

  • Định nghĩa Composite trong 1 câu là gì?
  • Cụm và lá khác nhau thế nào?
  • Trong suốt và an toàn khác nhau ở đâu?
  • Lệnh in lan xuống cây ra sao?

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