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
Ôn lại trước khi học
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ượng | Kế thừa lớp |
|---|---|---|
| Cơ chế | Giữ tham chiếu | Kế thừa |
| Dùng cho con của gà | Được | Không |
| Cần ngôn ngữ gì | Mọi nơi | Cầ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ớp | Hành vi |
|---|---|
MallardDuck : Duck | quack() trả "Quạc", fly() trả "Bay xa" |
WildTurkey : Turkey | gobble() trả "Cục tác", fly() trả "Bay ngắn" |
TurkeyAdapter : Duck | Giữ Turkey&. quack() gọi gobble(). fly() gọi turkey.fly() 5 lần, nối kết quả bằng " " |
DuckAdapter : Turkey | Chiề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
- Viết
TurkeyClassAdapter : public Duck, private WildTurkeybằng đa kế thừa. - Tạo
OldTurkey : WildTurkeyghi đèfly()trả"Không bay nổi". Object adapter dùng đượcOldTurkey, class adapter thì không. Viết test chứng minh. - 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.