Mẫu bộ chuyển đổi
Thuộc phần Lập trình hướng đối tượng trong hành trình C++ của Coddy. Bài học 97 trên 104.
Mẫu Adapter cho phép các giao diện không tương thích hoạt động cùng nhau. Mẫu này đóng vai trò cầu nối giữa hai lớp, chuyển đổi giao diện của một lớp thành giao diện mà các ứng dụng khách mong đợi. Cách này đặc biệt hữu ích khi tích hợp code cũ hoặc thư viện bên thứ ba không khớp với giao diện của hệ thống.
Hãy tưởng tượng bạn có một lớp hiện có thực hiện đúng điều bạn cần, nhưng giao diện của nó không khớp với yêu cầu của code. Thay vì sửa đổi một trong hai phía, bạn tạo một bộ chuyển đổi bao bọc lớp hiện có và chuyển đổi các lời gọi:
#include <iostream>
#include <string>
// Lớp hiện có với giao diện không tương thích
class LegacyPrinter {
public:
void printMessage(const std::string& msg) {
std::cout << "Legacy: " << msg << "\n";
}
};
// Giao diện đích mà mã gọi mong đợi
class Printer {
public:
virtual void print(const std::string& text) = 0;
virtual ~Printer() = default;
};
// Bộ chuyển đổi bao bọc LegacyPrinter và triển khai giao diện Printer
class PrinterAdapter : public Printer {
LegacyPrinter& legacy;
public:
PrinterAdapter(LegacyPrinter& lp) : legacy(lp) {}
void print(const std::string& text) override {
legacy.printMessage(text); // Chuyển đổi lời gọi
}
};
int main() {
LegacyPrinter oldPrinter;
PrinterAdapter adapter(oldPrinter);
// Mã gọi sử dụng giao diện mong đợi
adapter.print("Hello World");
}Bộ chuyển đổi giữ tham chiếu đến LegacyPrinter và triển khai giao diện Printer. Khi gọi print(), nó chuyển tiếp lời gọi đến printMessage(). Đoạn code phía máy khách làm việc với giao diện Printer mà không cần biết về cách triển khai cũ bên dưới.
Dùng Adapter khi bạn muốn sử dụng một lớp có sẵn nhưng giao diện của nó không phù hợp với nhu cầu của bạn, hoặc khi bạn cần tạo một lớp có thể tái sử dụng và hoạt động cùng các lớp có giao diện không tương thích.
Thử thách
DễHãy xây dựng hệ thống Bộ chuyển đổi trình phát đa phương tiện để kết nối các giao diện thư viện âm thanh khác nhau. Hãy tưởng tượng bạn đang tích hợp một thư viện âm thanh cũ vào ứng dụng trình phát đa phương tiện hiện đại: thư viện cũ hoạt động hoàn hảo, nhưng giao diện của nó không khớp với những gì trình phát của bạn mong đợi. Đây chính là lúc mẫu Adapter phát huy tác dụng.
Bạn sẽ tổ chức code trong bốn tệp:
MediaPlayer.h: Định nghĩa giao diện đích mà trình phát đa phương tiện hiện đại của bạn mong đợi.Tạo một lớp
MediaPlayertrừu tượng với các phương thức ảo thuần túy sau:play(const std::string& filename): phát tệp được chỉ địnhstop(): dừng phátsetVolume(int level): đặt âm lượng (0-100)
Thêm hàm hủy ảo.
LegacyAudioLib.h: Tạo thư viện âm thanh "legacy" hiện có với giao diện không tương thích.Lớp
LegacyAudioLibđại diện cho một thư viện cũ hoạt động khác với cách trình phát của bạn mong đợi. Lớp này cần có:loadAudio(const std::string& path): in raLegacy: Loading [path]startPlayback(): in raLegacy: Playback startedstopPlayback(): in raLegacy: Playback stoppedadjustVolume(double percentage): nhận một số thập phân (0.0 đến 1.0) và in raLegacy: Volume set to [percentage](hiển thị giá trị thập phân)
AudioAdapter.h: Xây dựng bộ chuyển đổi để kết nối hai giao diện.Lớp
AudioAdaptercần kế thừa từMediaPlayervà giữ một tham chiếu đến đối tượngLegacyAudioLib. Bộ chuyển đổi chuyển các lời gọi từ giao diện hiện đại sang giao diện cũ:play()cần gọi cảloadAudio()vàstartPlayback()trên thư viện cũstop()cần ủy quyền chostopPlayback()setVolume()nhận một số nguyên (0-100), nhưng thư viện cũ cần một số thập phân (0.0-1.0), vì vậy bạn cần chuyển đổi giá trị
main.cpp: Minh họa cách bộ chuyển đổi hoạt động.Đọc hai đầu vào:
- Tên tệp cần phát (chuỗi)
- Mức âm lượng (số nguyên, 0-100)
Tạo một instance
LegacyAudioLibvà mộtAudioAdapterbao bọc nó. Sau đó, dùng bộ chuyển đổi thông qua giao diệnMediaPlayer(lưu nó dưới dạng con trỏMediaPlayer) để:- Đặt âm lượng theo mức đầu vào
- Phát tệp đầu vào
- Dừng phát
Ví dụ, với đầu vào song.mp3 và 75:
Legacy: Volume set to 0.75
Legacy: Loading song.mp3
Legacy: Playback started
Legacy: Playback stoppedVới đầu vào podcast.wav và 50:
Legacy: Volume set to 0.5
Legacy: Loading podcast.wav
Legacy: Playback started
Legacy: Playback stoppedHãy chú ý rằng code phía ứng dụng chỉ làm việc với giao diện MediaPlayer. Nó không biết thư viện cũ đang thực hiện công việc thực tế bên dưới. Bộ chuyển đổi xử lý mọi chuyển đổi, bao gồm chuyển mức âm lượng từ số nguyên sang số thập phân. Đây chính là bản chất của mẫu Adapter: giúp các giao diện không tương thích hoạt động cùng nhau mà không cần sửa đổi bên nào.
Tự làm thử
#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "LegacyAudioLib.h"
#include "AudioAdapter.h"
using namespace std;
int main() {
// Đọc dữ liệu đầu vào
string filename;
int volumeLevel;
cin >> filename;
cin >> volumeLevel;
// TODO: Tạo một instance của LegacyAudioLib
// TODO: Tạo một AudioAdapter bao bọc thư viện cũ
// TODO: Lưu adapter dưới dạng con trỏ MediaPlayer
// TODO: Dùng giao diện MediaPlayer để:
// 1. Đặt âm lượng theo mức đầu vào
// 2. Phát tệp có tên được nhập vào
// 3. Dừng phát
return 0;
}
Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.
Tất cả bài học trong Lập trình hướng đối tượng
1Nền tảng về OOP
Tệp bên ngoàiBiên dịch và tạo bản dựng C++Tệp header và tệp nguồnNamespace và phạm viGiới thiệu về OOP trong C++Lớp và đối tượngCon trỏ thisPhương thức (hàm thành viên)Thuộc tính (dữ liệu thành viên)Cơ bản về hàm khởi tạo và hàm hủyÔn tập: máy tính đơn giản4Thuộc tính của lớp
Thành viên đối tượng và thành viên tĩnhHàm truy xuất và hàm thiết lậpHàm thành viên hằngTừ khóa mutablePhương thức và biến tĩnhHàm và lớp bạnÔn tập: Trình quản lý tài khoản ngân hàng7Kế thừa
Kế thừa cơ bảnMức truy cập trong kế thừaThứ tự gọi hàm khởi tạo và hàm hủyGhi đè phương thứcHàm ảo và VTableĐa kế thừaKế thừa ảoÔn tập: hệ phân cấp nhân viên2Quản lý bộ nhớ
Bộ nhớ ngăn xếp và heapCon trỏ và tham chiếuBộ nhớ động (new/delete)Con trỏ thông minh trong C++RAII trong C++Ôn tập: Trình quản lý mảng động5Tính đóng gói
Phạm vi truy cập trong C++Tìm hiểu sâu về phạm vi truy cậpẨn thông tinStruct và lớpLớp lồng nhau và lớp bên trongÔn tập: hệ thống hồ sơ sinh viên8Đa hình
Đa hình lúc biên dịch và khi chạyNạp chồng hàmÔn lại hàm ảoHàm ảo thuần túyLớp trừu tượngThiết kế interface trong C++Ép kiểu động và RTTIÔn tập: Máy tính hình học11Các khái niệm OOP nâng cao
Kết hợp so với kế thừaMixin qua CRTPMẫu PimplXóa kiểuLớp enum và định kiểu mạnhXử lý ngoại lệ trong OOPPhân cấp ngoại lệ tùy chỉnh14Mẫu thiết kế phần 2
Mẫu lệnhMẫu bộ chuyển đổiMẫu trang tríMẫu phương thức khuôn mẫuMẫu trạng tháiMẫu tổng hợpRAII như một mẫu thiết kế3Hàm khởi tạo và hàm hủy
Hàm khởi tạo mặc địnhHàm khởi tạo có tham sốHàm khởi tạo sao chépHàm khởi tạo di chuyểnDanh sách khởi tạoHàm khởi tạo ủy quyềnTìm hiểu sâu về hàm hủyQuy tắc 3 / 5 / 0Ôn tập: lớp chuỗi6Nạp chồng toán tử
Giới thiệu về nạp chồng toán tửNạp chồng toán tử số họcNạp chồng toán tử so sánhToán tử luồngNạp chồng toán tử gánNạp chồng toán tử [] và ()Toán tử chuyển đổi kiểuÔn tập: lớp ma trận9Mẫu
Mẫu hàmMẫu lớpChuyên biệt hóa mẫuMẫu biến tham sốCơ bản về SFINAE và đặc điểm kiểuÔn tập: vùng chứa tổng quátTự luyện tập: Trình biên dịch C++ online