Decorator: Starbuzz Coffee và chuỗi bọc

Định nghĩa Decorator, nguyên lý mở rộng không sửa đổi, và cách Starbuzz cộng dồn giá topping bằng chuỗi bọc.
Trung bìnhDesign pattern ~60 phút

Giới thiệu

Bài này học cách cộng topping không vỡ lớp cũ. Bạn bọc đối tượng như búp bê Nga.

Kiến thức cần nhớ

Vấn đề của kế thừa

  • Mỗi tổ hợp topping một lớp thì lớp bùng nổ.
  • Nhét hết vào lớp cha thì lớp cha phình to.
  • Thêm topping mới phải sửa code cũ.
  • Ví dụ đời thường: in menu riêng cho mọi combo.

Chuỗi bọc cộng dồn

  • Mỗi topping bọc đồ uống bên trong.
  • Hỏi giá thì hỏi vào trong rồi cộng thêm.
  • Hỏi mô tả thì nối thêm tên mình.
  • Ví dụ đời thường: hộp quà lồng nhau, mở dần.
  • Thêm topping mới chỉ thêm 1 lớp bọc.

Khi nào bọc, khi nào không

  • Cần gắn thêm việc cho từng món riêng lẻ thì bọc.
  • Số combo quá lớn không liệt kê nổi thì bọc.
  • Chỉ 2-3 combo cố định thì liệt kê thẳng.
  • Ví dụ đời thường: dán sticker lên cốc riêng.
Cách làmThêm topping mới
Mỗi combo 1 lớpSố lớp nhân đôi
Cờ ở lớp chaMở lớp cha ra sửa
Chuỗi bọcThêm 1 lớp mới

Ví dụ

  • Input: cà phê đậm + 1 sữa. Output: giá nền + 1 sữa, mô tả đủ 2 món.
  • Giải thích: lớp sữa hỏi giá lõi rồi cộng phần mình.

💻 Bài tập

Ngôn ngữ: C++17 (hoặc Java). Nộp vào day-012/. Giá tính bằng đồng, kiểu long long.

Bài 1: Starbuzz Coffee

class Beverage {
 public:
  virtual std::string description() const = 0;
  virtual long long cost() const = 0;
  virtual ~Beverage() = default;
};
class CondimentDecorator : public Beverage {
 protected:
  std::unique_ptr<Beverage> inner;
 public:
  explicit CondimentDecorator(std::unique_ptr<Beverage> b);
};
LớpGiáMô tả
Espresso30.000"Espresso"
HouseBlend25.000"House Blend"
DarkRoast28.000"Dark Roast"
Milk+5.000nối ", Sữa"
Mocha+7.000nối ", Mocha"
Soy+6.000nối ", Sữa đậu nành"
Whip+4.000nối ", Kem"
DarkRoast + Mocha + Mocha + Whip  -> "Dark Roast, Mocha, Mocha, Kem" = 46000
Espresso                          -> "Espresso" = 30000
HouseBlend + Soy + Mocha + Whip   -> 42000

Bài 2: Size ly và thêm topping mới

  1. Thêm enum class Size { Small, Medium, Large } vào Beverage. Topping lấy size từ inner.
  2. Soy tính giá theo size: nhỏ 4.000, vừa 6.000, lớn 8.000.
  3. Thêm Caramel (+9.000) chỉ bằng một file mới.
Espresso(Large) + Soy -> 38000
Espresso(Small) + Soy + Caramel -> 43000

Bài 3: Vé xem phim và decorator vào-ra (nâng cao)

  1. Ticket gốc 90.000. Decorator: WeekendSurcharge (+20%), ThreeD (+30.000), StudentDiscount (-15%). Thứ tự bọc làm đổi giá: tính và giải thích 2 thứ tự khác nhau.
  2. Viết interface TextSource { virtual std::optional<char> read() = 0; }. Viết StringSource và 2 decorator: UpperCase (đổi chữ thường thành hoa) và SkipSpaces (bỏ khoảng trắng).
UpperCase(SkipSpaces(StringSource("a b c"))) đọc hết -> "ABC"
SkipSpaces(UpperCase(StringSource("x y")))   đọc hết -> "XY"

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

  • Test mẫu pass.
  • Thêm Caramel không sửa file cũ.
  • Bài 3 có bảng giá vé theo 2 thứ tự bọc kèm giải thích.
  • Không rò bộ nhớ: dùng unique_ptr, sanitizer sạch.

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

  • Decorator là gì, nói 1 câu?
  • Vì sao kế thừa gây bùng nổ lớp?
  • Giá món bọc nhiều lớp tính thế nào?
  • Khi nào không nên bọc?

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