Adapter: vịt-gà tây và object vs class adapter

Định nghĩa Adapter, câu chuyện vịt kêu quạc gặp gà tây kêu cục tác, và so sánh bộ điều hợp đối tượng với bộ điều hợp lớp.
Trung bìnhDesign pattern ~60 phút

Giới thiệu

Bài này học cục chuyển đổi giao diện. Bạn cho gà tây đóng giả vịt.

Kiến thức cần nhớ

Adapter là gì

  • Đổi giao diện cũ thành giao diện khách chờ.
  • Không sửa bên cũ, không sửa bên mới.
  • Ví dụ đời thường: cục chuyển phích cắm du lịch.
  • Giúp 2 bên không khớp vẫn làm việc chung.

Gà tây giả vịt

  • Vịt kêu quạc bay xa, gà kêu cục tác bay ngắn.
  • Bộ đổi đứng giữa, ngoài là vịt trong là gà.
  • Kêu thì dịch thành cục tác.
  • Bay xa thì cho bay ngắn nhiều lần.
  • Ví dụ đời thường: phiên dịch viên giữa 2 người.

Hai biến thể và phân biệt

  • Bọc đối tượng: giữ gà bên trong, linh hoạt.
  • Kế thừa lớp: vừa là vịt vừa là gà, gọn nhưng cứng.
  • Nên ưu tiên bọc đối tượng.
  • Ví dụ đời thường: thuê dịch vụ và tự học tiếng.
Tiêu chíBọc đối tượngKế thừa lớp
Cơ chếGiữ tham chiếuKế thừa
Dùng cho con của gàĐượcKhông
Cần ngôn ngữ gìMọi nơiCần đa kế thừa

Ví dụ

  • Input: bầy vịt gọi kêu. Output: nghe cục tác nhưng vẫn chấp nhận.
  • Giải thích: bộ đổi dịch đúng chuẩn vịt ra ngoài.

💻 Bài tập

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

Bài 1: Gà tây giả vịt (object adapter)

struct Duck   { virtual std::string quack() = 0;  virtual std::string fly() = 0; virtual ~Duck() = default; };
struct Turkey { virtual std::string gobble() = 0; virtual std::string fly() = 0; virtual ~Turkey() = default; };
LớpHành vi
MallardDuck : Duckquack() trả "Quạc", fly() trả "Bay xa"
WildTurkey : Turkeygobble() trả "Cục tác", fly() trả "Bay ngắn"
TurkeyAdapter : DuckGiữ Turkey&. quack() gọi gobble(). fly() gọi turkey.fly() 5 lần, nối kết quả bằng " "
DuckAdapter : TurkeyChiều ngược lại. fly() của vịt chỉ được gọi 1 lần mỗi 5 lần fly() của gà tây; 4 lần còn lại trả "Nghỉ"

Viết void testDuck(Duck &d) in quack() và fly(). Truyền cả MallardDuck và TurkeyAdapter vào.

testDuck(TurkeyAdapter(turkey)) -> "Cục tác", "Bay ngắn Bay ngắn Bay ngắn Bay ngắn Bay ngắn"
DuckAdapter gọi fly 6 lần       -> "Bay xa", "Nghỉ", "Nghỉ", "Nghỉ", "Nghỉ", "Bay xa"

Bài 2: Class adapter và so sánh

  1. Viết TurkeyClassAdapter : public Duck, private WildTurkey bằng đa kế thừa.
  2. Tạo OldTurkey : WildTurkey ghi đè fly() trả "Không bay nổi". Object adapter dùng được OldTurkey, class adapter thì không. Viết test chứng minh.
  3. Viết bảng so sánh 2 biến thể vào README.md.

Bài 3: Adapter cho thư viện thật (nâng cao)

Ứng dụng của bạn có interface:

struct Logger { virtual void log(Level lv, const std::string &msg) = 0; };  // Level: Info, Warn, Error

Có 2 "thư viện ngoài" với API khác nhau (tự viết giả lập, không được sửa):

class LegacyFileLog { public: void write(const char *line); };                       // ghi "[I] msg"
class CloudLog { public: void send(std::map<std::string, std::string> fields); };    // {"severity", "text"}

Viết LegacyFileLogAdapter và CloudLogAdapter. Ứng dụng chỉ biết Logger. Viết test cho mỗi adapter kiểm tra định dạng đầu ra đúng.

log(Warn, "đĩa đầy") qua LegacyFileLogAdapter -> write("[W] đĩa đầy")
log(Error, "mất kết nối") qua CloudLogAdapter  -> send({severity: "ERROR", text: "mất kết nối"})

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

  • Test mẫu pass.
  • Không sửa Turkey, LegacyFileLog, CloudLog.
  • Có test chứng minh class adapter không dùng được với lớp con của adaptee.
  • Phân biệt được Adapter với Decorator: Adapter đổi interface, Decorator giữ interface.

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

  • Adapter giải bài toán gì?
  • Gà tây giả vịt bằng cách nào?
  • Bọc đối tượng khác kế thừa lớp ở đâu?
  • Adapter khác Decorator ở đâu?

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