Menu
CoddyTech

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.

challenge icon

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 MediaPlayer trừ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ỉ định
    • stop(): dừng phát
    • setVolume(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 ra Legacy: Loading [path]
    • startPlayback(): in ra Legacy: Playback started
    • stopPlayback(): in ra Legacy: Playback stopped
    • adjustVolume(double percentage): nhận một số thập phân (0.0 đến 1.0) và in ra Legacy: 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 AudioAdapter cần kế thừa từ MediaPlayer và giữ một tham chiếu đến đối tượng LegacyAudioLib. 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 cho stopPlayback()
    • 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:

    1. Tên tệp cần phát (chuỗi)
    2. Mức âm lượng (số nguyên, 0-100)

    Tạo một instance LegacyAudioLib và một AudioAdapter bao bọc nó. Sau đó, dùng bộ chuyển đổi thông qua giao diện MediaPlayer (lưu nó dưới dạng con trỏ MediaPlayer) để:

    1. Đặt âm lượng theo mức đầu vào
    2. Phát tệp đầu vào
    3. 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 stopped

Với đầu vào podcast.wav và 50:

Legacy: Volume set to 0.5
Legacy: Loading podcast.wav
Legacy: Playback started
Legacy: Playback stopped

Hã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;
}
quiz iconTự kiểm tra

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

Tự luyện tập: Trình biên dịch C++ online